gsd-pi 2.19.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-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- 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 +41 -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 +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- 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/system.md +2 -1
- 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/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/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/model-isolation.test.ts +99 -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 +262 -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/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/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 +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- 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/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 +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- 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 +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- 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-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- 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 +41 -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 +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- 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/system.md +2 -1
- 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/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/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/model-isolation.test.ts +99 -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 +262 -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/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/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 +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- 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/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 +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- 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 +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- 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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// GSD Extension — Hook Engine (Post-Unit, Pre-Dispatch, State Persistence)
|
|
2
2
|
// Manages hook queue, cycle tracking, artifact verification, pre-dispatch
|
|
3
3
|
// interception, and durable hook state for user-configured extensibility.
|
|
4
|
-
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
5
4
|
|
|
6
5
|
import type {
|
|
7
6
|
PostUnitHookConfig,
|
|
@@ -412,6 +411,76 @@ export function getHookStatus(): HookStatusEntry[] {
|
|
|
412
411
|
return entries;
|
|
413
412
|
}
|
|
414
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Manually trigger a specific hook for a unit.
|
|
416
|
+
* This bypasses the normal flow and forces the hook to run even if its artifact exists.
|
|
417
|
+
*
|
|
418
|
+
* @param hookName - The name of the hook to trigger (e.g., "code-review")
|
|
419
|
+
* @param unitType - The type of unit that triggered the hook (e.g., "execute-task")
|
|
420
|
+
* @param unitId - The unit ID (e.g., "M001/S01/T01")
|
|
421
|
+
* @param basePath - The project base path
|
|
422
|
+
* @returns The hook dispatch result or null if hook not found
|
|
423
|
+
*/
|
|
424
|
+
export function triggerHookManually(
|
|
425
|
+
hookName: string,
|
|
426
|
+
unitType: string,
|
|
427
|
+
unitId: string,
|
|
428
|
+
basePath: string,
|
|
429
|
+
): HookDispatchResult | null {
|
|
430
|
+
// Find the hook configuration
|
|
431
|
+
const hook = resolvePostUnitHooks().find(h => h.name === hookName);
|
|
432
|
+
if (!hook) {
|
|
433
|
+
console.error(`[triggerHookManually] Hook "${hookName}" not found in post_unit_hooks`);
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!hook.prompt || typeof hook.prompt !== 'string' || hook.prompt.trim().length === 0) {
|
|
438
|
+
console.error(`[triggerHookManually] Hook "${hookName}" has empty prompt`);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Reset any active hook state to allow manual triggering
|
|
443
|
+
activeHook = {
|
|
444
|
+
hookName: hook.name,
|
|
445
|
+
triggerUnitType: unitType,
|
|
446
|
+
triggerUnitId: unitId,
|
|
447
|
+
cycle: 1,
|
|
448
|
+
pendingRetry: false,
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// Build the hook queue with just this hook
|
|
452
|
+
hookQueue = [{
|
|
453
|
+
config: hook,
|
|
454
|
+
triggerUnitType: unitType,
|
|
455
|
+
triggerUnitId: unitId,
|
|
456
|
+
}];
|
|
457
|
+
|
|
458
|
+
// Set the cycle count for this specific hook+trigger
|
|
459
|
+
const cycleKey = `${hook.name}/${unitType}/${unitId}`;
|
|
460
|
+
const currentCycle = (cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
461
|
+
cycleCounts.set(cycleKey, currentCycle);
|
|
462
|
+
|
|
463
|
+
// Update active hook with the cycle count
|
|
464
|
+
activeHook.cycle = currentCycle;
|
|
465
|
+
|
|
466
|
+
// Build the prompt with variable substitution
|
|
467
|
+
const [mid, sid, tid] = unitId.split("/");
|
|
468
|
+
const prompt = hook.prompt
|
|
469
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
470
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
471
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
472
|
+
|
|
473
|
+
console.log(`[triggerHookManually] Built prompt for ${hookName}, length: ${prompt.length}`);
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
hookName: hook.name,
|
|
477
|
+
prompt,
|
|
478
|
+
model: hook.model,
|
|
479
|
+
unitType: `hook/${hook.name}`,
|
|
480
|
+
unitId,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
415
484
|
/**
|
|
416
485
|
* Format hook status for terminal display.
|
|
417
486
|
*/
|
|
@@ -2,6 +2,7 @@ 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";
|
|
7
8
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
@@ -17,9 +18,40 @@ const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.m
|
|
|
17
18
|
const PROJECT_PREFERENCES_PATH_UPPERCASE = join(process.cwd(), ".gsd", "PREFERENCES.md");
|
|
18
19
|
const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
19
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
|
+
|
|
20
51
|
/** All recognized top-level keys in GSDPreferences. Used to detect typos / stale config. */
|
|
21
52
|
const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
22
53
|
"version",
|
|
54
|
+
"mode",
|
|
23
55
|
"always_use_skills",
|
|
24
56
|
"prefer_skills",
|
|
25
57
|
"avoid_skills",
|
|
@@ -27,6 +59,7 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
27
59
|
"custom_instructions",
|
|
28
60
|
"models",
|
|
29
61
|
"skill_discovery",
|
|
62
|
+
"skill_staleness_days",
|
|
30
63
|
"auto_supervisor",
|
|
31
64
|
"uat_dispatch",
|
|
32
65
|
"unique_milestone_ids",
|
|
@@ -106,7 +139,7 @@ export interface AutoSupervisorConfig {
|
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
export interface RemoteQuestionsConfig {
|
|
109
|
-
channel: "slack" | "discord";
|
|
142
|
+
channel: "slack" | "discord" | "telegram";
|
|
110
143
|
channel_id: string | number;
|
|
111
144
|
timeout_minutes?: number; // clamped to 1-30
|
|
112
145
|
poll_interval_seconds?: number; // clamped to 2-30
|
|
@@ -114,6 +147,7 @@ export interface RemoteQuestionsConfig {
|
|
|
114
147
|
|
|
115
148
|
export interface GSDPreferences {
|
|
116
149
|
version?: number;
|
|
150
|
+
mode?: WorkflowMode;
|
|
117
151
|
always_use_skills?: string[];
|
|
118
152
|
prefer_skills?: string[];
|
|
119
153
|
avoid_skills?: string[];
|
|
@@ -121,6 +155,7 @@ export interface GSDPreferences {
|
|
|
121
155
|
custom_instructions?: string[];
|
|
122
156
|
models?: GSDModelConfig | GSDModelConfigV2;
|
|
123
157
|
skill_discovery?: SkillDiscoveryMode;
|
|
158
|
+
skill_staleness_days?: number; // Skills unused for N days get deprioritized (#599). 0 = disabled. Default: 60.
|
|
124
159
|
auto_supervisor?: AutoSupervisorConfig;
|
|
125
160
|
uat_dispatch?: boolean;
|
|
126
161
|
unique_milestone_ids?: boolean;
|
|
@@ -169,25 +204,49 @@ export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
|
|
|
169
204
|
?? loadPreferencesFile(PROJECT_PREFERENCES_PATH_UPPERCASE, "project");
|
|
170
205
|
}
|
|
171
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
|
+
|
|
172
217
|
export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
|
173
218
|
const globalPreferences = loadGlobalGSDPreferences();
|
|
174
219
|
const projectPreferences = loadProjectGSDPreferences();
|
|
175
220
|
|
|
176
221
|
if (!globalPreferences && !projectPreferences) return null;
|
|
177
|
-
if (!globalPreferences) return projectPreferences;
|
|
178
|
-
if (!projectPreferences) return globalPreferences;
|
|
179
222
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
}
|
|
184
240
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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;
|
|
191
250
|
}
|
|
192
251
|
|
|
193
252
|
// ─── Skill Reference Resolution ───────────────────────────────────────────────
|
|
@@ -425,148 +484,27 @@ function loadPreferencesFile(path: string, scope: "global" | "project"): LoadedG
|
|
|
425
484
|
|
|
426
485
|
/** @internal Exported for testing only */
|
|
427
486
|
export function parsePreferencesMarkdown(content: string): GSDPreferences | null {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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, ''));
|
|
431
495
|
}
|
|
432
496
|
|
|
433
497
|
function parseFrontmatterBlock(frontmatter: string): GSDPreferences {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
for (let i = 0; i < lines.length; i++) {
|
|
439
|
-
const line = lines[i];
|
|
440
|
-
if (!line.trim()) continue;
|
|
441
|
-
|
|
442
|
-
const indent = line.match(/^\s*/)?.[0].length ?? 0;
|
|
443
|
-
const trimmed = line.trim();
|
|
444
|
-
|
|
445
|
-
// Skip comment lines (standalone YAML comments)
|
|
446
|
-
if (trimmed.startsWith("#")) continue;
|
|
447
|
-
|
|
448
|
-
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
449
|
-
stack.pop();
|
|
498
|
+
try {
|
|
499
|
+
const parsed = parseYaml(frontmatter);
|
|
500
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
501
|
+
return {} as GSDPreferences;
|
|
450
502
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const [, key, remainder] = keyMatch;
|
|
457
|
-
// Strip inline comments from the value portion
|
|
458
|
-
const valuePart = remainder.replace(/\s+#.*$/, "").trim();
|
|
459
|
-
|
|
460
|
-
if (valuePart === "") {
|
|
461
|
-
const nextLine = lines[i + 1] ?? "";
|
|
462
|
-
const nextTrimmed = nextLine.trim();
|
|
463
|
-
if (nextTrimmed.startsWith("- ")) {
|
|
464
|
-
const items: unknown[] = [];
|
|
465
|
-
let j = i + 1;
|
|
466
|
-
while (j < lines.length) {
|
|
467
|
-
const candidate = lines[j];
|
|
468
|
-
const candidateIndent = candidate.match(/^\s*/)?.[0].length ?? 0;
|
|
469
|
-
const candidateTrimmed = candidate.trim();
|
|
470
|
-
if (!candidateTrimmed) {
|
|
471
|
-
j++;
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
if (candidateIndent <= indent || !candidateTrimmed.startsWith("- ")) break;
|
|
475
|
-
|
|
476
|
-
const itemText = candidateTrimmed.slice(2).trim();
|
|
477
|
-
const nextCandidate = lines[j + 1] ?? "";
|
|
478
|
-
const nextCandidateIndent = nextCandidate.match(/^\s*/)?.[0].length ?? 0;
|
|
479
|
-
const nextCandidateTrimmed = nextCandidate.trim();
|
|
480
|
-
|
|
481
|
-
// Treat an array item as a structured object only when:
|
|
482
|
-
// a) It looks like a YAML key-value pair (key starts with [A-Za-z0-9_]+:), OR
|
|
483
|
-
// b) The next line is indented deeper (nested block under this item).
|
|
484
|
-
// Bare colons (e.g. "qwen/qwen3-coder:free") are NOT key-value pairs.
|
|
485
|
-
const looksLikeKeyValue = /^[A-Za-z0-9_]+:/.test(itemText);
|
|
486
|
-
if (looksLikeKeyValue || (nextCandidateTrimmed && nextCandidateIndent > candidateIndent)) {
|
|
487
|
-
const obj: Record<string, unknown> = {};
|
|
488
|
-
const firstMatch = itemText.match(/^([A-Za-z0-9_]+):(.*)$/);
|
|
489
|
-
if (firstMatch) {
|
|
490
|
-
obj[firstMatch[1]] = parseScalar(firstMatch[2].trim());
|
|
491
|
-
}
|
|
492
|
-
j++;
|
|
493
|
-
while (j < lines.length) {
|
|
494
|
-
const nested = lines[j];
|
|
495
|
-
const nestedIndent = nested.match(/^\s*/)?.[0].length ?? 0;
|
|
496
|
-
const nestedTrimmed = nested.trim();
|
|
497
|
-
if (!nestedTrimmed) {
|
|
498
|
-
j++;
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
if (nestedIndent <= candidateIndent) break;
|
|
502
|
-
const nestedMatch = nestedTrimmed.match(/^([A-Za-z0-9_]+):(.*)$/);
|
|
503
|
-
if (nestedMatch) {
|
|
504
|
-
const nestedValue = nestedMatch[2].trim();
|
|
505
|
-
if (nestedValue === "") {
|
|
506
|
-
const nestedItems: string[] = [];
|
|
507
|
-
j++;
|
|
508
|
-
while (j < lines.length) {
|
|
509
|
-
const nestedArrayLine = lines[j];
|
|
510
|
-
const nestedArrayIndent = nestedArrayLine.match(/^\s*/)?.[0].length ?? 0;
|
|
511
|
-
const nestedArrayTrimmed = nestedArrayLine.trim();
|
|
512
|
-
if (!nestedArrayTrimmed) {
|
|
513
|
-
j++;
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
if (nestedArrayIndent <= nestedIndent || !nestedArrayTrimmed.startsWith("- ")) break;
|
|
517
|
-
nestedItems.push(String(parseScalar(nestedArrayTrimmed.slice(2).trim())));
|
|
518
|
-
j++;
|
|
519
|
-
}
|
|
520
|
-
obj[nestedMatch[1]] = nestedItems;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
obj[nestedMatch[1]] = parseScalar(nestedValue);
|
|
524
|
-
}
|
|
525
|
-
j++;
|
|
526
|
-
}
|
|
527
|
-
items.push(obj);
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
items.push(parseScalar(itemText));
|
|
532
|
-
j++;
|
|
533
|
-
}
|
|
534
|
-
current[key] = items;
|
|
535
|
-
i = j - 1;
|
|
536
|
-
} else {
|
|
537
|
-
const obj: Record<string, unknown> = {};
|
|
538
|
-
current[key] = obj;
|
|
539
|
-
stack.push({ indent, value: obj });
|
|
540
|
-
}
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
current[key] = parseScalar(valuePart);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return root as GSDPreferences;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function parseScalar(value: string): unknown {
|
|
551
|
-
// Strip inline YAML comments: " # comment" (# preceded by whitespace).
|
|
552
|
-
// Quoted strings are returned as-is (the comment is inside quotes).
|
|
553
|
-
const quoteMatch = value.match(/^(['"])(.*)(\1)$/);
|
|
554
|
-
if (quoteMatch) return quoteMatch[2];
|
|
555
|
-
|
|
556
|
-
const stripped = value.replace(/\s+#.*$/, "");
|
|
557
|
-
if (stripped === "true") return true;
|
|
558
|
-
if (stripped === "false") return false;
|
|
559
|
-
// Recognize empty array/object literals (with or without surrounding quotes)
|
|
560
|
-
const unquoted = stripped.replace(/^['\"]|['\"]$/g, "");
|
|
561
|
-
if (unquoted === "[]") return [];
|
|
562
|
-
if (unquoted === "{}") return {};
|
|
563
|
-
if (/^-?\d+$/.test(stripped)) {
|
|
564
|
-
const n = Number(stripped);
|
|
565
|
-
// Keep large integers (e.g. Discord channel IDs) as strings to avoid precision loss
|
|
566
|
-
if (Number.isSafeInteger(n)) return n;
|
|
567
|
-
return stripped;
|
|
503
|
+
return parsed as GSDPreferences;
|
|
504
|
+
} catch (e) {
|
|
505
|
+
console.error("[parseFrontmatterBlock] YAML parse error:", e);
|
|
506
|
+
return {} as GSDPreferences;
|
|
568
507
|
}
|
|
569
|
-
return unquoted;
|
|
570
508
|
}
|
|
571
509
|
|
|
572
510
|
/**
|
|
@@ -578,6 +516,15 @@ export function resolveSkillDiscoveryMode(): SkillDiscoveryMode {
|
|
|
578
516
|
return prefs?.preferences.skill_discovery ?? "suggest";
|
|
579
517
|
}
|
|
580
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
|
+
|
|
581
528
|
/**
|
|
582
529
|
* Resolve which model ID to use for a given auto-mode unit type.
|
|
583
530
|
* Returns undefined if no model preference is set for this unit type.
|
|
@@ -776,6 +723,7 @@ export function resolveInlineLevel(): InlineLevel {
|
|
|
776
723
|
function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPreferences {
|
|
777
724
|
return {
|
|
778
725
|
version: override.version ?? base.version,
|
|
726
|
+
mode: override.mode ?? base.mode,
|
|
779
727
|
always_use_skills: mergeStringLists(base.always_use_skills, override.always_use_skills),
|
|
780
728
|
prefer_skills: mergeStringLists(base.prefer_skills, override.prefer_skills),
|
|
781
729
|
avoid_skills: mergeStringLists(base.avoid_skills, override.avoid_skills),
|
|
@@ -783,6 +731,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
783
731
|
custom_instructions: mergeStringLists(base.custom_instructions, override.custom_instructions),
|
|
784
732
|
models: { ...(base.models ?? {}), ...(override.models ?? {}) },
|
|
785
733
|
skill_discovery: override.skill_discovery ?? base.skill_discovery,
|
|
734
|
+
skill_staleness_days: override.skill_staleness_days ?? base.skill_staleness_days,
|
|
786
735
|
auto_supervisor: { ...(base.auto_supervisor ?? {}), ...(override.auto_supervisor ?? {}) },
|
|
787
736
|
uat_dispatch: override.uat_dispatch ?? base.uat_dispatch,
|
|
788
737
|
unique_milestone_ids: override.unique_milestone_ids ?? base.unique_milestone_ids,
|
|
@@ -834,6 +783,16 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
834
783
|
}
|
|
835
784
|
}
|
|
836
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
|
+
|
|
837
796
|
const validDiscoveryModes = new Set(["auto", "suggest", "off"]);
|
|
838
797
|
if (preferences.skill_discovery) {
|
|
839
798
|
if (validDiscoveryModes.has(preferences.skill_discovery)) {
|
|
@@ -843,6 +802,15 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
843
802
|
}
|
|
844
803
|
}
|
|
845
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
|
+
|
|
846
814
|
validated.always_use_skills = normalizeStringList(preferences.always_use_skills);
|
|
847
815
|
validated.prefer_skills = normalizeStringList(preferences.prefer_skills);
|
|
848
816
|
validated.avoid_skills = normalizeStringList(preferences.avoid_skills);
|
|
@@ -1240,6 +1208,13 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
1240
1208
|
if (typeof g.commit_docs === "boolean") git.commit_docs = g.commit_docs;
|
|
1241
1209
|
else errors.push("git.commit_docs must be a boolean");
|
|
1242
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
|
+
}
|
|
1243
1218
|
// Deprecated: merge_to_main is ignored (branchless architecture).
|
|
1244
1219
|
if (g.merge_to_main !== undefined) {
|
|
1245
1220
|
warnings.push("git.merge_to_main is deprecated — milestone-level merge is now always used. Remove this setting.");
|
|
@@ -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.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
You are executing a GSD quick task — a lightweight, focused unit of work outside the milestone/slice ceremony.
|
|
2
|
+
|
|
3
|
+
## QUICK TASK: {{description}}
|
|
4
|
+
|
|
5
|
+
**Task directory:** `{{taskDir}}`
|
|
6
|
+
**Branch:** `{{branch}}`
|
|
7
|
+
|
|
8
|
+
## Instructions
|
|
9
|
+
|
|
10
|
+
1. Read the task description above carefully. This is a focused, self-contained task.
|
|
11
|
+
2. If a `GSD Skill Preferences` block is present in system context, follow it.
|
|
12
|
+
3. Read relevant code before modifying. Understand existing patterns.
|
|
13
|
+
4. Execute the task completely:
|
|
14
|
+
- Build the real thing, not stubs or placeholders.
|
|
15
|
+
- Write or update tests where appropriate.
|
|
16
|
+
- Handle error cases and edge cases.
|
|
17
|
+
5. Verify your work:
|
|
18
|
+
- Run tests if applicable.
|
|
19
|
+
- Verify both happy path and failure modes for non-trivial changes.
|
|
20
|
+
6. Commit your changes atomically:
|
|
21
|
+
- Use conventional commit messages (feat:, fix:, refactor:, etc.)
|
|
22
|
+
- Stage only relevant files — never commit secrets or runtime files.
|
|
23
|
+
- Commit logical units separately if the task involves distinct changes.
|
|
24
|
+
7. Write a brief summary to `{{summaryPath}}`:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
# Quick Task: {{description}}
|
|
28
|
+
|
|
29
|
+
**Date:** {{date}}
|
|
30
|
+
**Branch:** {{branch}}
|
|
31
|
+
|
|
32
|
+
## What Changed
|
|
33
|
+
- <concise list of changes>
|
|
34
|
+
|
|
35
|
+
## Files Modified
|
|
36
|
+
- <list of files>
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
- <what was tested/verified>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
8. Update `.gsd/STATE.md` — add or update the "Quick Tasks Completed" table:
|
|
43
|
+
- If the section doesn't exist, create it after "### Blockers/Concerns"
|
|
44
|
+
- Table format: `| # | Description | Date | Commit | Directory |`
|
|
45
|
+
- Add a row: `| {{taskNum}} | {{description}} | {{date}} | <commit-hash> | [{{taskNum}}-{{slug}}](./quick/{{taskNum}}-{{slug}}/) |`
|
|
46
|
+
- Update the "Last activity" line
|
|
47
|
+
|
|
48
|
+
When done, say: "Quick task {{taskNum}} complete."
|
|
@@ -128,6 +128,7 @@ Templates showing the expected format for each artifact type are in:
|
|
|
128
128
|
- `/gsd stop` - stop auto-mode
|
|
129
129
|
- `/gsd status` - progress dashboard overlay
|
|
130
130
|
- `/gsd queue` - queue future milestones (safe while auto-mode is running)
|
|
131
|
+
- `/gsd quick <task>` - quick task with GSD guarantees (atomic commits, state tracking) but no milestone ceremony
|
|
131
132
|
- `Ctrl+Alt+G` - toggle dashboard overlay
|
|
132
133
|
- `Ctrl+Alt+B` - show shell processes
|
|
133
134
|
|
|
@@ -139,7 +140,7 @@ Templates showing the expected format for each artifact type are in:
|
|
|
139
140
|
|
|
140
141
|
**File editing:** Always `read` a file before using `edit`. The `edit` tool requires exact text match — you need the real content, not a guess. Use `write` only for new files or complete rewrites.
|
|
141
142
|
|
|
142
|
-
**Code navigation:** Use `lsp` for
|
|
143
|
+
**Code navigation:** Use `lsp` for definition, type_definition, implementation, references, incoming_calls, outgoing_calls, hover, signature, symbols, rename, code_actions, format, and diagnostics. Falls back gracefully if no server is available. Never `grep` for a symbol definition when `lsp` can resolve it semantically. Never shell out to prettier/rustfmt/gofmt when `lsp format` is available. After editing code, use `lsp diagnostics` to verify no type errors were introduced.
|
|
143
144
|
|
|
144
145
|
**Codebase exploration:** Use `subagent` with `scout` for broad unfamiliar subsystem mapping. Use `rg` for text search across files. Use `lsp` for structural navigation. Never read files one-by-one to "explore" — search first, then read what's relevant.
|
|
145
146
|
|