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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-post-create-hook.test.ts — Tests for #597 worktree post-create hook.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that runWorktreePostCreateHook correctly executes user scripts
|
|
5
|
+
* with SOURCE_DIR and WORKTREE_DIR environment variables.
|
|
6
|
+
*
|
|
7
|
+
* Uses Node.js scripts instead of bash for Windows compatibility.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, rmSync, existsSync, writeFileSync, readFileSync, chmodSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
|
|
16
|
+
import { runWorktreePostCreateHook } from "../auto-worktree.ts";
|
|
17
|
+
|
|
18
|
+
function makeTmpDir(): string {
|
|
19
|
+
return mkdtempSync(join(tmpdir(), "gsd-wt-hook-test-"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isWin = process.platform === "win32";
|
|
23
|
+
|
|
24
|
+
/** Return the platform-appropriate hook file path (adds .bat on Windows). */
|
|
25
|
+
function hookPath(base: string): string {
|
|
26
|
+
return isWin ? `${base}.bat` : base;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Create a cross-platform Node.js hook script. */
|
|
30
|
+
function writeNodeHookScript(filePath: string, code: string): void {
|
|
31
|
+
if (isWin) {
|
|
32
|
+
// Write the JS code to a companion .js file and have the .bat invoke it.
|
|
33
|
+
// node -e with multi-line code breaks on Windows because cmd.exe splits on newlines.
|
|
34
|
+
const jsPath = filePath.replace(/\.bat$/, ".js");
|
|
35
|
+
writeFileSync(jsPath, code);
|
|
36
|
+
writeFileSync(filePath, `@echo off\nnode "%~dp0${jsPath.split("\\").pop()}" %*\n`);
|
|
37
|
+
} else {
|
|
38
|
+
writeFileSync(filePath, `#!/usr/bin/env node\n${code}\n`);
|
|
39
|
+
chmodSync(filePath, 0o755);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── runWorktreePostCreateHook ──────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
test("returns null when no hook path is provided", () => {
|
|
46
|
+
const src = makeTmpDir();
|
|
47
|
+
const wt = makeTmpDir();
|
|
48
|
+
try {
|
|
49
|
+
const result = runWorktreePostCreateHook(src, wt, undefined);
|
|
50
|
+
assert.equal(result, null);
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(src, { recursive: true, force: true });
|
|
53
|
+
rmSync(wt, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("returns error when hook script does not exist", () => {
|
|
58
|
+
const src = makeTmpDir();
|
|
59
|
+
const wt = makeTmpDir();
|
|
60
|
+
try {
|
|
61
|
+
const result = runWorktreePostCreateHook(src, wt, ".gsd/hooks/nonexistent");
|
|
62
|
+
assert.ok(result !== null, "should return error string");
|
|
63
|
+
assert.ok(result!.includes("not found"), "error should mention 'not found'");
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(src, { recursive: true, force: true });
|
|
66
|
+
rmSync(wt, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("executes hook script with correct SOURCE_DIR and WORKTREE_DIR env vars", () => {
|
|
71
|
+
const src = makeTmpDir();
|
|
72
|
+
const wt = makeTmpDir();
|
|
73
|
+
try {
|
|
74
|
+
const hooksDir = join(src, ".gsd", "hooks");
|
|
75
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
76
|
+
const hookFile = hookPath(join(hooksDir, "post-create"));
|
|
77
|
+
const code = [
|
|
78
|
+
`const fs = require("fs");`,
|
|
79
|
+
`const path = require("path");`,
|
|
80
|
+
`const out = path.join(process.env.WORKTREE_DIR, "hook-output.txt");`,
|
|
81
|
+
`fs.writeFileSync(out, "SOURCE=" + process.env.SOURCE_DIR + "\\n" + "WORKTREE=" + process.env.WORKTREE_DIR + "\\n");`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
writeNodeHookScript(hookFile, code);
|
|
84
|
+
|
|
85
|
+
const result = runWorktreePostCreateHook(src, wt, hookPath(".gsd/hooks/post-create"));
|
|
86
|
+
assert.equal(result, null, "should succeed");
|
|
87
|
+
|
|
88
|
+
const outputFile = join(wt, "hook-output.txt");
|
|
89
|
+
assert.ok(existsSync(outputFile), "hook should have created output file");
|
|
90
|
+
|
|
91
|
+
const output = readFileSync(outputFile, "utf-8");
|
|
92
|
+
assert.ok(output.includes(`SOURCE=${src}`), "SOURCE_DIR should match source dir");
|
|
93
|
+
assert.ok(output.includes(`WORKTREE=${wt}`), "WORKTREE_DIR should match worktree dir");
|
|
94
|
+
} finally {
|
|
95
|
+
rmSync(src, { recursive: true, force: true });
|
|
96
|
+
rmSync(wt, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("returns error message when hook script fails", () => {
|
|
101
|
+
const src = makeTmpDir();
|
|
102
|
+
const wt = makeTmpDir();
|
|
103
|
+
try {
|
|
104
|
+
const hooksDir = join(src, ".gsd", "hooks");
|
|
105
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
106
|
+
const hookFile = hookPath(join(hooksDir, "failing-hook"));
|
|
107
|
+
writeNodeHookScript(hookFile, `process.exit(1);`);
|
|
108
|
+
|
|
109
|
+
const result = runWorktreePostCreateHook(src, wt, hookPath(".gsd/hooks/failing-hook"));
|
|
110
|
+
assert.ok(result !== null, "should return error string");
|
|
111
|
+
assert.ok(result!.includes("hook failed"), "error should mention 'hook failed'");
|
|
112
|
+
} finally {
|
|
113
|
+
rmSync(src, { recursive: true, force: true });
|
|
114
|
+
rmSync(wt, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("supports absolute hook paths", () => {
|
|
119
|
+
const src = makeTmpDir();
|
|
120
|
+
const wt = makeTmpDir();
|
|
121
|
+
try {
|
|
122
|
+
const hookFile = hookPath(join(src, "absolute-hook"));
|
|
123
|
+
const code = [
|
|
124
|
+
`const fs = require("fs");`,
|
|
125
|
+
`const path = require("path");`,
|
|
126
|
+
`fs.writeFileSync(path.join(process.env.WORKTREE_DIR, "absolute-hook-ran"), "");`,
|
|
127
|
+
].join("\n");
|
|
128
|
+
writeNodeHookScript(hookFile, code);
|
|
129
|
+
|
|
130
|
+
const result = runWorktreePostCreateHook(src, wt, hookFile);
|
|
131
|
+
assert.equal(result, null, "absolute path hook should succeed");
|
|
132
|
+
assert.ok(existsSync(join(wt, "absolute-hook-ran")), "hook should have run");
|
|
133
|
+
} finally {
|
|
134
|
+
rmSync(src, { recursive: true, force: true });
|
|
135
|
+
rmSync(wt, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("hook can copy files from source to worktree", () => {
|
|
140
|
+
const src = makeTmpDir();
|
|
141
|
+
const wt = makeTmpDir();
|
|
142
|
+
try {
|
|
143
|
+
writeFileSync(join(src, ".env"), "DB_HOST=localhost\nAPI_KEY=secret123\n");
|
|
144
|
+
|
|
145
|
+
const hookFile = hookPath(join(src, "setup-hook"));
|
|
146
|
+
const code = [
|
|
147
|
+
`const fs = require("fs");`,
|
|
148
|
+
`const path = require("path");`,
|
|
149
|
+
`const envSrc = path.join(process.env.SOURCE_DIR, ".env");`,
|
|
150
|
+
`const envDst = path.join(process.env.WORKTREE_DIR, ".env");`,
|
|
151
|
+
`fs.copyFileSync(envSrc, envDst);`,
|
|
152
|
+
].join("\n");
|
|
153
|
+
writeNodeHookScript(hookFile, code);
|
|
154
|
+
|
|
155
|
+
const result = runWorktreePostCreateHook(src, wt, hookFile);
|
|
156
|
+
assert.equal(result, null, "hook should succeed");
|
|
157
|
+
|
|
158
|
+
assert.ok(existsSync(join(wt, ".env")), ".env should be copied to worktree");
|
|
159
|
+
const envContent = readFileSync(join(wt, ".env"), "utf-8");
|
|
160
|
+
assert.ok(envContent.includes("API_KEY=secret123"), ".env content should match");
|
|
161
|
+
} finally {
|
|
162
|
+
rmSync(src, { recursive: true, force: true });
|
|
163
|
+
rmSync(wt, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Triage Resolution — Execute triage classifications
|
|
3
|
+
*
|
|
4
|
+
* Provides resolution executors for each capture classification type:
|
|
5
|
+
*
|
|
6
|
+
* - inject: appends a new task to the current slice plan
|
|
7
|
+
* - replan: writes REPLAN-TRIGGER.md so next dispatchNextUnit enters replanning-slice
|
|
8
|
+
* - defer/note: query helpers for loading deferred/replan captures
|
|
9
|
+
*
|
|
10
|
+
* Also provides detectFileOverlap() for surfacing downstream impact on quick tasks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import type { Classification, CaptureEntry } from "./captures.js";
|
|
16
|
+
import {
|
|
17
|
+
loadPendingCaptures,
|
|
18
|
+
loadAllCaptures,
|
|
19
|
+
markCaptureResolved,
|
|
20
|
+
} from "./captures.js";
|
|
21
|
+
|
|
22
|
+
// ─── Resolution Executors ─────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Inject a new task into the current slice plan.
|
|
26
|
+
* Reads the plan, finds the highest task ID, appends a new task entry.
|
|
27
|
+
* Returns the new task ID, or null if injection failed.
|
|
28
|
+
*/
|
|
29
|
+
export function executeInject(
|
|
30
|
+
basePath: string,
|
|
31
|
+
mid: string,
|
|
32
|
+
sid: string,
|
|
33
|
+
capture: CaptureEntry,
|
|
34
|
+
): string | null {
|
|
35
|
+
try {
|
|
36
|
+
// Resolve the plan file path
|
|
37
|
+
const planPath = join(basePath, ".gsd", "milestones", mid, "slices", sid, `${sid}-PLAN.md`);
|
|
38
|
+
if (!existsSync(planPath)) return null;
|
|
39
|
+
|
|
40
|
+
const content = readFileSync(planPath, "utf-8");
|
|
41
|
+
|
|
42
|
+
// Find the highest existing task ID
|
|
43
|
+
const taskMatches = [...content.matchAll(/- \[[ x]\] \*\*T(\d+):/g)];
|
|
44
|
+
if (taskMatches.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const maxId = Math.max(...taskMatches.map(m => parseInt(m[1], 10)));
|
|
47
|
+
const newId = `T${String(maxId + 1).padStart(2, "0")}`;
|
|
48
|
+
|
|
49
|
+
// Build the new task entry
|
|
50
|
+
const newTask = [
|
|
51
|
+
`- [ ] **${newId}: ${capture.text}** \`est:30m\``,
|
|
52
|
+
` - Why: Injected from capture ${capture.id} during triage`,
|
|
53
|
+
` - Do: ${capture.text}`,
|
|
54
|
+
` - Done when: Capture intent fulfilled`,
|
|
55
|
+
].join("\n");
|
|
56
|
+
|
|
57
|
+
// Find the last task entry and append after it
|
|
58
|
+
// Look for the "## Files Likely Touched" section as the boundary
|
|
59
|
+
const filesSection = content.indexOf("## Files Likely Touched");
|
|
60
|
+
if (filesSection !== -1) {
|
|
61
|
+
const updated = content.slice(0, filesSection) + newTask + "\n\n" + content.slice(filesSection);
|
|
62
|
+
writeFileSync(planPath, updated, "utf-8");
|
|
63
|
+
} else {
|
|
64
|
+
// No Files section — append at end
|
|
65
|
+
writeFileSync(planPath, content.trimEnd() + "\n\n" + newTask + "\n", "utf-8");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return newId;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Trigger replanning by writing a REPLAN-TRIGGER.md marker file.
|
|
76
|
+
* The existing state.ts derivation detects this and sets phase to "replanning-slice".
|
|
77
|
+
* Returns true if the trigger was written successfully.
|
|
78
|
+
*/
|
|
79
|
+
export function executeReplan(
|
|
80
|
+
basePath: string,
|
|
81
|
+
mid: string,
|
|
82
|
+
sid: string,
|
|
83
|
+
capture: CaptureEntry,
|
|
84
|
+
): boolean {
|
|
85
|
+
try {
|
|
86
|
+
const triggerPath = join(
|
|
87
|
+
basePath, ".gsd", "milestones", mid, "slices", sid, `${sid}-REPLAN-TRIGGER.md`,
|
|
88
|
+
);
|
|
89
|
+
const content = [
|
|
90
|
+
`# Replan Trigger`,
|
|
91
|
+
``,
|
|
92
|
+
`**Source:** Capture ${capture.id}`,
|
|
93
|
+
`**Capture:** ${capture.text}`,
|
|
94
|
+
`**Rationale:** ${capture.rationale ?? "User-initiated replan via capture triage"}`,
|
|
95
|
+
`**Triggered:** ${new Date().toISOString()}`,
|
|
96
|
+
``,
|
|
97
|
+
`This file was created by the triage pipeline. The next dispatch cycle`,
|
|
98
|
+
`will detect it and enter the replanning-slice phase.`,
|
|
99
|
+
].join("\n");
|
|
100
|
+
|
|
101
|
+
writeFileSync(triggerPath, content, "utf-8");
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── File Overlap Detection ───────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Detect file overlap between a capture's affected files and planned tasks.
|
|
112
|
+
*
|
|
113
|
+
* Parses the slice plan for task file references and returns task IDs
|
|
114
|
+
* whose files overlap with the capture's affected files.
|
|
115
|
+
*
|
|
116
|
+
* @param affectedFiles - Files the capture would touch
|
|
117
|
+
* @param planContent - Content of the slice plan.md
|
|
118
|
+
* @returns Array of task IDs (e.g., ["T03", "T04"]) whose files overlap
|
|
119
|
+
*/
|
|
120
|
+
export function detectFileOverlap(
|
|
121
|
+
affectedFiles: string[],
|
|
122
|
+
planContent: string,
|
|
123
|
+
): string[] {
|
|
124
|
+
if (!affectedFiles || affectedFiles.length === 0) return [];
|
|
125
|
+
|
|
126
|
+
const overlappingTasks: string[] = [];
|
|
127
|
+
|
|
128
|
+
// Normalize affected files for comparison
|
|
129
|
+
const normalizedAffected = new Set(
|
|
130
|
+
affectedFiles.map(f => f.replace(/^\.\//, "").toLowerCase()),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Parse plan for incomplete tasks and their file references
|
|
134
|
+
const taskPattern = /- \[ \] \*\*(T\d+):[^*]*\*\*/g;
|
|
135
|
+
const tasks = [...planContent.matchAll(taskPattern)];
|
|
136
|
+
|
|
137
|
+
for (const taskMatch of tasks) {
|
|
138
|
+
const taskId = taskMatch[1];
|
|
139
|
+
const taskStart = taskMatch.index!;
|
|
140
|
+
|
|
141
|
+
// Find the end of this task (next task or end of section)
|
|
142
|
+
const nextTask = planContent.indexOf("- [", taskStart + 1);
|
|
143
|
+
const sectionEnd = planContent.indexOf("##", taskStart + 1);
|
|
144
|
+
const taskEnd = Math.min(
|
|
145
|
+
nextTask === -1 ? planContent.length : nextTask,
|
|
146
|
+
sectionEnd === -1 ? planContent.length : sectionEnd,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const taskContent = planContent.slice(taskStart, taskEnd);
|
|
150
|
+
|
|
151
|
+
// Extract file references — look for backtick-quoted paths
|
|
152
|
+
const fileRefs = [...taskContent.matchAll(/`([^`]+\.[a-z]+)`/g)]
|
|
153
|
+
.map(m => m[1].replace(/^\.\//, "").toLowerCase());
|
|
154
|
+
|
|
155
|
+
// Check for overlap
|
|
156
|
+
const hasOverlap = fileRefs.some(f => normalizedAffected.has(f));
|
|
157
|
+
if (hasOverlap) {
|
|
158
|
+
overlappingTasks.push(taskId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return overlappingTasks;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Load deferred captures (classification === "defer") for injection into
|
|
167
|
+
* reassess-roadmap prompts.
|
|
168
|
+
*/
|
|
169
|
+
export function loadDeferredCaptures(basePath: string): CaptureEntry[] {
|
|
170
|
+
return loadAllCaptures(basePath).filter(c => c.classification === "defer");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load replan-triggering captures for injection into replan-slice prompts.
|
|
175
|
+
*/
|
|
176
|
+
export function loadReplanCaptures(basePath: string): CaptureEntry[] {
|
|
177
|
+
return loadAllCaptures(basePath).filter(c => c.classification === "replan");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build a quick-task execution prompt from a capture.
|
|
182
|
+
*/
|
|
183
|
+
export function buildQuickTaskPrompt(capture: CaptureEntry): string {
|
|
184
|
+
return [
|
|
185
|
+
`You are executing a quick one-off task captured during a GSD auto-mode session.`,
|
|
186
|
+
``,
|
|
187
|
+
`## Quick Task`,
|
|
188
|
+
``,
|
|
189
|
+
`**Capture ID:** ${capture.id}`,
|
|
190
|
+
`**Task:** ${capture.text}`,
|
|
191
|
+
``,
|
|
192
|
+
`## Instructions`,
|
|
193
|
+
``,
|
|
194
|
+
`1. Execute this task as a small, self-contained change.`,
|
|
195
|
+
`2. Do NOT modify any \`.gsd/\` plan files — this is a one-off, not a planned task.`,
|
|
196
|
+
`3. Commit your changes with a descriptive message.`,
|
|
197
|
+
`4. Keep changes minimal and focused on the capture text.`,
|
|
198
|
+
`5. When done, say: "Quick task complete."`,
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Triage UI — Confirmation flow for programmatic triage results
|
|
3
|
+
*
|
|
4
|
+
* Used by auto-mode dispatch (S02) when triage fires between tasks.
|
|
5
|
+
* For manual `/gsd triage`, the LLM session handles confirmation directly.
|
|
6
|
+
*
|
|
7
|
+
* This module provides `showTriageConfirmation` which presents each
|
|
8
|
+
* triage result to the user via `showNextAction` and returns the
|
|
9
|
+
* confirmed classifications.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
|
+
import { showNextAction } from "../shared/next-action-ui.js";
|
|
14
|
+
import type { CaptureEntry, Classification, TriageResult } from "./captures.js";
|
|
15
|
+
import { markCaptureResolved } from "./captures.js";
|
|
16
|
+
|
|
17
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export interface ConfirmedTriage {
|
|
20
|
+
captureId: string;
|
|
21
|
+
classification: Classification;
|
|
22
|
+
rationale: string;
|
|
23
|
+
affectedFiles?: string[];
|
|
24
|
+
targetSlice?: string;
|
|
25
|
+
userOverride: boolean; // true if user changed the proposed classification
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Classification Labels ────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CLASSIFICATION_LABELS: Record<Classification, { label: string; description: string }> = {
|
|
31
|
+
"quick-task": {
|
|
32
|
+
label: "Quick task",
|
|
33
|
+
description: "Execute as a one-off at the next seam — no plan modification.",
|
|
34
|
+
},
|
|
35
|
+
"inject": {
|
|
36
|
+
label: "Inject into plan",
|
|
37
|
+
description: "Add a new task to the current slice plan.",
|
|
38
|
+
},
|
|
39
|
+
"defer": {
|
|
40
|
+
label: "Defer",
|
|
41
|
+
description: "Move to a future slice or milestone — not urgent now.",
|
|
42
|
+
},
|
|
43
|
+
"replan": {
|
|
44
|
+
label: "Replan slice",
|
|
45
|
+
description: "Remaining tasks need rewriting — triggers slice replan.",
|
|
46
|
+
},
|
|
47
|
+
"note": {
|
|
48
|
+
label: "Note",
|
|
49
|
+
description: "Informational only — no action needed.",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ALL_CLASSIFICATIONS: Classification[] = [
|
|
54
|
+
"quick-task", "inject", "defer", "replan", "note",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Present triage results to the user for confirmation.
|
|
61
|
+
*
|
|
62
|
+
* For each capture:
|
|
63
|
+
* - note/defer: auto-confirm (no user interaction needed)
|
|
64
|
+
* - quick-task/inject/replan: show confirmation UI with proposed + alternatives
|
|
65
|
+
*
|
|
66
|
+
* Returns confirmed results with final classifications.
|
|
67
|
+
* Updates CAPTURES.md with resolved status.
|
|
68
|
+
*
|
|
69
|
+
* @param fileOverlaps - Map of captureId → list of planned task IDs whose files overlap
|
|
70
|
+
*/
|
|
71
|
+
export async function showTriageConfirmation(
|
|
72
|
+
ctx: ExtensionCommandContext,
|
|
73
|
+
triageResults: TriageResult[],
|
|
74
|
+
captures: CaptureEntry[],
|
|
75
|
+
basePath: string,
|
|
76
|
+
fileOverlaps?: Map<string, string[]>,
|
|
77
|
+
): Promise<ConfirmedTriage[]> {
|
|
78
|
+
const confirmed: ConfirmedTriage[] = [];
|
|
79
|
+
const captureMap = new Map(captures.map(c => [c.id, c]));
|
|
80
|
+
|
|
81
|
+
for (const result of triageResults) {
|
|
82
|
+
const capture = captureMap.get(result.captureId);
|
|
83
|
+
if (!capture) continue;
|
|
84
|
+
|
|
85
|
+
// Auto-confirm note and defer — low-impact, no plan modification
|
|
86
|
+
if (result.classification === "note" || result.classification === "defer") {
|
|
87
|
+
const resolution = result.classification === "note"
|
|
88
|
+
? "acknowledged as note"
|
|
89
|
+
: `deferred${result.targetSlice ? ` to ${result.targetSlice}` : ""}`;
|
|
90
|
+
|
|
91
|
+
markCaptureResolved(
|
|
92
|
+
basePath,
|
|
93
|
+
result.captureId,
|
|
94
|
+
result.classification,
|
|
95
|
+
resolution,
|
|
96
|
+
result.rationale,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
confirmed.push({
|
|
100
|
+
captureId: result.captureId,
|
|
101
|
+
classification: result.classification,
|
|
102
|
+
rationale: result.rationale,
|
|
103
|
+
affectedFiles: result.affectedFiles,
|
|
104
|
+
targetSlice: result.targetSlice,
|
|
105
|
+
userOverride: false,
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Build summary lines for the confirmation UI
|
|
111
|
+
const summary: string[] = [
|
|
112
|
+
`"${capture.text}"`,
|
|
113
|
+
"",
|
|
114
|
+
`Proposed: **${CLASSIFICATION_LABELS[result.classification].label}** — ${result.rationale}`,
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// Add file overlap warning if present
|
|
118
|
+
const overlaps = fileOverlaps?.get(result.captureId);
|
|
119
|
+
if (overlaps && overlaps.length > 0) {
|
|
120
|
+
summary.push("");
|
|
121
|
+
summary.push(`⚠ Touches files planned for ${overlaps.join(", ")} — consider inject or defer`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (result.affectedFiles && result.affectedFiles.length > 0) {
|
|
125
|
+
summary.push("");
|
|
126
|
+
summary.push(`Files: ${result.affectedFiles.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Build action options — proposed first (recommended), then alternatives
|
|
130
|
+
const proposed = result.classification;
|
|
131
|
+
const actions = ALL_CLASSIFICATIONS.map(cls => ({
|
|
132
|
+
id: cls,
|
|
133
|
+
label: CLASSIFICATION_LABELS[cls].label,
|
|
134
|
+
description: CLASSIFICATION_LABELS[cls].description,
|
|
135
|
+
recommended: cls === proposed,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
const choice = await showNextAction(ctx as any, {
|
|
139
|
+
title: `Triage: ${result.captureId}`,
|
|
140
|
+
summary,
|
|
141
|
+
actions,
|
|
142
|
+
notYetMessage: "Capture will remain pending for later triage.",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (choice === "not_yet") {
|
|
146
|
+
// User skipped — leave capture pending
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const finalClassification = choice as Classification;
|
|
151
|
+
const userOverride = finalClassification !== proposed;
|
|
152
|
+
const resolution = userOverride
|
|
153
|
+
? `user chose ${finalClassification} (was ${proposed})`
|
|
154
|
+
: `confirmed as ${finalClassification}`;
|
|
155
|
+
|
|
156
|
+
markCaptureResolved(
|
|
157
|
+
basePath,
|
|
158
|
+
result.captureId,
|
|
159
|
+
finalClassification,
|
|
160
|
+
resolution,
|
|
161
|
+
userOverride ? `User override: ${result.rationale}` : result.rationale,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
confirmed.push({
|
|
165
|
+
captureId: result.captureId,
|
|
166
|
+
classification: finalClassification,
|
|
167
|
+
rationale: result.rationale,
|
|
168
|
+
affectedFiles: result.affectedFiles,
|
|
169
|
+
targetSlice: result.targetSlice,
|
|
170
|
+
userOverride,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return confirmed;
|
|
175
|
+
}
|
|
@@ -334,3 +334,32 @@ export interface HookStatusEntry {
|
|
|
334
334
|
/** Current cycle counts for active triggers. */
|
|
335
335
|
activeCycles: Record<string, number>;
|
|
336
336
|
}
|
|
337
|
+
|
|
338
|
+
// ─── Database Types (Decisions & Requirements) ────────────────────────────
|
|
339
|
+
|
|
340
|
+
export interface Decision {
|
|
341
|
+
seq: number; // auto-increment primary key
|
|
342
|
+
id: string; // e.g. "D001"
|
|
343
|
+
when_context: string; // when/context of the decision
|
|
344
|
+
scope: string; // scope (milestone, slice, global, etc.)
|
|
345
|
+
decision: string; // what was decided
|
|
346
|
+
choice: string; // the specific choice made
|
|
347
|
+
rationale: string; // why this choice
|
|
348
|
+
revisable: string; // whether/when revisable
|
|
349
|
+
superseded_by: string | null; // ID of superseding decision, or null
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface Requirement {
|
|
353
|
+
id: string; // e.g. "R001"
|
|
354
|
+
class: string; // requirement class (functional, non-functional, etc.)
|
|
355
|
+
status: string; // active, validated, deferred, etc.
|
|
356
|
+
description: string; // short description
|
|
357
|
+
why: string; // rationale
|
|
358
|
+
source: string; // origin (milestone, user, etc.)
|
|
359
|
+
primary_owner: string; // owning slice/milestone
|
|
360
|
+
supporting_slices: string; // other slices that touch this
|
|
361
|
+
validation: string; // how to validate
|
|
362
|
+
notes: string; // additional notes
|
|
363
|
+
full_content: string; // full requirement text
|
|
364
|
+
superseded_by: string | null; // ID of superseding requirement, or null
|
|
365
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// GSD Extension — Undo Last Unit
|
|
2
2
|
// Rollback the most recent completed unit: revert git, remove state, uncheck plans.
|
|
3
|
-
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
4
3
|
|
|
5
4
|
import type { ExtensionCommandContext, ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
6
5
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from "node:fs";
|
|
@@ -36,6 +36,7 @@ export interface AutoUnitRuntimeRecord {
|
|
|
36
36
|
updatedAt: number;
|
|
37
37
|
phase: UnitRuntimePhase;
|
|
38
38
|
wrapupWarningSent: boolean;
|
|
39
|
+
continueHereFired: boolean;
|
|
39
40
|
timeoutAt: number | null;
|
|
40
41
|
lastProgressAt: number;
|
|
41
42
|
progressCount: number;
|
|
@@ -50,7 +51,9 @@ function runtimeDir(basePath: string): string {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
function runtimePath(basePath: string, unitType: string, unitId: string): string {
|
|
53
|
-
|
|
54
|
+
const sanitizedUnitType = unitType.replace(/[\/]/g, "-");
|
|
55
|
+
const sanitizedUnitId = unitId.replace(/[\/]/g, "-");
|
|
56
|
+
return join(runtimeDir(basePath), `${sanitizedUnitType}-${sanitizedUnitId}.json`);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export function writeUnitRuntimeRecord(
|
|
@@ -72,6 +75,7 @@ export function writeUnitRuntimeRecord(
|
|
|
72
75
|
updatedAt: Date.now(),
|
|
73
76
|
phase: updates.phase ?? prev?.phase ?? "dispatched",
|
|
74
77
|
wrapupWarningSent: updates.wrapupWarningSent ?? prev?.wrapupWarningSent ?? false,
|
|
78
|
+
continueHereFired: updates.continueHereFired ?? prev?.continueHereFired ?? false,
|
|
75
79
|
timeoutAt: updates.timeoutAt ?? prev?.timeoutAt ?? null,
|
|
76
80
|
lastProgressAt: updates.lastProgressAt ?? prev?.lastProgressAt ?? Date.now(),
|
|
77
81
|
progressCount: updates.progressCount ?? prev?.progressCount ?? 0,
|