From efbbcc790de71a99ffcb220f1c26cc540c019a22 Mon Sep 17 00:00:00 2001 From: Jeremy McSpadden Date: Thu, 19 Mar 2026 08:55:25 -0500 Subject: [PATCH] fix(prefs): close merge, validation, serialization, and docs gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mergePreferences(): add auto_visualize and auto_report (both were silently dropped when a project prefs file existed alongside global) - preferences-validation.ts: add validation blocks for auto_visualize, auto_report, compression_strategy, and context_selection — all four were in KNOWN_PREFERENCE_KEYS and the GSDPreferences interface but accepted any value without type-checking - serializePreferencesToFrontmatter orderedKeys: add skill_staleness_days, dynamic_routing, token_profile, phases, parallel, auto_visualize, auto_report, verification_commands, verification_auto_fix, verification_max_retries, search_provider, compression_strategy, context_selection — these were falling through to the arbitrary-order fallback loop instead of appearing in consistent positions - preferences-reference.md: document git.auto_pr, git.pr_target_branch, search_provider, compression_strategy, context_selection; add deprecation notices for git.commit_docs and git.merge_to_main - tests/preferences.test.ts: two new test cases covering all four newly validated fields (valid values pass, invalid values produce errors) --- .../extensions/gsd/commands-prefs-wizard.ts | 6 ++- .../gsd/docs/preferences-reference.md | 10 +++++ .../extensions/gsd/preferences-validation.ts | 38 +++++++++++++++++++ src/resources/extensions/gsd/preferences.ts | 2 + .../extensions/gsd/tests/preferences.test.ts | 28 ++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/commands-prefs-wizard.ts b/src/resources/extensions/gsd/commands-prefs-wizard.ts index ec695ea32..a0baf1035 100644 --- a/src/resources/extensions/gsd/commands-prefs-wizard.ts +++ b/src/resources/extensions/gsd/commands-prefs-wizard.ts @@ -738,10 +738,14 @@ export function serializePreferencesToFrontmatter(prefs: Record const orderedKeys = [ "version", "mode", "always_use_skills", "prefer_skills", "avoid_skills", "skill_rules", "custom_instructions", "models", "skill_discovery", - "auto_supervisor", "uat_dispatch", "unique_milestone_ids", + "skill_staleness_days", "auto_supervisor", "uat_dispatch", "unique_milestone_ids", "budget_ceiling", "budget_enforcement", "context_pause_threshold", "notifications", "remote_questions", "git", "post_unit_hooks", "pre_dispatch_hooks", + "dynamic_routing", "token_profile", "phases", "parallel", + "auto_visualize", "auto_report", + "verification_commands", "verification_auto_fix", "verification_max_retries", + "search_provider", "compression_strategy", "context_selection", ]; const seen = new Set(); diff --git a/src/resources/extensions/gsd/docs/preferences-reference.md b/src/resources/extensions/gsd/docs/preferences-reference.md index 6e7af17a2..08b075fa5 100644 --- a/src/resources/extensions/gsd/docs/preferences-reference.md +++ b/src/resources/extensions/gsd/docs/preferences-reference.md @@ -128,6 +128,10 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea - `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for step-mode with hot reloads). Default: `"worktree"`. - `manage_gitignore`: boolean — when `false`, GSD will not touch `.gitignore` at all. Useful when your project has a strictly managed `.gitignore` and you don't want GSD adding entries. Default: `true`. - `worktree_post_create`: string — script to run after a worktree is created (both auto-mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none. + - `auto_pr`: boolean — automatically create a GitHub pull request after a milestone branch is merged. Requires `gh` CLI to be installed. Default: `false`. + - `pr_target_branch`: string — branch to target when `auto_pr` is enabled. Defaults to `main_branch` when omitted. + - **Deprecated:** `commit_docs` — no longer valid; `.gsd/` is always gitignored. Remove this setting. + - **Deprecated:** `merge_to_main` — no longer valid; milestone-level merge is always used. Remove this setting. - `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`. @@ -174,6 +178,12 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea - `auto_report`: boolean — generate an HTML report snapshot after each milestone completion. Default: `true`. +- `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`. + +- `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`. + +- `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`. + - `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys: - `enabled`: boolean — enable parallel execution. Default: `false`. - `max_workers`: number — maximum concurrent workers (1-4). Default: `2`. diff --git a/src/resources/extensions/gsd/preferences-validation.ts b/src/resources/extensions/gsd/preferences-validation.ts index 739a4bf12..12df09fa1 100644 --- a/src/resources/extensions/gsd/preferences-validation.ts +++ b/src/resources/extensions/gsd/preferences-validation.ts @@ -586,5 +586,43 @@ export function validatePreferences(preferences: GSDPreferences): { } } + // ─── Auto Visualize ───────────────────────────────────────────────── + if (preferences.auto_visualize !== undefined) { + if (typeof preferences.auto_visualize === "boolean") { + validated.auto_visualize = preferences.auto_visualize; + } else { + errors.push("auto_visualize must be a boolean"); + } + } + + // ─── Auto Report ──────────────────────────────────────────────────── + if (preferences.auto_report !== undefined) { + if (typeof preferences.auto_report === "boolean") { + validated.auto_report = preferences.auto_report; + } else { + errors.push("auto_report must be a boolean"); + } + } + + // ─── Compression Strategy ─────────────────────────────────────────── + if (preferences.compression_strategy !== undefined) { + const validStrategies = new Set(["truncate", "compress"]); + if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) { + validated.compression_strategy = preferences.compression_strategy as GSDPreferences["compression_strategy"]; + } else { + errors.push(`compression_strategy must be one of: truncate, compress`); + } + } + + // ─── Context Selection ────────────────────────────────────────────── + if (preferences.context_selection !== undefined) { + const validModes = new Set(["full", "smart"]); + if (typeof preferences.context_selection === "string" && validModes.has(preferences.context_selection)) { + validated.context_selection = preferences.context_selection as GSDPreferences["context_selection"]; + } else { + errors.push(`context_selection must be one of: full, smart`); + } + } + return { preferences: validated, errors, warnings }; } diff --git a/src/resources/extensions/gsd/preferences.ts b/src/resources/extensions/gsd/preferences.ts index e4a5725b8..e83f3f400 100644 --- a/src/resources/extensions/gsd/preferences.ts +++ b/src/resources/extensions/gsd/preferences.ts @@ -252,6 +252,8 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr search_provider: override.search_provider ?? base.search_provider, compression_strategy: override.compression_strategy ?? base.compression_strategy, context_selection: override.context_selection ?? base.context_selection, + auto_visualize: override.auto_visualize ?? base.auto_visualize, + auto_report: override.auto_report ?? base.auto_report, }; } diff --git a/src/resources/extensions/gsd/tests/preferences.test.ts b/src/resources/extensions/gsd/tests/preferences.test.ts index e03b24722..06de5f649 100644 --- a/src/resources/extensions/gsd/tests/preferences.test.ts +++ b/src/resources/extensions/gsd/tests/preferences.test.ts @@ -175,6 +175,34 @@ test("git fields comprehensive validation", () => { assert.equal(preferences.git?.isolation, "branch"); }); +test("auto_visualize, auto_report, compression_strategy, context_selection validate correctly", () => { + const { preferences, errors } = validatePreferences({ + auto_visualize: true, + auto_report: false, + compression_strategy: "compress", + context_selection: "smart", + }); + assert.equal(errors.length, 0); + assert.equal(preferences.auto_visualize, true); + assert.equal(preferences.auto_report, false); + assert.equal(preferences.compression_strategy, "compress"); + assert.equal(preferences.context_selection, "smart"); +}); + +test("auto_visualize, auto_report, compression_strategy, context_selection reject invalid values", () => { + const { errors: e1 } = validatePreferences({ auto_visualize: "yes" as never }); + assert.ok(e1.some(e => e.includes("auto_visualize"))); + + const { errors: e2 } = validatePreferences({ auto_report: 1 as never }); + assert.ok(e2.some(e => e.includes("auto_report"))); + + const { errors: e3 } = validatePreferences({ compression_strategy: "shrink" as never }); + assert.ok(e3.some(e => e.includes("compression_strategy"))); + + const { errors: e4 } = validatePreferences({ context_selection: "partial" as never }); + assert.ok(e4.some(e => e.includes("context_selection"))); +}); + test("all wizard fields together produce no errors", () => { const { errors, warnings } = validatePreferences({ version: 1,