fix(gsd): add codebase validation in validatePreferences so preferences are not silently dropped
The codebase preferences block was accepted as a known key but never validated or assigned in validatePreferences(), causing all user-configured codebase defaults to be silently discarded. Adds validation for exclude_patterns (string[]), max_files (positive int), and collapse_threshold (positive int) with unknown-key warnings and 4 new tests.
This commit is contained in:
parent
bbe67da02c
commit
4ddb9ca8a5
2 changed files with 107 additions and 0 deletions
|
|
@ -857,5 +857,50 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── Codebase Map ──────────────────────────────────────────────────
|
||||
if (preferences.codebase !== undefined) {
|
||||
if (typeof preferences.codebase === "object" && preferences.codebase !== null) {
|
||||
const cb = preferences.codebase as Record<string, unknown>;
|
||||
const validCb: import("./preferences-types.js").CodebaseMapPreferences = {};
|
||||
|
||||
if (cb.exclude_patterns !== undefined) {
|
||||
if (Array.isArray(cb.exclude_patterns) && cb.exclude_patterns.every((p: unknown) => typeof p === "string")) {
|
||||
validCb.exclude_patterns = cb.exclude_patterns as string[];
|
||||
} else {
|
||||
errors.push("codebase.exclude_patterns must be an array of strings");
|
||||
}
|
||||
}
|
||||
if (cb.max_files !== undefined) {
|
||||
const mf = typeof cb.max_files === "number" ? cb.max_files : Number(cb.max_files);
|
||||
if (Number.isFinite(mf) && mf >= 1) {
|
||||
validCb.max_files = Math.floor(mf);
|
||||
} else {
|
||||
errors.push("codebase.max_files must be a positive integer");
|
||||
}
|
||||
}
|
||||
if (cb.collapse_threshold !== undefined) {
|
||||
const ct = typeof cb.collapse_threshold === "number" ? cb.collapse_threshold : Number(cb.collapse_threshold);
|
||||
if (Number.isFinite(ct) && ct >= 1) {
|
||||
validCb.collapse_threshold = Math.floor(ct);
|
||||
} else {
|
||||
errors.push("codebase.collapse_threshold must be a positive integer");
|
||||
}
|
||||
}
|
||||
|
||||
const knownCbKeys = new Set(["exclude_patterns", "max_files", "collapse_threshold"]);
|
||||
for (const key of Object.keys(cb)) {
|
||||
if (!knownCbKeys.has(key)) {
|
||||
warnings.push(`unknown codebase key "${key}" — ignored`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(validCb).length > 0) {
|
||||
validated.codebase = validCb;
|
||||
}
|
||||
} else {
|
||||
errors.push("codebase must be an object");
|
||||
}
|
||||
}
|
||||
|
||||
return { preferences: validated, errors, warnings };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -461,3 +461,65 @@ test("experimental.rtk defaults to off in new project preferences", () => {
|
|||
assert.notEqual(prefs, null);
|
||||
assert.equal(prefs!.experimental?.rtk, undefined);
|
||||
});
|
||||
|
||||
// ── Codebase Map Preferences ─────────────────────────────────────────────────
|
||||
|
||||
test("codebase preferences validate and pass through correctly", () => {
|
||||
const result = validatePreferences({
|
||||
codebase: {
|
||||
exclude_patterns: ["docs/", "fixtures/"],
|
||||
max_files: 1000,
|
||||
collapse_threshold: 15,
|
||||
},
|
||||
});
|
||||
assert.equal(result.errors.length, 0);
|
||||
assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/", "fixtures/"]);
|
||||
assert.equal(result.preferences.codebase?.max_files, 1000);
|
||||
assert.equal(result.preferences.codebase?.collapse_threshold, 15);
|
||||
});
|
||||
|
||||
test("codebase preferences reject invalid types", () => {
|
||||
const result = validatePreferences({
|
||||
codebase: {
|
||||
exclude_patterns: "not-an-array" as any,
|
||||
max_files: -5,
|
||||
collapse_threshold: 0,
|
||||
},
|
||||
});
|
||||
assert.ok(result.errors.some(e => e.includes("exclude_patterns must be an array")));
|
||||
assert.ok(result.errors.some(e => e.includes("max_files must be a positive")));
|
||||
assert.ok(result.errors.some(e => e.includes("collapse_threshold must be a positive")));
|
||||
});
|
||||
|
||||
test("codebase preferences warn on unknown keys", () => {
|
||||
const result = validatePreferences({
|
||||
codebase: {
|
||||
exclude_patterns: ["docs/"],
|
||||
unknown_key: true,
|
||||
} as any,
|
||||
});
|
||||
assert.equal(result.errors.length, 0);
|
||||
assert.ok(result.warnings.some(w => w.includes('unknown codebase key "unknown_key"')));
|
||||
assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/"]);
|
||||
});
|
||||
|
||||
test("codebase preferences parse from markdown frontmatter", () => {
|
||||
const content = [
|
||||
"---",
|
||||
"version: 1",
|
||||
"codebase:",
|
||||
" exclude_patterns:",
|
||||
' - "docs/"',
|
||||
' - ".cache/"',
|
||||
" max_files: 800",
|
||||
" collapse_threshold: 10",
|
||||
"---",
|
||||
].join("\n");
|
||||
const prefs = parsePreferencesMarkdown(content);
|
||||
assert.notEqual(prefs, null);
|
||||
const result = validatePreferences(prefs!);
|
||||
assert.equal(result.errors.length, 0);
|
||||
assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/", ".cache/"]);
|
||||
assert.equal(result.preferences.codebase?.max_files, 800);
|
||||
assert.equal(result.preferences.codebase?.collapse_threshold, 10);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue