gsd-pi 2.18.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +690 -39
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +654 -36
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +84 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/dist/resources/extensions/gsd/preferences.ts +198 -150
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/dist/resources/extensions/remote-questions/format.ts +166 -14
- package/dist/resources/extensions/remote-questions/manager.ts +14 -4
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +690 -39
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +654 -36
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +84 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/src/resources/extensions/gsd/preferences.ts +198 -150
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/src/resources/extensions/remote-questions/format.ts +166 -14
- package/src/resources/extensions/remote-questions/manager.ts +14 -4
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -2,8 +2,11 @@ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { isAbsolute, join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@gsd/pi-coding-agent";
|
|
5
|
+
import { parse as parseYaml } from "yaml";
|
|
5
6
|
import type { GitPreferences } from "./git-service.js";
|
|
6
7
|
import type { PostUnitHookConfig, PreDispatchHookConfig, BudgetEnforcementMode, NotificationPreferences, TokenProfile, InlineLevel, PhaseSkipPreferences } from "./types.js";
|
|
8
|
+
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
9
|
+
import { defaultRoutingConfig } from "./model-router.js";
|
|
7
10
|
import { VALID_BRANCH_NAME } from "./git-service.js";
|
|
8
11
|
|
|
9
12
|
const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
|
|
@@ -15,9 +18,40 @@ const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.m
|
|
|
15
18
|
const PROJECT_PREFERENCES_PATH_UPPERCASE = join(process.cwd(), ".gsd", "PREFERENCES.md");
|
|
16
19
|
const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
17
20
|
|
|
21
|
+
// ─── Workflow Modes ──────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export type WorkflowMode = "solo" | "team";
|
|
24
|
+
|
|
25
|
+
/** Default preference values for each workflow mode. */
|
|
26
|
+
const MODE_DEFAULTS: Record<WorkflowMode, Partial<GSDPreferences>> = {
|
|
27
|
+
solo: {
|
|
28
|
+
git: {
|
|
29
|
+
auto_push: true,
|
|
30
|
+
push_branches: false,
|
|
31
|
+
pre_merge_check: false,
|
|
32
|
+
merge_strategy: "squash",
|
|
33
|
+
isolation: "worktree",
|
|
34
|
+
commit_docs: true,
|
|
35
|
+
},
|
|
36
|
+
unique_milestone_ids: false,
|
|
37
|
+
},
|
|
38
|
+
team: {
|
|
39
|
+
git: {
|
|
40
|
+
auto_push: false,
|
|
41
|
+
push_branches: true,
|
|
42
|
+
pre_merge_check: true,
|
|
43
|
+
merge_strategy: "squash",
|
|
44
|
+
isolation: "worktree",
|
|
45
|
+
commit_docs: true,
|
|
46
|
+
},
|
|
47
|
+
unique_milestone_ids: true,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
18
51
|
/** All recognized top-level keys in GSDPreferences. Used to detect typos / stale config. */
|
|
19
52
|
const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
20
53
|
"version",
|
|
54
|
+
"mode",
|
|
21
55
|
"always_use_skills",
|
|
22
56
|
"prefer_skills",
|
|
23
57
|
"avoid_skills",
|
|
@@ -25,6 +59,7 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
25
59
|
"custom_instructions",
|
|
26
60
|
"models",
|
|
27
61
|
"skill_discovery",
|
|
62
|
+
"skill_staleness_days",
|
|
28
63
|
"auto_supervisor",
|
|
29
64
|
"uat_dispatch",
|
|
30
65
|
"unique_milestone_ids",
|
|
@@ -36,8 +71,10 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
36
71
|
"git",
|
|
37
72
|
"post_unit_hooks",
|
|
38
73
|
"pre_dispatch_hooks",
|
|
74
|
+
"dynamic_routing",
|
|
39
75
|
"token_profile",
|
|
40
76
|
"phases",
|
|
77
|
+
"auto_visualize",
|
|
41
78
|
]);
|
|
42
79
|
|
|
43
80
|
export interface GSDSkillRule {
|
|
@@ -102,7 +139,7 @@ export interface AutoSupervisorConfig {
|
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
export interface RemoteQuestionsConfig {
|
|
105
|
-
channel: "slack" | "discord";
|
|
142
|
+
channel: "slack" | "discord" | "telegram";
|
|
106
143
|
channel_id: string | number;
|
|
107
144
|
timeout_minutes?: number; // clamped to 1-30
|
|
108
145
|
poll_interval_seconds?: number; // clamped to 2-30
|
|
@@ -110,6 +147,7 @@ export interface RemoteQuestionsConfig {
|
|
|
110
147
|
|
|
111
148
|
export interface GSDPreferences {
|
|
112
149
|
version?: number;
|
|
150
|
+
mode?: WorkflowMode;
|
|
113
151
|
always_use_skills?: string[];
|
|
114
152
|
prefer_skills?: string[];
|
|
115
153
|
avoid_skills?: string[];
|
|
@@ -117,6 +155,7 @@ export interface GSDPreferences {
|
|
|
117
155
|
custom_instructions?: string[];
|
|
118
156
|
models?: GSDModelConfig | GSDModelConfigV2;
|
|
119
157
|
skill_discovery?: SkillDiscoveryMode;
|
|
158
|
+
skill_staleness_days?: number; // Skills unused for N days get deprioritized (#599). 0 = disabled. Default: 60.
|
|
120
159
|
auto_supervisor?: AutoSupervisorConfig;
|
|
121
160
|
uat_dispatch?: boolean;
|
|
122
161
|
unique_milestone_ids?: boolean;
|
|
@@ -128,8 +167,10 @@ export interface GSDPreferences {
|
|
|
128
167
|
git?: GitPreferences;
|
|
129
168
|
post_unit_hooks?: PostUnitHookConfig[];
|
|
130
169
|
pre_dispatch_hooks?: PreDispatchHookConfig[];
|
|
170
|
+
dynamic_routing?: DynamicRoutingConfig;
|
|
131
171
|
token_profile?: TokenProfile;
|
|
132
172
|
phases?: PhaseSkipPreferences;
|
|
173
|
+
auto_visualize?: boolean;
|
|
133
174
|
}
|
|
134
175
|
|
|
135
176
|
export interface LoadedGSDPreferences {
|
|
@@ -163,25 +204,49 @@ export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
|
|
|
163
204
|
?? loadPreferencesFile(PROJECT_PREFERENCES_PATH_UPPERCASE, "project");
|
|
164
205
|
}
|
|
165
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Apply mode defaults as the lowest-priority layer.
|
|
209
|
+
* Mode defaults fill in undefined fields; any explicit user value wins.
|
|
210
|
+
*/
|
|
211
|
+
export function applyModeDefaults(mode: WorkflowMode, prefs: GSDPreferences): GSDPreferences {
|
|
212
|
+
const defaults = MODE_DEFAULTS[mode];
|
|
213
|
+
if (!defaults) return prefs;
|
|
214
|
+
return mergePreferences(defaults, prefs);
|
|
215
|
+
}
|
|
216
|
+
|
|
166
217
|
export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
|
167
218
|
const globalPreferences = loadGlobalGSDPreferences();
|
|
168
219
|
const projectPreferences = loadProjectGSDPreferences();
|
|
169
220
|
|
|
170
221
|
if (!globalPreferences && !projectPreferences) return null;
|
|
171
|
-
if (!globalPreferences) return projectPreferences;
|
|
172
|
-
if (!projectPreferences) return globalPreferences;
|
|
173
222
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
223
|
+
let result: LoadedGSDPreferences;
|
|
224
|
+
if (!globalPreferences) {
|
|
225
|
+
result = projectPreferences!;
|
|
226
|
+
} else if (!projectPreferences) {
|
|
227
|
+
result = globalPreferences;
|
|
228
|
+
} else {
|
|
229
|
+
const mergedWarnings = [
|
|
230
|
+
...(globalPreferences.warnings ?? []),
|
|
231
|
+
...(projectPreferences.warnings ?? []),
|
|
232
|
+
];
|
|
233
|
+
result = {
|
|
234
|
+
path: projectPreferences.path,
|
|
235
|
+
scope: "project",
|
|
236
|
+
preferences: mergePreferences(globalPreferences.preferences, projectPreferences.preferences),
|
|
237
|
+
...(mergedWarnings.length > 0 ? { warnings: mergedWarnings } : {}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
178
240
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
241
|
+
// Apply mode defaults as the lowest-priority layer
|
|
242
|
+
if (result.preferences.mode) {
|
|
243
|
+
result = {
|
|
244
|
+
...result,
|
|
245
|
+
preferences: applyModeDefaults(result.preferences.mode, result.preferences),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result;
|
|
185
250
|
}
|
|
186
251
|
|
|
187
252
|
// ─── Skill Reference Resolution ───────────────────────────────────────────────
|
|
@@ -419,148 +484,27 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
|
|
|
419
484
|
|
|
420
485
|
/** @internal Exported for testing only */
|
|
421
486
|
export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
487
|
+
// Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
|
|
488
|
+
const startMarker = content.startsWith('---\r\n') ? '---\r\n' : '---\n';
|
|
489
|
+
if (!content.startsWith(startMarker)) return null;
|
|
490
|
+
const searchStart = startMarker.length;
|
|
491
|
+
const endIdx = content.indexOf('\n---', searchStart);
|
|
492
|
+
if (endIdx === -1) return null;
|
|
493
|
+
const block = content.slice(searchStart, endIdx);
|
|
494
|
+
return parseFrontmatterBlock(block.replace(/\r/g, ''));
|
|
425
495
|
}
|
|
426
496
|
|
|
427
497
|
function parseFrontmatterBlock(frontmatter: string): GSDPreferences {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
for (let i = 0; i < lines.length; i++) {
|
|
433
|
-
const line = lines[i];
|
|
434
|
-
if (!line.trim()) continue;
|
|
435
|
-
|
|
436
|
-
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
437
|
-
const trimmed = line.trim();
|
|
438
|
-
|
|
439
|
-
// Skip comment lines (standalone YAML comments)
|
|
440
|
-
if (trimmed.startsWith("#")) continue;
|
|
441
|
-
|
|
442
|
-
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
443
|
-
stack.pop();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const current = stack[stack.length - 1].value;
|
|
447
|
-
const keyMatch = trimmed.match(/^([A-Za-z0-9_]+):(.*)$/);
|
|
448
|
-
if (!keyMatch) continue;
|
|
449
|
-
|
|
450
|
-
const [, key, remainder] = keyMatch;
|
|
451
|
-
// Strip inline comments from the value portion
|
|
452
|
-
const valuePart = remainder.replace(/\s+#.*$/, "").trim();
|
|
453
|
-
|
|
454
|
-
if (valuePart === "") {
|
|
455
|
-
const nextLine = lines[i + 1] ?? "";
|
|
456
|
-
const nextTrimmed = nextLine.trim();
|
|
457
|
-
if (nextTrimmed.startsWith("- ")) {
|
|
458
|
-
const items: unknown[] = [];
|
|
459
|
-
let j = i + 1;
|
|
460
|
-
while (j < lines.length) {
|
|
461
|
-
const candidate = lines[j];
|
|
462
|
-
const candidateIndent = candidate.match(/^\s*/)?.[0].length ?? 0;
|
|
463
|
-
const candidateTrimmed = candidate.trim();
|
|
464
|
-
if (!candidateTrimmed) {
|
|
465
|
-
j++;
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
if (candidateIndent <= indent || !candidateTrimmed.startsWith("- ")) break;
|
|
469
|
-
|
|
470
|
-
const itemText = candidateTrimmed.slice(2).trim();
|
|
471
|
-
const nextCandidate = lines[j + 1] ?? "";
|
|
472
|
-
const nextCandidateIndent = nextCandidate.match(/^\s*/)?.[0].length ?? 0;
|
|
473
|
-
const nextCandidateTrimmed = nextCandidate.trim();
|
|
474
|
-
|
|
475
|
-
// Treat an array item as a structured object only when:
|
|
476
|
-
// a) It looks like a YAML key-value pair (key starts with [A-Za-z0-9_]+:), OR
|
|
477
|
-
// b) The next line is indented deeper (nested block under this item).
|
|
478
|
-
// Bare colons (e.g. "qwen/qwen3-coder:free") are NOT key-value pairs.
|
|
479
|
-
const looksLikeKeyValue = /^[A-Za-z0-9_]+:/.test(itemText);
|
|
480
|
-
if (looksLikeKeyValue || (nextCandidateTrimmed && nextCandidateIndent > candidateIndent)) {
|
|
481
|
-
const obj: Record<string, unknown> = {};
|
|
482
|
-
const firstMatch = itemText.match(/^([A-Za-z0-9_]+):(.*)$/);
|
|
483
|
-
if (firstMatch) {
|
|
484
|
-
obj[firstMatch[1]] = parseScalar(firstMatch[2].trim());
|
|
485
|
-
}
|
|
486
|
-
j++;
|
|
487
|
-
while (j < lines.length) {
|
|
488
|
-
const nested = lines[j];
|
|
489
|
-
const nestedIndent = nested.match(/^\s*/)?.[0].length ?? 0;
|
|
490
|
-
const nestedTrimmed = nested.trim();
|
|
491
|
-
if (!nestedTrimmed) {
|
|
492
|
-
j++;
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (nestedIndent <= candidateIndent) break;
|
|
496
|
-
const nestedMatch = nestedTrimmed.match(/^([A-Za-z0-9_]+):(.*)$/);
|
|
497
|
-
if (nestedMatch) {
|
|
498
|
-
const nestedValue = nestedMatch[2].trim();
|
|
499
|
-
if (nestedValue === "") {
|
|
500
|
-
const nestedItems: string[] = [];
|
|
501
|
-
j++;
|
|
502
|
-
while (j < lines.length) {
|
|
503
|
-
const nestedArrayLine = lines[j];
|
|
504
|
-
const nestedArrayIndent = nestedArrayLine.match(/^\s*/)?.[0].length ?? 0;
|
|
505
|
-
const nestedArrayTrimmed = nestedArrayLine.trim();
|
|
506
|
-
if (!nestedArrayTrimmed) {
|
|
507
|
-
j++;
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (nestedArrayIndent <= nestedIndent || !nestedArrayTrimmed.startsWith("- ")) break;
|
|
511
|
-
nestedItems.push(String(parseScalar(nestedArrayTrimmed.slice(2).trim())));
|
|
512
|
-
j++;
|
|
513
|
-
}
|
|
514
|
-
obj[nestedMatch[1]] = nestedItems;
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
obj[nestedMatch[1]] = parseScalar(nestedValue);
|
|
518
|
-
}
|
|
519
|
-
j++;
|
|
520
|
-
}
|
|
521
|
-
items.push(obj);
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
items.push(parseScalar(itemText));
|
|
526
|
-
j++;
|
|
527
|
-
}
|
|
528
|
-
current[key] = items;
|
|
529
|
-
i = j - 1;
|
|
530
|
-
} else {
|
|
531
|
-
const obj: Record<string, unknown> = {};
|
|
532
|
-
current[key] = obj;
|
|
533
|
-
stack.push({ indent, value: obj });
|
|
534
|
-
}
|
|
535
|
-
continue;
|
|
498
|
+
try {
|
|
499
|
+
const parsed = parseYaml(frontmatter);
|
|
500
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
501
|
+
return {} as GSDPreferences;
|
|
536
502
|
}
|
|
537
|
-
|
|
538
|
-
|
|
503
|
+
return parsed as GSDPreferences;
|
|
504
|
+
} catch (e) {
|
|
505
|
+
console.error("[parseFrontmatterBlock] YAML parse error:", e);
|
|
506
|
+
return {} as GSDPreferences;
|
|
539
507
|
}
|
|
540
|
-
|
|
541
|
-
return root as GSDPreferences;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function parseScalar(value: string): unknown {
|
|
545
|
-
// Strip inline YAML comments: " # comment" (# preceded by whitespace).
|
|
546
|
-
// Quoted strings are returned as-is (the comment is inside quotes).
|
|
547
|
-
const quoteMatch = value.match(/^(['"])(.*)(\1)$/);
|
|
548
|
-
if (quoteMatch) return quoteMatch[2];
|
|
549
|
-
|
|
550
|
-
const stripped = value.replace(/\s+#.*$/, "");
|
|
551
|
-
if (stripped === "true") return true;
|
|
552
|
-
if (stripped === "false") return false;
|
|
553
|
-
// Recognize empty array/object literals (with or without surrounding quotes)
|
|
554
|
-
const unquoted = stripped.replace(/^['\"]|['\"]$/g, "");
|
|
555
|
-
if (unquoted === "[]") return [];
|
|
556
|
-
if (unquoted === "{}") return {};
|
|
557
|
-
if (/^-?\d+$/.test(stripped)) {
|
|
558
|
-
const n = Number(stripped);
|
|
559
|
-
// Keep large integers (e.g. Discord channel IDs) as strings to avoid precision loss
|
|
560
|
-
if (Number.isSafeInteger(n)) return n;
|
|
561
|
-
return stripped;
|
|
562
|
-
}
|
|
563
|
-
return unquoted;
|
|
564
508
|
}
|
|
565
509
|
|
|
566
510
|
/**
|
|
@@ -572,6 +516,15 @@ export function resolveSkillDiscoveryMode(): SkillDiscoveryMode {
|
|
|
572
516
|
return prefs?.preferences.skill_discovery ?? "suggest";
|
|
573
517
|
}
|
|
574
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Resolve the skill staleness threshold in days.
|
|
521
|
+
* Returns 0 if disabled, default 60 if not configured.
|
|
522
|
+
*/
|
|
523
|
+
export function resolveSkillStalenessDays(): number {
|
|
524
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
525
|
+
return prefs?.preferences.skill_staleness_days ?? 60;
|
|
526
|
+
}
|
|
527
|
+
|
|
575
528
|
/**
|
|
576
529
|
* Resolve which model ID to use for a given auto-mode unit type.
|
|
577
530
|
* Returns undefined if no model preference is set for this unit type.
|
|
@@ -674,6 +627,20 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
|
|
|
674
627
|
};
|
|
675
628
|
}
|
|
676
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Resolve the dynamic routing configuration from effective preferences.
|
|
632
|
+
* Returns the merged config with defaults applied.
|
|
633
|
+
*/
|
|
634
|
+
export function resolveDynamicRoutingConfig(): DynamicRoutingConfig {
|
|
635
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
636
|
+
const configured = prefs?.preferences.dynamic_routing;
|
|
637
|
+
if (!configured) return defaultRoutingConfig();
|
|
638
|
+
return {
|
|
639
|
+
...defaultRoutingConfig(),
|
|
640
|
+
...configured,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
677
644
|
export function resolveAutoSupervisorConfig(): AutoSupervisorConfig {
|
|
678
645
|
const prefs = loadEffectiveGSDPreferences();
|
|
679
646
|
const configured = prefs?.preferences.auto_supervisor ?? {};
|
|
@@ -756,6 +723,7 @@ export function resolveInlineLevel(): InlineLevel {
|
|
|
756
723
|
function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPreferences {
|
|
757
724
|
return {
|
|
758
725
|
version: override.version ?? base.version,
|
|
726
|
+
mode: override.mode ?? base.mode,
|
|
759
727
|
always_use_skills: mergeStringLists(base.always_use_skills, override.always_use_skills),
|
|
760
728
|
prefer_skills: mergeStringLists(base.prefer_skills, override.prefer_skills),
|
|
761
729
|
avoid_skills: mergeStringLists(base.avoid_skills, override.avoid_skills),
|
|
@@ -763,6 +731,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
763
731
|
custom_instructions: mergeStringLists(base.custom_instructions, override.custom_instructions),
|
|
764
732
|
models: { ...(base.models ?? {}), ...(override.models ?? {}) },
|
|
765
733
|
skill_discovery: override.skill_discovery ?? base.skill_discovery,
|
|
734
|
+
skill_staleness_days: override.skill_staleness_days ?? base.skill_staleness_days,
|
|
766
735
|
auto_supervisor: { ...(base.auto_supervisor ?? {}), ...(override.auto_supervisor ?? {}) },
|
|
767
736
|
uat_dispatch: override.uat_dispatch ?? base.uat_dispatch,
|
|
768
737
|
unique_milestone_ids: override.unique_milestone_ids ?? base.unique_milestone_ids,
|
|
@@ -780,6 +749,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
780
749
|
: undefined,
|
|
781
750
|
post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
|
|
782
751
|
pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
|
|
752
|
+
dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
|
|
753
|
+
? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
|
|
754
|
+
: undefined,
|
|
783
755
|
token_profile: override.token_profile ?? base.token_profile,
|
|
784
756
|
phases: (base.phases || override.phases)
|
|
785
757
|
? { ...(base.phases ?? {}), ...(override.phases ?? {}) }
|
|
@@ -811,6 +783,16 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
811
783
|
}
|
|
812
784
|
}
|
|
813
785
|
|
|
786
|
+
// ─── Workflow Mode ──────────────────────────────────────────────────
|
|
787
|
+
if (preferences.mode !== undefined) {
|
|
788
|
+
const validModes = new Set<string>(["solo", "team"]);
|
|
789
|
+
if (typeof preferences.mode === "string" && validModes.has(preferences.mode)) {
|
|
790
|
+
validated.mode = preferences.mode as WorkflowMode;
|
|
791
|
+
} else {
|
|
792
|
+
errors.push(`invalid mode "${preferences.mode}" — must be one of: solo, team`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
814
796
|
const validDiscoveryModes = new Set(["auto", "suggest", "off"]);
|
|
815
797
|
if (preferences.skill_discovery) {
|
|
816
798
|
if (validDiscoveryModes.has(preferences.skill_discovery)) {
|
|
@@ -820,6 +802,15 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
820
802
|
}
|
|
821
803
|
}
|
|
822
804
|
|
|
805
|
+
if (preferences.skill_staleness_days !== undefined) {
|
|
806
|
+
const days = Number(preferences.skill_staleness_days);
|
|
807
|
+
if (Number.isFinite(days) && days >= 0) {
|
|
808
|
+
validated.skill_staleness_days = Math.floor(days);
|
|
809
|
+
} else {
|
|
810
|
+
errors.push(`invalid skill_staleness_days: must be a non-negative number`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
823
814
|
validated.always_use_skills = normalizeStringList(preferences.always_use_skills);
|
|
824
815
|
validated.prefer_skills = normalizeStringList(preferences.prefer_skills);
|
|
825
816
|
validated.avoid_skills = normalizeStringList(preferences.avoid_skills);
|
|
@@ -1100,6 +1091,56 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
1100
1091
|
}
|
|
1101
1092
|
}
|
|
1102
1093
|
|
|
1094
|
+
// ─── Dynamic Routing ─────────────────────────────────────────────────
|
|
1095
|
+
if (preferences.dynamic_routing !== undefined) {
|
|
1096
|
+
if (typeof preferences.dynamic_routing === "object" && preferences.dynamic_routing !== null) {
|
|
1097
|
+
const dr = preferences.dynamic_routing as unknown as Record<string, unknown>;
|
|
1098
|
+
const validDr: Partial<DynamicRoutingConfig> = {};
|
|
1099
|
+
|
|
1100
|
+
if (dr.enabled !== undefined) {
|
|
1101
|
+
if (typeof dr.enabled === "boolean") validDr.enabled = dr.enabled;
|
|
1102
|
+
else errors.push("dynamic_routing.enabled must be a boolean");
|
|
1103
|
+
}
|
|
1104
|
+
if (dr.escalate_on_failure !== undefined) {
|
|
1105
|
+
if (typeof dr.escalate_on_failure === "boolean") validDr.escalate_on_failure = dr.escalate_on_failure;
|
|
1106
|
+
else errors.push("dynamic_routing.escalate_on_failure must be a boolean");
|
|
1107
|
+
}
|
|
1108
|
+
if (dr.budget_pressure !== undefined) {
|
|
1109
|
+
if (typeof dr.budget_pressure === "boolean") validDr.budget_pressure = dr.budget_pressure;
|
|
1110
|
+
else errors.push("dynamic_routing.budget_pressure must be a boolean");
|
|
1111
|
+
}
|
|
1112
|
+
if (dr.cross_provider !== undefined) {
|
|
1113
|
+
if (typeof dr.cross_provider === "boolean") validDr.cross_provider = dr.cross_provider;
|
|
1114
|
+
else errors.push("dynamic_routing.cross_provider must be a boolean");
|
|
1115
|
+
}
|
|
1116
|
+
if (dr.hooks !== undefined) {
|
|
1117
|
+
if (typeof dr.hooks === "boolean") validDr.hooks = dr.hooks;
|
|
1118
|
+
else errors.push("dynamic_routing.hooks must be a boolean");
|
|
1119
|
+
}
|
|
1120
|
+
if (dr.tier_models !== undefined) {
|
|
1121
|
+
if (typeof dr.tier_models === "object" && dr.tier_models !== null) {
|
|
1122
|
+
const tm = dr.tier_models as Record<string, unknown>;
|
|
1123
|
+
const validTm: Record<string, string> = {};
|
|
1124
|
+
for (const tier of ["light", "standard", "heavy"]) {
|
|
1125
|
+
if (tm[tier] !== undefined) {
|
|
1126
|
+
if (typeof tm[tier] === "string") validTm[tier] = tm[tier] as string;
|
|
1127
|
+
else errors.push(`dynamic_routing.tier_models.${tier} must be a string`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (Object.keys(validTm).length > 0) validDr.tier_models = validTm as DynamicRoutingConfig["tier_models"];
|
|
1131
|
+
} else {
|
|
1132
|
+
errors.push("dynamic_routing.tier_models must be an object");
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (Object.keys(validDr).length > 0) {
|
|
1137
|
+
validated.dynamic_routing = validDr as unknown as DynamicRoutingConfig;
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
errors.push("dynamic_routing must be an object");
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1103
1144
|
// ─── Git Preferences ───────────────────────────────────────────────────
|
|
1104
1145
|
if (preferences.git && typeof preferences.git === "object") {
|
|
1105
1146
|
const git: Record<string, unknown> = {};
|
|
@@ -1167,6 +1208,13 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
1167
1208
|
if (typeof g.commit_docs === "boolean") git.commit_docs = g.commit_docs;
|
|
1168
1209
|
else errors.push("git.commit_docs must be a boolean");
|
|
1169
1210
|
}
|
|
1211
|
+
if (g.worktree_post_create !== undefined) {
|
|
1212
|
+
if (typeof g.worktree_post_create === "string" && g.worktree_post_create.trim()) {
|
|
1213
|
+
git.worktree_post_create = g.worktree_post_create.trim();
|
|
1214
|
+
} else {
|
|
1215
|
+
errors.push("git.worktree_post_create must be a non-empty string (path to script)");
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1170
1218
|
// Deprecated: merge_to_main is ignored (branchless architecture).
|
|
1171
1219
|
if (g.merge_to_main !== undefined) {
|
|
1172
1220
|
warnings.push("git.merge_to_main is deprecated — milestone-level merge is now always used. Remove this setting.");
|
|
@@ -7,15 +7,17 @@
|
|
|
7
7
|
* Templates live at prompts/ relative to this module's directory.
|
|
8
8
|
* They use {{variableName}} syntax for substitution.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* session from being invalidated when another `gsd`
|
|
12
|
-
* ~/.gsd/agent/ with newer templates via initResources().
|
|
13
|
-
* the in-memory extension code (which knows variable
|
|
14
|
-
* newer template from disk (which expects variable set B),
|
|
15
|
-
* "template declares {{X}} but no value was provided" crash
|
|
10
|
+
* All templates are eagerly loaded into cache at module init via warmCache().
|
|
11
|
+
* This prevents a running session from being invalidated when another `gsd`
|
|
12
|
+
* launch overwrites ~/.gsd/agent/ with newer templates via initResources().
|
|
13
|
+
* Without eager caching, the in-memory extension code (which knows variable
|
|
14
|
+
* set A) can read a newer template from disk (which expects variable set B),
|
|
15
|
+
* causing a "template declares {{X}} but no value was provided" crash
|
|
16
|
+
* mid-session — especially for late-loading templates like complete-milestone
|
|
17
|
+
* that aren't read until the end of a long auto-mode run.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
|
-
import { readFileSync } from "node:fs";
|
|
20
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
19
21
|
import { join, dirname } from "node:path";
|
|
20
22
|
import { fileURLToPath } from "node:url";
|
|
21
23
|
|
|
@@ -23,10 +25,44 @@ const __extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
|
23
25
|
const promptsDir = join(__extensionDir, "prompts");
|
|
24
26
|
const templatesDir = join(__extensionDir, "templates");
|
|
25
27
|
|
|
26
|
-
// Cache templates
|
|
27
|
-
// that were on disk
|
|
28
|
+
// Cache all templates eagerly at module load — a running session uses the
|
|
29
|
+
// template versions that were on disk at startup, immune to later overwrites.
|
|
28
30
|
const templateCache = new Map<string, string>();
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Eagerly read all .md files from prompts/ and templates/ into cache.
|
|
34
|
+
* Called once at module init so that every template is snapshot before
|
|
35
|
+
* a concurrent initResources() can overwrite files on disk.
|
|
36
|
+
*/
|
|
37
|
+
function warmCache(): void {
|
|
38
|
+
try {
|
|
39
|
+
for (const file of readdirSync(promptsDir)) {
|
|
40
|
+
if (!file.endsWith(".md")) continue;
|
|
41
|
+
const name = file.slice(0, -3);
|
|
42
|
+
if (!templateCache.has(name)) {
|
|
43
|
+
templateCache.set(name, readFileSync(join(promptsDir, file), "utf-8"));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// prompts/ may not exist in test environments — lazy loading still works
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
for (const file of readdirSync(templatesDir)) {
|
|
52
|
+
if (!file.endsWith(".md")) continue;
|
|
53
|
+
const cacheKey = `tpl:${file.slice(0, -3)}`;
|
|
54
|
+
if (!templateCache.has(cacheKey)) {
|
|
55
|
+
templateCache.set(cacheKey, readFileSync(join(templatesDir, file), "utf-8"));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// templates/ may not exist in test environments — lazy loading still works
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Snapshot all templates at module load time
|
|
64
|
+
warmCache();
|
|
65
|
+
|
|
30
66
|
/**
|
|
31
67
|
* Load a prompt template and substitute variables.
|
|
32
68
|
*
|
|
@@ -43,7 +43,7 @@ Then:
|
|
|
43
43
|
9. If the task plan includes an Observability Impact section, verify those signals directly. Skip this step if the task plan omits the section.
|
|
44
44
|
10. **If execution is running long or verification fails:**
|
|
45
45
|
|
|
46
|
-
**Context budget:** If you've used most of your context and haven't finished all steps, stop implementing and prioritize writing the task summary with clear notes on what's done and what remains. A partial summary that enables clean resumption is more valuable than one more half-finished step with no documentation. Never sacrifice summary quality for one more implementation step.
|
|
46
|
+
**Context budget:** You have approximately **{{verificationBudget}}** reserved for verification context. If you've used most of your context and haven't finished all steps, stop implementing and prioritize writing the task summary with clear notes on what's done and what remains. A partial summary that enables clean resumption is more valuable than one more half-finished step with no documentation. Never sacrifice summary quality for one more implementation step.
|
|
47
47
|
|
|
48
48
|
**Debugging discipline:** If a verification check fails or implementation hits unexpected behavior:
|
|
49
49
|
- Form a hypothesis first. State what you think is wrong and why, then test that specific theory. Don't shotgun-fix.
|
|
@@ -53,9 +53,9 @@ Then:
|
|
|
53
53
|
- Know when to stop. If you've tried 3+ fixes without progress, your mental model is probably wrong. Stop. List what you know for certain. List what you've ruled out. Form fresh hypotheses from there.
|
|
54
54
|
- Don't fix symptoms. Understand *why* something fails before changing code. A test that passes after a change you don't understand is luck, not a fix.
|
|
55
55
|
11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
|
|
56
|
-
12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (
|
|
56
|
+
12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
|
|
57
57
|
13. If you discover a non-obvious rule, recurring gotcha, or useful pattern during execution, append it to `.gsd/KNOWLEDGE.md`. Only add entries that would save future agents from repeating your investigation. Don't add obvious things.
|
|
58
|
-
14.
|
|
58
|
+
14. Read the template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md`
|
|
59
59
|
15. Write `{{taskSummaryPath}}`
|
|
60
60
|
16. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
|
|
61
61
|
17. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
@@ -65,6 +65,4 @@ All work stays in your working directory: `{{workingDirectory}}`.
|
|
|
65
65
|
|
|
66
66
|
**You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{taskSummaryPath}}` before finishing.**
|
|
67
67
|
|
|
68
|
-
{{inlinedTemplates}}
|
|
69
|
-
|
|
70
68
|
When done, say: "Task {{taskId}} complete."
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
## Skill Heal Analysis
|
|
2
|
+
|
|
3
|
+
Analyze the just-completed unit ({{unitId}}) for skill drift.
|
|
4
|
+
|
|
5
|
+
### Steps
|
|
6
|
+
|
|
7
|
+
1. **Identify loaded skill**: Check which SKILL.md file was read during this unit by examining recent tool calls. If no skill was explicitly loaded (no `read` call to a SKILL.md path), write "No skill loaded — skipping heal analysis" to {{healArtifact}} and stop.
|
|
8
|
+
|
|
9
|
+
2. **Read the skill**: Load the SKILL.md that was used during this unit.
|
|
10
|
+
|
|
11
|
+
3. **Compare execution to skill guidance**: Review what the agent actually did vs what the skill recommended. Look for:
|
|
12
|
+
- API patterns the skill recommended that the agent did differently
|
|
13
|
+
- Error handling approaches the skill specified but the agent bypassed
|
|
14
|
+
- Conventions the skill documented that the agent ignored
|
|
15
|
+
- Outdated instructions in the skill that caused errors, retries, or workarounds
|
|
16
|
+
- Commands or tools the skill referenced that no longer exist or have changed
|
|
17
|
+
|
|
18
|
+
4. **Assess drift severity**:
|
|
19
|
+
- **None**: Agent followed skill correctly → write "No drift detected" to {{healArtifact}} and stop
|
|
20
|
+
- **Minor**: Agent found a better approach but skill isn't wrong → append a note to `.gsd/KNOWLEDGE.md` and stop
|
|
21
|
+
- **Significant**: Skill has outdated or incorrect guidance → continue to step 5
|
|
22
|
+
|
|
23
|
+
5. **If significant drift found**, append a heal suggestion to `.gsd/skill-review-queue.md`:
|
|
24
|
+
|
|
25
|
+
```markdown
|
|
26
|
+
### {{skillName}} (flagged {{date}})
|
|
27
|
+
- **Unit:** {{unitId}}
|
|
28
|
+
- **Issue:** {1-2 sentence description of what was wrong}
|
|
29
|
+
- **Root cause:** {outdated API / incorrect pattern / missing context / etc.}
|
|
30
|
+
- **Discovery method:** {how the agent discovered the skill was wrong — error message, trial and error, docs lookup, etc.}
|
|
31
|
+
- **Proposed fix:**
|
|
32
|
+
- File: {relative path to the file in the skill directory}
|
|
33
|
+
- Section: {section heading or line range}
|
|
34
|
+
- Current: {quote the incorrect/outdated text}
|
|
35
|
+
- Suggested: {the corrected text}
|
|
36
|
+
- **Action:** [ ] Reviewed [ ] Updated [ ] Dismissed
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then write a brief summary of the finding to {{healArtifact}}.
|
|
40
|
+
|
|
41
|
+
**Critical rules:**
|
|
42
|
+
- Do NOT modify any skill files directly. Only write to the review queue.
|
|
43
|
+
- The SkillsBench research (Feb 2026) shows curated skills beat auto-generated ones by +16.2pp. Human review is what makes this valuable.
|
|
44
|
+
- Keep the analysis focused — don't flag stylistic preferences, only genuine errors or outdated content.
|
|
45
|
+
- If multiple issues found, write one entry per issue.
|
|
@@ -26,9 +26,13 @@ Narrate your decomposition reasoning — why you're grouping work this way, what
|
|
|
26
26
|
|
|
27
27
|
**Right-size the plan.** If the slice is simple enough to be 1 task, plan 1 task. Don't split into multiple tasks just because you can identify sub-steps. Don't fill in sections with "None" when the section doesn't apply — omit them entirely. The plan's job is to guide execution, not to fill a template.
|
|
28
28
|
|
|
29
|
+
{{executorContextConstraints}}
|
|
30
|
+
|
|
29
31
|
Then:
|
|
30
32
|
0. If `REQUIREMENTS.md` was preloaded above, identify which Active requirements the roadmap says this slice owns or supports. These are the requirements this plan must deliver — every owned requirement needs at least one task that directly advances it, and verification must prove the requirement is met.
|
|
31
|
-
1.
|
|
33
|
+
1. Read the templates:
|
|
34
|
+
- `~/.gsd/agent/extensions/gsd/templates/plan.md`
|
|
35
|
+
- `~/.gsd/agent/extensions/gsd/templates/task-plan.md`
|
|
32
36
|
2. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow during planning, without overriding required plan formatting
|
|
33
37
|
3. Define slice-level verification — the objective stopping condition for this slice:
|
|
34
38
|
- For non-trivial slices: plan actual test files with real assertions. Name the files.
|