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,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Captures — Fire-and-forget thought capture with triage classification
|
|
3
|
+
*
|
|
4
|
+
* Append-only capture file at `.gsd/CAPTURES.md`. Each capture is an H3 section
|
|
5
|
+
* with bold metadata fields, parseable by the same patterns used in files.ts.
|
|
6
|
+
*
|
|
7
|
+
* Worktree-aware: captures always resolve to the original project root's
|
|
8
|
+
* `.gsd/CAPTURES.md`, not the worktree's local `.gsd/`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join, resolve, sep } from "node:path";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { gsdRoot } from "./paths.js";
|
|
15
|
+
|
|
16
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export type Classification = "quick-task" | "inject" | "defer" | "replan" | "note";
|
|
19
|
+
|
|
20
|
+
export interface CaptureEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
text: string;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
status: "pending" | "triaged" | "resolved";
|
|
25
|
+
classification?: Classification;
|
|
26
|
+
resolution?: string;
|
|
27
|
+
rationale?: string;
|
|
28
|
+
resolvedAt?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TriageResult {
|
|
32
|
+
captureId: string;
|
|
33
|
+
classification: Classification;
|
|
34
|
+
rationale: string;
|
|
35
|
+
affectedFiles?: string[];
|
|
36
|
+
targetSlice?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const CAPTURES_FILENAME = "CAPTURES.md";
|
|
42
|
+
const VALID_CLASSIFICATIONS: readonly string[] = [
|
|
43
|
+
"quick-task", "inject", "defer", "replan", "note",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
// ─── Path Resolution ──────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the path to CAPTURES.md, aware of worktree context.
|
|
50
|
+
*
|
|
51
|
+
* In worktree-isolated mode, basePath is `.gsd/worktrees/<MID>/`.
|
|
52
|
+
* Captures must resolve to the *original* project root's `.gsd/CAPTURES.md`,
|
|
53
|
+
* not the worktree-local `.gsd/`. This ensures all captures go to one file
|
|
54
|
+
* regardless of which worktree the agent is running in.
|
|
55
|
+
*
|
|
56
|
+
* Detection: if basePath contains `/.gsd/worktrees/`, walk up to the
|
|
57
|
+
* directory that contains `.gsd/worktrees/` — that's the project root.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveCapturesPath(basePath: string): string {
|
|
60
|
+
const resolved = resolve(basePath);
|
|
61
|
+
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
62
|
+
const idx = resolved.indexOf(worktreeMarker);
|
|
63
|
+
if (idx !== -1) {
|
|
64
|
+
// basePath is inside a worktree — resolve to project root
|
|
65
|
+
const projectRoot = resolved.slice(0, idx);
|
|
66
|
+
return join(projectRoot, ".gsd", CAPTURES_FILENAME);
|
|
67
|
+
}
|
|
68
|
+
return join(gsdRoot(basePath), CAPTURES_FILENAME);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── File I/O ─────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Append a new capture entry to CAPTURES.md.
|
|
75
|
+
* Creates `.gsd/` and the file if they don't exist.
|
|
76
|
+
* Returns the generated capture ID.
|
|
77
|
+
*/
|
|
78
|
+
export function appendCapture(basePath: string, text: string): string {
|
|
79
|
+
const filePath = resolveCapturesPath(basePath);
|
|
80
|
+
const dir = join(filePath, "..");
|
|
81
|
+
if (!existsSync(dir)) {
|
|
82
|
+
mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const id = `CAP-${randomUUID().slice(0, 8)}`;
|
|
86
|
+
const timestamp = new Date().toISOString();
|
|
87
|
+
|
|
88
|
+
const entry = [
|
|
89
|
+
`### ${id}`,
|
|
90
|
+
`**Text:** ${text}`,
|
|
91
|
+
`**Captured:** ${timestamp}`,
|
|
92
|
+
`**Status:** pending`,
|
|
93
|
+
"",
|
|
94
|
+
].join("\n");
|
|
95
|
+
|
|
96
|
+
if (existsSync(filePath)) {
|
|
97
|
+
const existing = readFileSync(filePath, "utf-8");
|
|
98
|
+
writeFileSync(filePath, existing.trimEnd() + "\n\n" + entry, "utf-8");
|
|
99
|
+
} else {
|
|
100
|
+
const header = `# Captures\n\n`;
|
|
101
|
+
writeFileSync(filePath, header + entry, "utf-8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return id;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Parse all capture entries from CAPTURES.md.
|
|
109
|
+
* Returns entries in file order (oldest first).
|
|
110
|
+
*/
|
|
111
|
+
export function loadAllCaptures(basePath: string): CaptureEntry[] {
|
|
112
|
+
const filePath = resolveCapturesPath(basePath);
|
|
113
|
+
if (!existsSync(filePath)) return [];
|
|
114
|
+
|
|
115
|
+
const content = readFileSync(filePath, "utf-8");
|
|
116
|
+
return parseCapturesContent(content);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Load only pending (unresolved) captures.
|
|
121
|
+
*/
|
|
122
|
+
export function loadPendingCaptures(basePath: string): CaptureEntry[] {
|
|
123
|
+
return loadAllCaptures(basePath).filter(c => c.status === "pending");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Fast check for pending captures without full parse.
|
|
128
|
+
* Reads the file and scans for `**Status:** pending` via regex.
|
|
129
|
+
* Returns false if the file doesn't exist.
|
|
130
|
+
*/
|
|
131
|
+
export function hasPendingCaptures(basePath: string): boolean {
|
|
132
|
+
const filePath = resolveCapturesPath(basePath);
|
|
133
|
+
if (!existsSync(filePath)) return false;
|
|
134
|
+
try {
|
|
135
|
+
const content = readFileSync(filePath, "utf-8");
|
|
136
|
+
return /\*\*Status:\*\*\s*pending/i.test(content);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Count pending captures without full parse — single file read.
|
|
144
|
+
* Uses regex to count `**Status:** pending` occurrences.
|
|
145
|
+
* Returns 0 if file doesn't exist or on error.
|
|
146
|
+
*/
|
|
147
|
+
export function countPendingCaptures(basePath: string): number {
|
|
148
|
+
const filePath = resolveCapturesPath(basePath);
|
|
149
|
+
if (!existsSync(filePath)) return 0;
|
|
150
|
+
try {
|
|
151
|
+
const content = readFileSync(filePath, "utf-8");
|
|
152
|
+
const matches = content.match(/\*\*Status:\*\*\s*pending/gi);
|
|
153
|
+
return matches ? matches.length : 0;
|
|
154
|
+
} catch {
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Mark a capture as resolved with classification and rationale.
|
|
161
|
+
* Rewrites the entry in place, preserving other entries.
|
|
162
|
+
*/
|
|
163
|
+
export function markCaptureResolved(
|
|
164
|
+
basePath: string,
|
|
165
|
+
captureId: string,
|
|
166
|
+
classification: Classification,
|
|
167
|
+
resolution: string,
|
|
168
|
+
rationale: string,
|
|
169
|
+
): void {
|
|
170
|
+
const filePath = resolveCapturesPath(basePath);
|
|
171
|
+
if (!existsSync(filePath)) return;
|
|
172
|
+
|
|
173
|
+
const content = readFileSync(filePath, "utf-8");
|
|
174
|
+
const resolvedAt = new Date().toISOString();
|
|
175
|
+
|
|
176
|
+
// Find the section for this capture ID and rewrite its fields
|
|
177
|
+
const sectionRegex = new RegExp(
|
|
178
|
+
`(### ${escapeRegex(captureId)}\\n(?:(?!### ).)*?)(?=### |$)`,
|
|
179
|
+
"s",
|
|
180
|
+
);
|
|
181
|
+
const match = sectionRegex.exec(content);
|
|
182
|
+
if (!match) return;
|
|
183
|
+
|
|
184
|
+
let section = match[1];
|
|
185
|
+
|
|
186
|
+
// Update Status field
|
|
187
|
+
section = section.replace(
|
|
188
|
+
/\*\*Status:\*\*\s*.+/,
|
|
189
|
+
`**Status:** resolved`,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Append classification, resolution, rationale, and timestamp if not present
|
|
193
|
+
const newFields = [
|
|
194
|
+
`**Classification:** ${classification}`,
|
|
195
|
+
`**Resolution:** ${resolution}`,
|
|
196
|
+
`**Rationale:** ${rationale}`,
|
|
197
|
+
`**Resolved:** ${resolvedAt}`,
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
// Remove any existing classification/resolution/rationale/resolved fields
|
|
201
|
+
// (in case of re-triage)
|
|
202
|
+
section = section.replace(/\*\*Classification:\*\*\s*.+\n?/g, "");
|
|
203
|
+
section = section.replace(/\*\*Resolution:\*\*\s*.+\n?/g, "");
|
|
204
|
+
section = section.replace(/\*\*Rationale:\*\*\s*.+\n?/g, "");
|
|
205
|
+
section = section.replace(/\*\*Resolved:\*\*\s*.+\n?/g, "");
|
|
206
|
+
|
|
207
|
+
// Add new fields after Status line
|
|
208
|
+
section = section.trimEnd() + "\n" + newFields.join("\n") + "\n";
|
|
209
|
+
|
|
210
|
+
const updated = content.replace(sectionRegex, section);
|
|
211
|
+
writeFileSync(filePath, updated, "utf-8");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Parse CAPTURES.md content into CaptureEntry array.
|
|
218
|
+
*/
|
|
219
|
+
function parseCapturesContent(content: string): CaptureEntry[] {
|
|
220
|
+
const entries: CaptureEntry[] = [];
|
|
221
|
+
|
|
222
|
+
// Split on H3 headings
|
|
223
|
+
const sections = content.split(/^### /m).slice(1); // skip content before first H3
|
|
224
|
+
|
|
225
|
+
for (const section of sections) {
|
|
226
|
+
const lines = section.split("\n");
|
|
227
|
+
const id = lines[0]?.trim();
|
|
228
|
+
if (!id) continue;
|
|
229
|
+
|
|
230
|
+
const body = lines.slice(1).join("\n");
|
|
231
|
+
const text = extractBoldField(body, "Text");
|
|
232
|
+
const timestamp = extractBoldField(body, "Captured");
|
|
233
|
+
const statusRaw = extractBoldField(body, "Status");
|
|
234
|
+
const classification = extractBoldField(body, "Classification") as Classification | null;
|
|
235
|
+
const resolution = extractBoldField(body, "Resolution");
|
|
236
|
+
const rationale = extractBoldField(body, "Rationale");
|
|
237
|
+
const resolvedAt = extractBoldField(body, "Resolved");
|
|
238
|
+
|
|
239
|
+
if (!text || !timestamp) continue;
|
|
240
|
+
|
|
241
|
+
const status = (statusRaw === "resolved" || statusRaw === "triaged")
|
|
242
|
+
? statusRaw
|
|
243
|
+
: "pending";
|
|
244
|
+
|
|
245
|
+
entries.push({
|
|
246
|
+
id,
|
|
247
|
+
text,
|
|
248
|
+
timestamp,
|
|
249
|
+
status,
|
|
250
|
+
...(classification && VALID_CLASSIFICATIONS.includes(classification) ? { classification } : {}),
|
|
251
|
+
...(resolution ? { resolution } : {}),
|
|
252
|
+
...(rationale ? { rationale } : {}),
|
|
253
|
+
...(resolvedAt ? { resolvedAt } : {}),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return entries;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Extract value from a bold-prefixed line like "**Key:** Value".
|
|
262
|
+
* Local copy of the pattern from files.ts to keep this module self-contained.
|
|
263
|
+
*/
|
|
264
|
+
function extractBoldField(text: string, key: string): string | null {
|
|
265
|
+
const regex = new RegExp(`^\\*\\*${escapeRegex(key)}:\\*\\*\\s*(.+)$`, "m");
|
|
266
|
+
const match = regex.exec(text);
|
|
267
|
+
return match ? match[1].trim() : null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function escapeRegex(s: string): string {
|
|
271
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── Triage Output Parser ─────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Parse LLM triage output into TriageResult array.
|
|
278
|
+
*
|
|
279
|
+
* Handles:
|
|
280
|
+
* - Clean JSON array
|
|
281
|
+
* - JSON wrapped in fenced code block (```json ... ```)
|
|
282
|
+
* - JSON with leading/trailing prose
|
|
283
|
+
* - Single object (not array) — wraps in array
|
|
284
|
+
* - Malformed JSON — returns empty array (caller should fall back to note)
|
|
285
|
+
* - Partial results — valid entries are kept, invalid skipped
|
|
286
|
+
*/
|
|
287
|
+
export function parseTriageOutput(llmResponse: string): TriageResult[] {
|
|
288
|
+
if (!llmResponse || !llmResponse.trim()) return [];
|
|
289
|
+
|
|
290
|
+
// Try to extract JSON from fenced code blocks first
|
|
291
|
+
const fenced = llmResponse.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
292
|
+
const jsonStr = fenced ? fenced[1] : extractJsonSubstring(llmResponse);
|
|
293
|
+
|
|
294
|
+
if (!jsonStr) return [];
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const parsed = JSON.parse(jsonStr);
|
|
298
|
+
const arr = Array.isArray(parsed) ? parsed : [parsed];
|
|
299
|
+
return arr
|
|
300
|
+
.filter(isValidTriageResult)
|
|
301
|
+
.map(normalizeTriageResult);
|
|
302
|
+
} catch {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Try to find a JSON array or object substring in prose text.
|
|
309
|
+
* Looks for the first [ or { and finds its matching bracket.
|
|
310
|
+
*/
|
|
311
|
+
function extractJsonSubstring(text: string): string | null {
|
|
312
|
+
// Find first [ or {
|
|
313
|
+
const arrStart = text.indexOf("[");
|
|
314
|
+
const objStart = text.indexOf("{");
|
|
315
|
+
|
|
316
|
+
let start: number;
|
|
317
|
+
let openChar: string;
|
|
318
|
+
let closeChar: string;
|
|
319
|
+
|
|
320
|
+
if (arrStart === -1 && objStart === -1) return null;
|
|
321
|
+
if (arrStart === -1) {
|
|
322
|
+
start = objStart;
|
|
323
|
+
openChar = "{";
|
|
324
|
+
closeChar = "}";
|
|
325
|
+
} else if (objStart === -1) {
|
|
326
|
+
start = arrStart;
|
|
327
|
+
openChar = "[";
|
|
328
|
+
closeChar = "]";
|
|
329
|
+
} else {
|
|
330
|
+
start = Math.min(arrStart, objStart);
|
|
331
|
+
openChar = start === arrStart ? "[" : "{";
|
|
332
|
+
closeChar = start === arrStart ? "]" : "}";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Find matching bracket
|
|
336
|
+
let depth = 0;
|
|
337
|
+
let inString = false;
|
|
338
|
+
let escape = false;
|
|
339
|
+
|
|
340
|
+
for (let i = start; i < text.length; i++) {
|
|
341
|
+
const ch = text[i];
|
|
342
|
+
if (escape) {
|
|
343
|
+
escape = false;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (ch === "\\") {
|
|
347
|
+
escape = true;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (ch === '"') {
|
|
351
|
+
inString = !inString;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (inString) continue;
|
|
355
|
+
if (ch === openChar) depth++;
|
|
356
|
+
if (ch === closeChar) depth--;
|
|
357
|
+
if (depth === 0) {
|
|
358
|
+
return text.slice(start, i + 1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isValidTriageResult(obj: unknown): boolean {
|
|
366
|
+
if (!obj || typeof obj !== "object") return false;
|
|
367
|
+
const o = obj as Record<string, unknown>;
|
|
368
|
+
return (
|
|
369
|
+
typeof o.captureId === "string" &&
|
|
370
|
+
typeof o.classification === "string" &&
|
|
371
|
+
VALID_CLASSIFICATIONS.includes(o.classification) &&
|
|
372
|
+
typeof o.rationale === "string"
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function normalizeTriageResult(obj: Record<string, unknown>): TriageResult {
|
|
377
|
+
return {
|
|
378
|
+
captureId: obj.captureId as string,
|
|
379
|
+
classification: obj.classification as Classification,
|
|
380
|
+
rationale: obj.rationale as string,
|
|
381
|
+
...(Array.isArray(obj.affectedFiles) ? { affectedFiles: obj.affectedFiles as string[] } : {}),
|
|
382
|
+
...(typeof obj.targetSlice === "string" ? { targetSlice: obj.targetSlice } : {}),
|
|
383
|
+
};
|
|
384
|
+
}
|