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,195 @@
|
|
|
1
|
+
// GSD Context Store — Query Layer & Formatters
|
|
2
|
+
//
|
|
3
|
+
// Typed query functions for decisions and requirements from the DB views,
|
|
4
|
+
// with optional filtering. Format functions produce prompt-injectable markdown.
|
|
5
|
+
// All functions degrade gracefully: return empty results when DB unavailable, never throw.
|
|
6
|
+
|
|
7
|
+
import { isDbAvailable, _getAdapter } from './gsd-db.js';
|
|
8
|
+
import type { Decision, Requirement } from './types.js';
|
|
9
|
+
|
|
10
|
+
// ─── Query Functions ───────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface DecisionQueryOpts {
|
|
13
|
+
milestoneId?: string;
|
|
14
|
+
scope?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RequirementQueryOpts {
|
|
18
|
+
sliceId?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Query active (non-superseded) decisions with optional filters.
|
|
24
|
+
* - milestoneId: filters where when_context LIKE '%milestoneId%'
|
|
25
|
+
* - scope: filters where scope = :scope (exact match)
|
|
26
|
+
*
|
|
27
|
+
* Returns [] if DB is not available. Never throws.
|
|
28
|
+
*/
|
|
29
|
+
export function queryDecisions(opts?: DecisionQueryOpts): Decision[] {
|
|
30
|
+
if (!isDbAvailable()) return [];
|
|
31
|
+
const adapter = _getAdapter();
|
|
32
|
+
if (!adapter) return [];
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const clauses: string[] = ['superseded_by IS NULL'];
|
|
36
|
+
const params: Record<string, unknown> = {};
|
|
37
|
+
|
|
38
|
+
if (opts?.milestoneId) {
|
|
39
|
+
clauses.push('when_context LIKE :milestone_pattern');
|
|
40
|
+
params[':milestone_pattern'] = `%${opts.milestoneId}%`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (opts?.scope) {
|
|
44
|
+
clauses.push('scope = :scope');
|
|
45
|
+
params[':scope'] = opts.scope;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sql = `SELECT * FROM decisions WHERE ${clauses.join(' AND ')} ORDER BY seq`;
|
|
49
|
+
const rows = adapter.prepare(sql).all(params);
|
|
50
|
+
|
|
51
|
+
return rows.map(row => ({
|
|
52
|
+
seq: row['seq'] as number,
|
|
53
|
+
id: row['id'] as string,
|
|
54
|
+
when_context: row['when_context'] as string,
|
|
55
|
+
scope: row['scope'] as string,
|
|
56
|
+
decision: row['decision'] as string,
|
|
57
|
+
choice: row['choice'] as string,
|
|
58
|
+
rationale: row['rationale'] as string,
|
|
59
|
+
revisable: row['revisable'] as string,
|
|
60
|
+
superseded_by: null,
|
|
61
|
+
}));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Query active (non-superseded) requirements with optional filters.
|
|
69
|
+
* - sliceId: filters where primary_owner LIKE '%sliceId%' OR supporting_slices LIKE '%sliceId%'
|
|
70
|
+
* - status: filters where status = :status (exact match)
|
|
71
|
+
*
|
|
72
|
+
* Returns [] if DB is not available. Never throws.
|
|
73
|
+
*/
|
|
74
|
+
export function queryRequirements(opts?: RequirementQueryOpts): Requirement[] {
|
|
75
|
+
if (!isDbAvailable()) return [];
|
|
76
|
+
const adapter = _getAdapter();
|
|
77
|
+
if (!adapter) return [];
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const clauses: string[] = ['superseded_by IS NULL'];
|
|
81
|
+
const params: Record<string, unknown> = {};
|
|
82
|
+
|
|
83
|
+
if (opts?.sliceId) {
|
|
84
|
+
clauses.push('(primary_owner LIKE :slice_pattern OR supporting_slices LIKE :slice_pattern)');
|
|
85
|
+
params[':slice_pattern'] = `%${opts.sliceId}%`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (opts?.status) {
|
|
89
|
+
clauses.push('status = :status');
|
|
90
|
+
params[':status'] = opts.status;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sql = `SELECT * FROM requirements WHERE ${clauses.join(' AND ')} ORDER BY id`;
|
|
94
|
+
const rows = adapter.prepare(sql).all(params);
|
|
95
|
+
|
|
96
|
+
return rows.map(row => ({
|
|
97
|
+
id: row['id'] as string,
|
|
98
|
+
class: row['class'] as string,
|
|
99
|
+
status: row['status'] as string,
|
|
100
|
+
description: row['description'] as string,
|
|
101
|
+
why: row['why'] as string,
|
|
102
|
+
source: row['source'] as string,
|
|
103
|
+
primary_owner: row['primary_owner'] as string,
|
|
104
|
+
supporting_slices: row['supporting_slices'] as string,
|
|
105
|
+
validation: row['validation'] as string,
|
|
106
|
+
notes: row['notes'] as string,
|
|
107
|
+
full_content: row['full_content'] as string,
|
|
108
|
+
superseded_by: null,
|
|
109
|
+
}));
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Format Functions ──────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Format decisions as a markdown table matching DECISIONS.md format.
|
|
119
|
+
* Returns empty string for empty input.
|
|
120
|
+
*/
|
|
121
|
+
export function formatDecisionsForPrompt(decisions: Decision[]): string {
|
|
122
|
+
if (decisions.length === 0) return '';
|
|
123
|
+
|
|
124
|
+
const header = '| # | When | Scope | Decision | Choice | Rationale | Revisable? |';
|
|
125
|
+
const separator = '|---|------|-------|----------|--------|-----------|------------|';
|
|
126
|
+
const rows = decisions.map(d =>
|
|
127
|
+
`| ${d.id} | ${d.when_context} | ${d.scope} | ${d.decision} | ${d.choice} | ${d.rationale} | ${d.revisable} |`,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return [header, separator, ...rows].join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format requirements as structured H3 sections matching REQUIREMENTS.md format.
|
|
135
|
+
* Returns empty string for empty input.
|
|
136
|
+
*/
|
|
137
|
+
export function formatRequirementsForPrompt(requirements: Requirement[]): string {
|
|
138
|
+
if (requirements.length === 0) return '';
|
|
139
|
+
|
|
140
|
+
return requirements.map(r => {
|
|
141
|
+
const lines: string[] = [
|
|
142
|
+
`### ${r.id}: ${r.description}`,
|
|
143
|
+
'',
|
|
144
|
+
`- **Class:** ${r.class}`,
|
|
145
|
+
`- **Status:** ${r.status}`,
|
|
146
|
+
`- **Why:** ${r.why}`,
|
|
147
|
+
`- **Source:** ${r.source}`,
|
|
148
|
+
`- **Primary Owner:** ${r.primary_owner}`,
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
if (r.supporting_slices) {
|
|
152
|
+
lines.push(`- **Supporting Slices:** ${r.supporting_slices}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
lines.push(`- **Validation:** ${r.validation}`);
|
|
156
|
+
|
|
157
|
+
if (r.notes) {
|
|
158
|
+
lines.push(`- **Notes:** ${r.notes}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}).join('\n\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Artifact Query Functions ──────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Query a hierarchy artifact by its relative path.
|
|
169
|
+
* Returns the full_content string or null if not found/unavailable.
|
|
170
|
+
* Never throws.
|
|
171
|
+
*/
|
|
172
|
+
export function queryArtifact(path: string): string | null {
|
|
173
|
+
if (!isDbAvailable()) return null;
|
|
174
|
+
const adapter = _getAdapter();
|
|
175
|
+
if (!adapter) return null;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const row = adapter.prepare('SELECT full_content FROM artifacts WHERE path = :path').get({ ':path': path });
|
|
179
|
+
if (!row) return null;
|
|
180
|
+
const content = row['full_content'] as string;
|
|
181
|
+
return content || null;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Query PROJECT.md content from the artifacts table.
|
|
189
|
+
* PROJECT.md is stored with the relative path 'PROJECT.md' by the importer.
|
|
190
|
+
* Returns the content string or null if not found/unavailable.
|
|
191
|
+
* Never throws.
|
|
192
|
+
*/
|
|
193
|
+
export function queryProject(): string | null {
|
|
194
|
+
return queryArtifact('PROJECT.md');
|
|
195
|
+
}
|
|
@@ -15,6 +15,7 @@ import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
|
|
|
15
15
|
import {
|
|
16
16
|
getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
|
|
17
17
|
aggregateByModel, formatCost, formatTokenCount, formatCostProjection,
|
|
18
|
+
type UnitMetrics,
|
|
18
19
|
} from "./metrics.js";
|
|
19
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
20
21
|
import { getActiveWorktreeName } from "./worktree-command.js";
|
|
@@ -39,6 +40,9 @@ function unitLabel(type: string): string {
|
|
|
39
40
|
case "execute-task": return "Execute";
|
|
40
41
|
case "complete-slice": return "Complete";
|
|
41
42
|
case "reassess-roadmap": return "Reassess";
|
|
43
|
+
case "triage-captures": return "Triage";
|
|
44
|
+
case "quick-task": return "Quick Task";
|
|
45
|
+
case "replan-slice": return "Replan";
|
|
42
46
|
default: return type;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
@@ -345,6 +349,13 @@ export class GSDDashboardOverlay {
|
|
|
345
349
|
lines.push(blank());
|
|
346
350
|
}
|
|
347
351
|
|
|
352
|
+
// Pending captures badge — only shown when captures are waiting for triage
|
|
353
|
+
if (this.dashData.pendingCaptureCount > 0) {
|
|
354
|
+
const count = this.dashData.pendingCaptureCount;
|
|
355
|
+
lines.push(row(th.fg("warning", `📌 ${count} pending capture${count === 1 ? "" : "s"} awaiting triage`)));
|
|
356
|
+
lines.push(blank());
|
|
357
|
+
}
|
|
358
|
+
|
|
348
359
|
if (this.loading) {
|
|
349
360
|
lines.push(centered(th.fg("dim", "Loading dashboard…")));
|
|
350
361
|
return lines;
|
|
@@ -403,11 +414,33 @@ export class GSDDashboardOverlay {
|
|
|
403
414
|
lines.push(row(th.fg("text", th.bold("Completed"))));
|
|
404
415
|
lines.push(blank());
|
|
405
416
|
|
|
417
|
+
// Build ledger lookup for budget indicators (last entry wins for retries)
|
|
418
|
+
const ledgerLookup = new Map<string, UnitMetrics>();
|
|
419
|
+
const currentLedger = getLedger();
|
|
420
|
+
if (currentLedger) {
|
|
421
|
+
for (const lu of currentLedger.units) {
|
|
422
|
+
ledgerLookup.set(`${lu.type}:${lu.id}`, lu);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
406
426
|
const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
|
|
407
427
|
for (const u of recent) {
|
|
408
428
|
const left = ` ${th.fg("success", "✓")} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
|
|
429
|
+
|
|
430
|
+
// Budget indicators from ledger
|
|
431
|
+
const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`);
|
|
432
|
+
let budgetMarkers = "";
|
|
433
|
+
if (ledgerEntry) {
|
|
434
|
+
if (ledgerEntry.truncationSections && ledgerEntry.truncationSections > 0) {
|
|
435
|
+
budgetMarkers += th.fg("warning", ` ▼${ledgerEntry.truncationSections}`);
|
|
436
|
+
}
|
|
437
|
+
if (ledgerEntry.continueHereFired === true) {
|
|
438
|
+
budgetMarkers += th.fg("error", " → wrap-up");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
409
442
|
const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
|
|
410
|
-
lines.push(row(joinColumns(left
|
|
443
|
+
lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth)));
|
|
411
444
|
}
|
|
412
445
|
|
|
413
446
|
if (this.dashData.completedUnits.length > 10) {
|
|
@@ -438,6 +471,18 @@ export class GSDDashboardOverlay {
|
|
|
438
471
|
`${th.fg("dim", "cache-w:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheWrite))}`,
|
|
439
472
|
], contentWidth, " ")));
|
|
440
473
|
|
|
474
|
+
// Budget aggregate line — only when data exists
|
|
475
|
+
if (totals.totalTruncationSections > 0 || totals.continueHereFiredCount > 0) {
|
|
476
|
+
const budgetParts: string[] = [];
|
|
477
|
+
if (totals.totalTruncationSections > 0) {
|
|
478
|
+
budgetParts.push(th.fg("warning", `${totals.totalTruncationSections} sections truncated`));
|
|
479
|
+
}
|
|
480
|
+
if (totals.continueHereFiredCount > 0) {
|
|
481
|
+
budgetParts.push(th.fg("error", `${totals.continueHereFiredCount} continue-here fired`));
|
|
482
|
+
}
|
|
483
|
+
lines.push(row(budgetParts.join(` ${th.fg("dim", "·")} `)));
|
|
484
|
+
}
|
|
485
|
+
|
|
441
486
|
const phases = aggregateByPhase(ledger.units);
|
|
442
487
|
if (phases.length > 0) {
|
|
443
488
|
lines.push(blank());
|
|
@@ -482,14 +527,17 @@ export class GSDDashboardOverlay {
|
|
|
482
527
|
}
|
|
483
528
|
|
|
484
529
|
const models = aggregateByModel(ledger.units);
|
|
485
|
-
if (models.length
|
|
530
|
+
if (models.length >= 1) {
|
|
486
531
|
lines.push(blank());
|
|
487
532
|
lines.push(row(th.fg("dim", "By Model")));
|
|
488
533
|
for (const m of models) {
|
|
489
534
|
const pct = totals.cost > 0 ? Math.round((m.cost / totals.cost) * 100) : 0;
|
|
490
535
|
const modelName = truncateToWidth(m.model, 38);
|
|
536
|
+
const ctxWindow = m.contextWindowTokens !== undefined
|
|
537
|
+
? th.fg("dim", ` [${formatTokenCount(m.contextWindowTokens)}]`)
|
|
538
|
+
: "";
|
|
491
539
|
const left = ` ${th.fg("text", modelName.padEnd(38))}${th.fg("warning", formatCost(m.cost).padStart(8))}`;
|
|
492
|
-
const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`);
|
|
540
|
+
const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`) + ctxWindow;
|
|
493
541
|
lines.push(row(joinColumns(left, right, contentWidth)));
|
|
494
542
|
}
|
|
495
543
|
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// GSD DB Writer — Markdown generators + DB-first write helpers
|
|
2
|
+
//
|
|
3
|
+
// The missing DB→markdown direction. S03 established markdown→DB (md-importer.ts).
|
|
4
|
+
// This module generates DECISIONS.md and REQUIREMENTS.md from DB state,
|
|
5
|
+
// computes next decision IDs, and provides write helpers that upsert to DB
|
|
6
|
+
// then regenerate the corresponding markdown file.
|
|
7
|
+
//
|
|
8
|
+
// Critical invariant: generated markdown must round-trip through
|
|
9
|
+
// parseDecisionsTable() and parseRequirementsSections() with field fidelity.
|
|
10
|
+
|
|
11
|
+
import { join, resolve } from 'node:path';
|
|
12
|
+
import type { Decision, Requirement } from './types.js';
|
|
13
|
+
import { resolveGsdRootFile } from './paths.js';
|
|
14
|
+
import { saveFile } from './files.js';
|
|
15
|
+
|
|
16
|
+
// ─── Markdown Generators ──────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate full DECISIONS.md content from an array of Decision objects.
|
|
20
|
+
* Produces the canonical format: H1 header, HTML comment block, table header,
|
|
21
|
+
* separator, and one data row per decision.
|
|
22
|
+
*
|
|
23
|
+
* Column order: #, When, Scope, Decision, Choice, Rationale, Revisable?
|
|
24
|
+
*/
|
|
25
|
+
export function generateDecisionsMd(decisions: Decision[]): string {
|
|
26
|
+
const lines: string[] = [];
|
|
27
|
+
|
|
28
|
+
lines.push('# Decisions Register');
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push('<!-- Append-only. Never edit or remove existing rows.');
|
|
31
|
+
lines.push(' To reverse a decision, add a new row that supersedes it.');
|
|
32
|
+
lines.push(' Read this file at the start of any planning or research phase. -->');
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push('| # | When | Scope | Decision | Choice | Rationale | Revisable? |');
|
|
35
|
+
lines.push('|---|------|-------|----------|--------|-----------|------------|');
|
|
36
|
+
|
|
37
|
+
for (const d of decisions) {
|
|
38
|
+
// Escape pipe characters within cell values to preserve table structure
|
|
39
|
+
const cells = [
|
|
40
|
+
d.id,
|
|
41
|
+
d.when_context,
|
|
42
|
+
d.scope,
|
|
43
|
+
d.decision,
|
|
44
|
+
d.choice,
|
|
45
|
+
d.rationale,
|
|
46
|
+
d.revisable,
|
|
47
|
+
].map(cell => (cell ?? '').replace(/\|/g, '\\|'));
|
|
48
|
+
|
|
49
|
+
lines.push(`| ${cells.join(' | ')} |`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return lines.join('\n') + '\n';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Requirements Markdown Generator ──────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/** Status values that map to specific sections, in display order. */
|
|
58
|
+
const STATUS_SECTION_MAP: Array<{ status: string; heading: string }> = [
|
|
59
|
+
{ status: 'active', heading: 'Active' },
|
|
60
|
+
{ status: 'validated', heading: 'Validated' },
|
|
61
|
+
{ status: 'deferred', heading: 'Deferred' },
|
|
62
|
+
{ status: 'out-of-scope', heading: 'Out of Scope' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate full REQUIREMENTS.md content from an array of Requirement objects.
|
|
67
|
+
* Groups requirements by status into sections (## Active, ## Validated, etc.),
|
|
68
|
+
* each containing ### RXXX — Description headings with bullet fields.
|
|
69
|
+
* Only emits sections that have content. Appends Traceability table and
|
|
70
|
+
* Coverage Summary at the bottom.
|
|
71
|
+
*/
|
|
72
|
+
export function generateRequirementsMd(requirements: Requirement[]): string {
|
|
73
|
+
const lines: string[] = [];
|
|
74
|
+
|
|
75
|
+
lines.push('# Requirements');
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push('This file is the explicit capability and coverage contract for the project.');
|
|
78
|
+
lines.push('');
|
|
79
|
+
|
|
80
|
+
// Group by status
|
|
81
|
+
const byStatus = new Map<string, Requirement[]>();
|
|
82
|
+
for (const r of requirements) {
|
|
83
|
+
const status = (r.status || 'active').toLowerCase();
|
|
84
|
+
if (!byStatus.has(status)) byStatus.set(status, []);
|
|
85
|
+
byStatus.get(status)!.push(r);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Emit sections in canonical order
|
|
89
|
+
for (const { status, heading } of STATUS_SECTION_MAP) {
|
|
90
|
+
const reqs = byStatus.get(status);
|
|
91
|
+
if (!reqs || reqs.length === 0) continue;
|
|
92
|
+
|
|
93
|
+
lines.push(`## ${heading}`);
|
|
94
|
+
lines.push('');
|
|
95
|
+
|
|
96
|
+
for (const r of reqs) {
|
|
97
|
+
lines.push(`### ${r.id} — ${r.description || 'Untitled'}`);
|
|
98
|
+
|
|
99
|
+
// Emit bullet fields — only those with content
|
|
100
|
+
if (r.class) lines.push(`- Class: ${r.class}`);
|
|
101
|
+
if (r.status) lines.push(`- Status: ${r.status}`);
|
|
102
|
+
if (r.description) lines.push(`- Description: ${r.description}`);
|
|
103
|
+
if (r.why) lines.push(`- Why it matters: ${r.why}`);
|
|
104
|
+
if (r.source) lines.push(`- Source: ${r.source}`);
|
|
105
|
+
if (r.primary_owner) lines.push(`- Primary owning slice: ${r.primary_owner}`);
|
|
106
|
+
if (r.supporting_slices) lines.push(`- Supporting slices: ${r.supporting_slices}`);
|
|
107
|
+
if (r.validation) lines.push(`- Validation: ${r.validation}`);
|
|
108
|
+
if (r.notes) lines.push(`- Notes: ${r.notes}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Traceability table
|
|
114
|
+
lines.push('## Traceability');
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push('| ID | Class | Status | Primary owner | Supporting | Proof |');
|
|
117
|
+
lines.push('|---|---|---|---|---|---|');
|
|
118
|
+
|
|
119
|
+
for (const r of requirements) {
|
|
120
|
+
const proof = r.validation || 'unmapped';
|
|
121
|
+
lines.push(
|
|
122
|
+
`| ${r.id} | ${r.class || ''} | ${r.status || ''} | ${r.primary_owner || 'none'} | ${r.supporting_slices || 'none'} | ${proof} |`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
lines.push('');
|
|
127
|
+
|
|
128
|
+
// Coverage Summary
|
|
129
|
+
const activeCount = byStatus.get('active')?.length ?? 0;
|
|
130
|
+
const validatedReqs = byStatus.get('validated') ?? [];
|
|
131
|
+
const validatedIds = validatedReqs.map(r => r.id).join(', ');
|
|
132
|
+
|
|
133
|
+
lines.push('## Coverage Summary');
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push(`- Active requirements: ${activeCount}`);
|
|
136
|
+
lines.push(`- Mapped to slices: ${activeCount}`);
|
|
137
|
+
lines.push(`- Validated: ${validatedReqs.length}${validatedIds ? ` (${validatedIds})` : ''}`);
|
|
138
|
+
lines.push(`- Unmapped active requirements: 0`);
|
|
139
|
+
|
|
140
|
+
return lines.join('\n') + '\n';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Next Decision ID ─────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compute the next decision ID from the current DB state.
|
|
147
|
+
* Queries MAX(CAST(SUBSTR(id, 2) AS INTEGER)) from decisions table.
|
|
148
|
+
* Returns D001 if no decisions exist. Zero-pads to 3 digits.
|
|
149
|
+
*/
|
|
150
|
+
export async function nextDecisionId(): Promise<string> {
|
|
151
|
+
try {
|
|
152
|
+
const db = await import('./gsd-db.js');
|
|
153
|
+
const adapter = db._getAdapter();
|
|
154
|
+
if (!adapter) return 'D001';
|
|
155
|
+
|
|
156
|
+
const row = adapter
|
|
157
|
+
.prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM decisions')
|
|
158
|
+
.get();
|
|
159
|
+
|
|
160
|
+
const maxNum = row ? (row['max_num'] as number | null) : null;
|
|
161
|
+
if (maxNum == null || isNaN(maxNum)) return 'D001';
|
|
162
|
+
|
|
163
|
+
const next = maxNum + 1;
|
|
164
|
+
return `D${String(next).padStart(3, '0')}`;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
process.stderr.write(`gsd-db: nextDecisionId failed: ${(err as Error).message}\n`);
|
|
167
|
+
return 'D001';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Save Decision to DB + Regenerate Markdown ────────────────────────────
|
|
172
|
+
|
|
173
|
+
export interface SaveDecisionFields {
|
|
174
|
+
scope: string;
|
|
175
|
+
decision: string;
|
|
176
|
+
choice: string;
|
|
177
|
+
rationale: string;
|
|
178
|
+
revisable?: string;
|
|
179
|
+
when_context?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Save a new decision to DB and regenerate DECISIONS.md.
|
|
184
|
+
* Auto-assigns the next ID via nextDecisionId().
|
|
185
|
+
* Returns the assigned ID.
|
|
186
|
+
*/
|
|
187
|
+
export async function saveDecisionToDb(
|
|
188
|
+
fields: SaveDecisionFields,
|
|
189
|
+
basePath: string,
|
|
190
|
+
): Promise<{ id: string }> {
|
|
191
|
+
try {
|
|
192
|
+
const db = await import('./gsd-db.js');
|
|
193
|
+
|
|
194
|
+
const id = await nextDecisionId();
|
|
195
|
+
|
|
196
|
+
db.upsertDecision({
|
|
197
|
+
id,
|
|
198
|
+
when_context: fields.when_context ?? '',
|
|
199
|
+
scope: fields.scope,
|
|
200
|
+
decision: fields.decision,
|
|
201
|
+
choice: fields.choice,
|
|
202
|
+
rationale: fields.rationale,
|
|
203
|
+
revisable: fields.revisable ?? 'Yes',
|
|
204
|
+
superseded_by: null,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Fetch all decisions (including superseded for the full register)
|
|
208
|
+
const adapter = db._getAdapter();
|
|
209
|
+
let allDecisions: Decision[] = [];
|
|
210
|
+
if (adapter) {
|
|
211
|
+
const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
|
|
212
|
+
allDecisions = rows.map(row => ({
|
|
213
|
+
seq: row['seq'] as number,
|
|
214
|
+
id: row['id'] as string,
|
|
215
|
+
when_context: row['when_context'] as string,
|
|
216
|
+
scope: row['scope'] as string,
|
|
217
|
+
decision: row['decision'] as string,
|
|
218
|
+
choice: row['choice'] as string,
|
|
219
|
+
rationale: row['rationale'] as string,
|
|
220
|
+
revisable: row['revisable'] as string,
|
|
221
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const md = generateDecisionsMd(allDecisions);
|
|
226
|
+
const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
|
|
227
|
+
await saveFile(filePath, md);
|
|
228
|
+
|
|
229
|
+
return { id };
|
|
230
|
+
} catch (err) {
|
|
231
|
+
process.stderr.write(`gsd-db: saveDecisionToDb failed: ${(err as Error).message}\n`);
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Update a requirement in DB and regenerate REQUIREMENTS.md.
|
|
240
|
+
* Fetches existing requirement, merges updates, upserts, then regenerates.
|
|
241
|
+
*/
|
|
242
|
+
export async function updateRequirementInDb(
|
|
243
|
+
id: string,
|
|
244
|
+
updates: Partial<Requirement>,
|
|
245
|
+
basePath: string,
|
|
246
|
+
): Promise<void> {
|
|
247
|
+
try {
|
|
248
|
+
const db = await import('./gsd-db.js');
|
|
249
|
+
|
|
250
|
+
const existing = db.getRequirementById(id);
|
|
251
|
+
if (!existing) {
|
|
252
|
+
throw new Error(`Requirement ${id} not found`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Merge updates into existing
|
|
256
|
+
const merged: Requirement = {
|
|
257
|
+
...existing,
|
|
258
|
+
...updates,
|
|
259
|
+
id: existing.id, // ID cannot be changed
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
db.upsertRequirement(merged);
|
|
263
|
+
|
|
264
|
+
// Fetch ALL requirements (including superseded) for full file regeneration
|
|
265
|
+
const adapter = db._getAdapter();
|
|
266
|
+
let allRequirements: Requirement[] = [];
|
|
267
|
+
if (adapter) {
|
|
268
|
+
const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
|
|
269
|
+
allRequirements = rows.map(row => ({
|
|
270
|
+
id: row['id'] as string,
|
|
271
|
+
class: row['class'] as string,
|
|
272
|
+
status: row['status'] as string,
|
|
273
|
+
description: row['description'] as string,
|
|
274
|
+
why: row['why'] as string,
|
|
275
|
+
source: row['source'] as string,
|
|
276
|
+
primary_owner: row['primary_owner'] as string,
|
|
277
|
+
supporting_slices: row['supporting_slices'] as string,
|
|
278
|
+
validation: row['validation'] as string,
|
|
279
|
+
notes: row['notes'] as string,
|
|
280
|
+
full_content: row['full_content'] as string,
|
|
281
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Filter to non-superseded for the markdown file
|
|
286
|
+
// (superseded requirements don't appear in section headings)
|
|
287
|
+
const nonSuperseded = allRequirements.filter(r => r.superseded_by == null);
|
|
288
|
+
|
|
289
|
+
const md = generateRequirementsMd(nonSuperseded);
|
|
290
|
+
const filePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
291
|
+
await saveFile(filePath, md);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
process.stderr.write(`gsd-db: updateRequirementInDb failed: ${(err as Error).message}\n`);
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Save Artifact to DB + Disk ───────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
export interface SaveArtifactOpts {
|
|
301
|
+
path: string;
|
|
302
|
+
artifact_type: string;
|
|
303
|
+
content: string;
|
|
304
|
+
milestone_id?: string;
|
|
305
|
+
slice_id?: string;
|
|
306
|
+
task_id?: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Save an artifact to DB and write the corresponding markdown file to disk.
|
|
311
|
+
* The path is relative to .gsd/ (e.g. "milestones/M001/slices/S06/tasks/T01-SUMMARY.md").
|
|
312
|
+
* The full file path is computed as basePath + '.gsd/' + path.
|
|
313
|
+
*/
|
|
314
|
+
export async function saveArtifactToDb(
|
|
315
|
+
opts: SaveArtifactOpts,
|
|
316
|
+
basePath: string,
|
|
317
|
+
): Promise<void> {
|
|
318
|
+
try {
|
|
319
|
+
const db = await import('./gsd-db.js');
|
|
320
|
+
|
|
321
|
+
db.insertArtifact({
|
|
322
|
+
path: opts.path,
|
|
323
|
+
artifact_type: opts.artifact_type,
|
|
324
|
+
milestone_id: opts.milestone_id ?? null,
|
|
325
|
+
slice_id: opts.slice_id ?? null,
|
|
326
|
+
task_id: opts.task_id ?? null,
|
|
327
|
+
full_content: opts.content,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Write the file to disk (guard against path traversal)
|
|
331
|
+
const gsdDir = resolve(basePath, '.gsd');
|
|
332
|
+
const fullPath = resolve(basePath, '.gsd', opts.path);
|
|
333
|
+
if (!fullPath.startsWith(gsdDir)) {
|
|
334
|
+
throw new Error(`saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
|
|
335
|
+
}
|
|
336
|
+
await saveFile(fullPath, opts.content);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
process.stderr.write(`gsd-db: saveArtifactToDb failed: ${(err as Error).message}\n`);
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
}
|