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,526 @@
|
|
|
1
|
+
// GSD Markdown Importer
|
|
2
|
+
// Parses DECISIONS.md, REQUIREMENTS.md, and hierarchy artifacts from a .gsd/ tree,
|
|
3
|
+
// then upserts everything into the SQLite database.
|
|
4
|
+
//
|
|
5
|
+
// Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
|
|
6
|
+
|
|
7
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
8
|
+
import { join, relative } from 'node:path';
|
|
9
|
+
import type { Decision, Requirement } from './types.js';
|
|
10
|
+
import {
|
|
11
|
+
upsertDecision,
|
|
12
|
+
upsertRequirement,
|
|
13
|
+
insertArtifact,
|
|
14
|
+
openDatabase,
|
|
15
|
+
transaction,
|
|
16
|
+
_getAdapter,
|
|
17
|
+
} from './gsd-db.js';
|
|
18
|
+
import {
|
|
19
|
+
resolveGsdRootFile,
|
|
20
|
+
milestonesDir,
|
|
21
|
+
resolveTaskFiles,
|
|
22
|
+
} from './paths.js';
|
|
23
|
+
import { findMilestoneIds } from './guided-flow.js';
|
|
24
|
+
|
|
25
|
+
// ─── DECISIONS.md Parser ───────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a DECISIONS.md markdown table into Decision objects (without seq).
|
|
29
|
+
* Detects `(amends DXXX)` in the Decision column to build supersession info.
|
|
30
|
+
* Returns parsed rows with superseded_by set to null; callers handle chaining.
|
|
31
|
+
*/
|
|
32
|
+
export function parseDecisionsTable(content: string): Omit<Decision, 'seq'>[] {
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
const results: Omit<Decision, 'seq'>[] = [];
|
|
35
|
+
|
|
36
|
+
// Map from amended ID → amending ID for supersession
|
|
37
|
+
const amendsMap = new Map<string, string>();
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
// Skip non-table lines, header, and separator
|
|
41
|
+
if (!line.trim().startsWith('|')) continue;
|
|
42
|
+
const trimmed = line.trim();
|
|
43
|
+
// Skip separator rows like |---|---|...|
|
|
44
|
+
if (/^\|[\s-|]+\|$/.test(trimmed)) continue;
|
|
45
|
+
|
|
46
|
+
// Split on | and strip leading/trailing empty cells
|
|
47
|
+
const cells = trimmed.split('|').map(c => c.trim());
|
|
48
|
+
// Remove first and last empty strings from leading/trailing |
|
|
49
|
+
if (cells.length > 0 && cells[0] === '') cells.shift();
|
|
50
|
+
if (cells.length > 0 && cells[cells.length - 1] === '') cells.pop();
|
|
51
|
+
|
|
52
|
+
if (cells.length < 7) continue;
|
|
53
|
+
|
|
54
|
+
const id = cells[0].trim();
|
|
55
|
+
// Skip header row
|
|
56
|
+
if (id === '#' || id.toLowerCase() === 'id') continue;
|
|
57
|
+
// Must look like a decision ID (D followed by digits)
|
|
58
|
+
if (!/^D\d+/.test(id)) continue;
|
|
59
|
+
|
|
60
|
+
const when_context = cells[1].trim();
|
|
61
|
+
const scope = cells[2].trim();
|
|
62
|
+
const decisionText = cells[3].trim();
|
|
63
|
+
const choice = cells[4].trim();
|
|
64
|
+
const rationale = cells[5].trim();
|
|
65
|
+
const revisable = cells[6].trim();
|
|
66
|
+
|
|
67
|
+
// Detect (amends DXXX) in the Decision column
|
|
68
|
+
const amendsMatch = decisionText.match(/\(amends\s+(D\d+)\)/i);
|
|
69
|
+
if (amendsMatch) {
|
|
70
|
+
amendsMap.set(amendsMatch[1], id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
results.push({
|
|
74
|
+
id,
|
|
75
|
+
when_context,
|
|
76
|
+
scope,
|
|
77
|
+
decision: decisionText,
|
|
78
|
+
choice,
|
|
79
|
+
rationale,
|
|
80
|
+
revisable,
|
|
81
|
+
superseded_by: null,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Apply supersession: if D010 amends D001, set D001.superseded_by = D010
|
|
86
|
+
// Handle chains: if D020 amends D010 and D010 amends D001,
|
|
87
|
+
// D001.superseded_by = D010, D010.superseded_by = D020
|
|
88
|
+
for (const row of results) {
|
|
89
|
+
if (amendsMap.has(row.id)) {
|
|
90
|
+
row.superseded_by = amendsMap.get(row.id)!;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── REQUIREMENTS.md Parser ────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
const STATUS_SECTIONS: Record<string, string> = {
|
|
100
|
+
'## active': 'active',
|
|
101
|
+
'## validated': 'validated',
|
|
102
|
+
'## deferred': 'deferred',
|
|
103
|
+
'## out of scope': 'out-of-scope',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Parse REQUIREMENTS.md into Requirement objects.
|
|
108
|
+
* Finds section headings (## Active, ## Validated, ## Deferred, ## Out of Scope),
|
|
109
|
+
* then within each section finds ### RXXX — Title blocks and extracts bullet fields.
|
|
110
|
+
*/
|
|
111
|
+
export function parseRequirementsSections(content: string): Requirement[] {
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
const results: Requirement[] = [];
|
|
114
|
+
|
|
115
|
+
let currentSectionStatus: string | null = null;
|
|
116
|
+
let currentReq: Partial<Requirement> | null = null;
|
|
117
|
+
let currentFullContentLines: string[] = [];
|
|
118
|
+
|
|
119
|
+
function flushReq(): void {
|
|
120
|
+
if (currentReq && currentReq.id) {
|
|
121
|
+
currentReq.full_content = currentFullContentLines.join('\n').trim();
|
|
122
|
+
results.push({
|
|
123
|
+
id: currentReq.id!,
|
|
124
|
+
class: currentReq.class ?? '',
|
|
125
|
+
status: currentReq.status ?? currentSectionStatus ?? '',
|
|
126
|
+
description: currentReq.description ?? '',
|
|
127
|
+
why: currentReq.why ?? '',
|
|
128
|
+
source: currentReq.source ?? '',
|
|
129
|
+
primary_owner: currentReq.primary_owner ?? '',
|
|
130
|
+
supporting_slices: currentReq.supporting_slices ?? '',
|
|
131
|
+
validation: currentReq.validation ?? '',
|
|
132
|
+
notes: currentReq.notes ?? '',
|
|
133
|
+
full_content: currentReq.full_content ?? '',
|
|
134
|
+
superseded_by: currentReq.superseded_by ?? null,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
currentReq = null;
|
|
138
|
+
currentFullContentLines = [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < lines.length; i++) {
|
|
142
|
+
const line = lines[i];
|
|
143
|
+
const lineLower = line.trim().toLowerCase();
|
|
144
|
+
|
|
145
|
+
// Check for section heading (## Active, ## Validated, etc.)
|
|
146
|
+
if (lineLower.startsWith('## ')) {
|
|
147
|
+
flushReq();
|
|
148
|
+
const matchedSection = Object.entries(STATUS_SECTIONS).find(
|
|
149
|
+
([prefix]) => lineLower === prefix || lineLower.startsWith(prefix + ' ')
|
|
150
|
+
);
|
|
151
|
+
if (matchedSection) {
|
|
152
|
+
currentSectionStatus = matchedSection[1];
|
|
153
|
+
} else {
|
|
154
|
+
// Sections like ## Traceability, ## Coverage Summary — stop parsing requirements
|
|
155
|
+
currentSectionStatus = null;
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check for requirement heading (### RXXX — Title)
|
|
161
|
+
const reqMatch = line.match(/^###\s+(R\d+)\s*[—–-]\s*(.+)/);
|
|
162
|
+
if (reqMatch) {
|
|
163
|
+
flushReq();
|
|
164
|
+
if (currentSectionStatus !== null) {
|
|
165
|
+
currentReq = {
|
|
166
|
+
id: reqMatch[1],
|
|
167
|
+
status: currentSectionStatus,
|
|
168
|
+
};
|
|
169
|
+
currentFullContentLines = [line];
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If we're inside a requirement block, collect content and extract bullets
|
|
175
|
+
if (currentReq && currentSectionStatus !== null) {
|
|
176
|
+
currentFullContentLines.push(line);
|
|
177
|
+
|
|
178
|
+
// Extract field bullets: "- Field: value" or "- Field name: value"
|
|
179
|
+
const bulletMatch = line.match(/^-\s+(.+?):\s+(.*)/);
|
|
180
|
+
if (bulletMatch) {
|
|
181
|
+
const fieldName = bulletMatch[1].trim().toLowerCase();
|
|
182
|
+
const value = bulletMatch[2].trim();
|
|
183
|
+
|
|
184
|
+
switch (fieldName) {
|
|
185
|
+
case 'class':
|
|
186
|
+
currentReq.class = value;
|
|
187
|
+
break;
|
|
188
|
+
case 'status':
|
|
189
|
+
// Bullet status takes precedence over section heading
|
|
190
|
+
currentReq.status = value;
|
|
191
|
+
break;
|
|
192
|
+
case 'description':
|
|
193
|
+
currentReq.description = value;
|
|
194
|
+
break;
|
|
195
|
+
case 'why it matters':
|
|
196
|
+
case 'why':
|
|
197
|
+
currentReq.why = value;
|
|
198
|
+
break;
|
|
199
|
+
case 'source':
|
|
200
|
+
currentReq.source = value;
|
|
201
|
+
break;
|
|
202
|
+
case 'primary owning slice':
|
|
203
|
+
case 'primary owner':
|
|
204
|
+
case 'primary_owner':
|
|
205
|
+
currentReq.primary_owner = value;
|
|
206
|
+
break;
|
|
207
|
+
case 'supporting slices':
|
|
208
|
+
case 'supporting_slices':
|
|
209
|
+
currentReq.supporting_slices = value;
|
|
210
|
+
break;
|
|
211
|
+
case 'validation':
|
|
212
|
+
case 'validated by':
|
|
213
|
+
currentReq.validation = value;
|
|
214
|
+
break;
|
|
215
|
+
case 'notes':
|
|
216
|
+
currentReq.notes = value;
|
|
217
|
+
break;
|
|
218
|
+
case 'proof':
|
|
219
|
+
// In validated section, "Proof:" serves as notes
|
|
220
|
+
currentReq.notes = value;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
flushReq();
|
|
228
|
+
|
|
229
|
+
// Deduplicate by ID: if a requirement appears in both Active and Validated sections,
|
|
230
|
+
// keep the fuller entry (typically Active) and merge in any non-empty fields from later entries.
|
|
231
|
+
const deduped = new Map<string, Requirement>();
|
|
232
|
+
for (const req of results) {
|
|
233
|
+
const existing = deduped.get(req.id);
|
|
234
|
+
if (!existing) {
|
|
235
|
+
deduped.set(req.id, req);
|
|
236
|
+
} else {
|
|
237
|
+
// Merge: non-empty fields from later entry override empty fields in existing
|
|
238
|
+
for (const key of Object.keys(req) as (keyof Requirement)[]) {
|
|
239
|
+
if (key === 'id' || key === 'superseded_by') continue;
|
|
240
|
+
const val = req[key];
|
|
241
|
+
if (val && val !== '' && (!existing[key] || existing[key] === '')) {
|
|
242
|
+
(existing as unknown as Record<string, unknown>)[key] = val;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return Array.from(deduped.values());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── Import Functions ──────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Import decisions from DECISIONS.md into the database.
|
|
255
|
+
* Handles supersession chains.
|
|
256
|
+
*/
|
|
257
|
+
function importDecisions(gsdDir: string): number {
|
|
258
|
+
const filePath = resolveGsdRootFile(gsdDir, 'DECISIONS');
|
|
259
|
+
if (!existsSync(filePath)) return 0;
|
|
260
|
+
|
|
261
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
262
|
+
const decisions = parseDecisionsTable(content);
|
|
263
|
+
|
|
264
|
+
for (const d of decisions) {
|
|
265
|
+
upsertDecision(d);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return decisions.length;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Import requirements from REQUIREMENTS.md into the database.
|
|
273
|
+
*/
|
|
274
|
+
function importRequirements(gsdDir: string): number {
|
|
275
|
+
const filePath = resolveGsdRootFile(gsdDir, 'REQUIREMENTS');
|
|
276
|
+
if (!existsSync(filePath)) return 0;
|
|
277
|
+
|
|
278
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
279
|
+
const requirements = parseRequirementsSections(content);
|
|
280
|
+
|
|
281
|
+
for (const r of requirements) {
|
|
282
|
+
upsertRequirement(r);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return requirements.length;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── Hierarchy Artifact Walker ─────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
/** Artifact suffixes to look for at each hierarchy level */
|
|
291
|
+
const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT'];
|
|
292
|
+
const SLICE_SUFFIXES = ['PLAN', 'SUMMARY', 'RESEARCH', 'CONTEXT', 'ASSESSMENT', 'UAT'];
|
|
293
|
+
const TASK_SUFFIXES = ['PLAN', 'SUMMARY', 'CONTINUE', 'CONTEXT', 'RESEARCH'];
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Import hierarchy artifacts (roadmaps, plans, summaries, etc.) from the .gsd/ tree.
|
|
297
|
+
* Walks milestones → slices → tasks directories.
|
|
298
|
+
*/
|
|
299
|
+
function importHierarchyArtifacts(gsdDir: string): number {
|
|
300
|
+
let count = 0;
|
|
301
|
+
const gsdPath = join(gsdDir, '.gsd');
|
|
302
|
+
|
|
303
|
+
// Root-level artifacts: PROJECT.md, QUEUE.md
|
|
304
|
+
const rootFiles = ['PROJECT.md', 'QUEUE.md', 'SECRETS-MANIFEST.md'];
|
|
305
|
+
for (const fileName of rootFiles) {
|
|
306
|
+
const filePath = join(gsdPath, fileName);
|
|
307
|
+
if (existsSync(filePath)) {
|
|
308
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
309
|
+
const artifactType = fileName.replace('.md', '').replace('-', '_');
|
|
310
|
+
insertArtifact({
|
|
311
|
+
path: fileName,
|
|
312
|
+
artifact_type: artifactType,
|
|
313
|
+
milestone_id: null,
|
|
314
|
+
slice_id: null,
|
|
315
|
+
task_id: null,
|
|
316
|
+
full_content: content,
|
|
317
|
+
});
|
|
318
|
+
count++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Walk milestones
|
|
323
|
+
const milestoneIds = findMilestoneIds(gsdDir);
|
|
324
|
+
const msDir = milestonesDir(gsdDir);
|
|
325
|
+
|
|
326
|
+
for (const milestoneId of milestoneIds) {
|
|
327
|
+
// Find the actual milestone directory name (handles legacy naming)
|
|
328
|
+
const milestoneDirName = findDirByPrefix(msDir, milestoneId);
|
|
329
|
+
if (!milestoneDirName) continue;
|
|
330
|
+
const milestoneFullPath = join(msDir, milestoneDirName);
|
|
331
|
+
|
|
332
|
+
// Milestone-level files
|
|
333
|
+
count += importFilesAtLevel(
|
|
334
|
+
milestoneFullPath,
|
|
335
|
+
milestoneId,
|
|
336
|
+
MILESTONE_SUFFIXES,
|
|
337
|
+
`milestones/${milestoneDirName}`,
|
|
338
|
+
milestoneId,
|
|
339
|
+
null,
|
|
340
|
+
null,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Walk slices
|
|
344
|
+
const slicesDir = join(milestoneFullPath, 'slices');
|
|
345
|
+
if (!existsSync(slicesDir)) continue;
|
|
346
|
+
|
|
347
|
+
const sliceDirs = readdirSync(slicesDir, { withFileTypes: true })
|
|
348
|
+
.filter(d => d.isDirectory() && /^S\d+/.test(d.name))
|
|
349
|
+
.map(d => d.name)
|
|
350
|
+
.sort();
|
|
351
|
+
|
|
352
|
+
for (const sliceDirName of sliceDirs) {
|
|
353
|
+
const sliceId = sliceDirName.match(/^(S\d+)/)?.[1] ?? sliceDirName;
|
|
354
|
+
const sliceFullPath = join(slicesDir, sliceDirName);
|
|
355
|
+
|
|
356
|
+
// Slice-level files
|
|
357
|
+
count += importFilesAtLevel(
|
|
358
|
+
sliceFullPath,
|
|
359
|
+
sliceId,
|
|
360
|
+
SLICE_SUFFIXES,
|
|
361
|
+
`milestones/${milestoneDirName}/slices/${sliceDirName}`,
|
|
362
|
+
milestoneId,
|
|
363
|
+
sliceId,
|
|
364
|
+
null,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Walk tasks
|
|
368
|
+
const tasksDir = join(sliceFullPath, 'tasks');
|
|
369
|
+
if (!existsSync(tasksDir)) continue;
|
|
370
|
+
|
|
371
|
+
for (const suffix of TASK_SUFFIXES) {
|
|
372
|
+
const taskFiles = resolveTaskFiles(tasksDir, suffix);
|
|
373
|
+
for (const taskFileName of taskFiles) {
|
|
374
|
+
const taskId = taskFileName.match(/^(T\d+)/)?.[1] ?? null;
|
|
375
|
+
const taskFilePath = join(tasksDir, taskFileName);
|
|
376
|
+
if (!existsSync(taskFilePath)) continue;
|
|
377
|
+
|
|
378
|
+
const content = readFileSync(taskFilePath, 'utf-8');
|
|
379
|
+
const relPath = `milestones/${milestoneDirName}/slices/${sliceDirName}/tasks/${taskFileName}`;
|
|
380
|
+
|
|
381
|
+
insertArtifact({
|
|
382
|
+
path: relPath,
|
|
383
|
+
artifact_type: suffix,
|
|
384
|
+
milestone_id: milestoneId,
|
|
385
|
+
slice_id: sliceId,
|
|
386
|
+
task_id: taskId,
|
|
387
|
+
full_content: content,
|
|
388
|
+
});
|
|
389
|
+
count++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return count;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Import files at a specific hierarchy level (milestone or slice).
|
|
400
|
+
*/
|
|
401
|
+
function importFilesAtLevel(
|
|
402
|
+
dirPath: string,
|
|
403
|
+
idPrefix: string,
|
|
404
|
+
suffixes: string[],
|
|
405
|
+
relativeBase: string,
|
|
406
|
+
milestoneId: string,
|
|
407
|
+
sliceId: string | null,
|
|
408
|
+
taskId: string | null,
|
|
409
|
+
): number {
|
|
410
|
+
let count = 0;
|
|
411
|
+
|
|
412
|
+
for (const suffix of suffixes) {
|
|
413
|
+
// Try ID-SUFFIX.md pattern (e.g., M001-ROADMAP.md, S01-PLAN.md)
|
|
414
|
+
const fileName = findFileByPrefixAndSuffix(dirPath, idPrefix, suffix);
|
|
415
|
+
if (!fileName) continue;
|
|
416
|
+
|
|
417
|
+
const filePath = join(dirPath, fileName);
|
|
418
|
+
if (!existsSync(filePath)) continue;
|
|
419
|
+
|
|
420
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
421
|
+
const relPath = `${relativeBase}/${fileName}`;
|
|
422
|
+
|
|
423
|
+
insertArtifact({
|
|
424
|
+
path: relPath,
|
|
425
|
+
artifact_type: suffix,
|
|
426
|
+
milestone_id: milestoneId,
|
|
427
|
+
slice_id: sliceId,
|
|
428
|
+
task_id: taskId,
|
|
429
|
+
full_content: content,
|
|
430
|
+
});
|
|
431
|
+
count++;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return count;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Find a directory by ID prefix within a parent directory.
|
|
439
|
+
*/
|
|
440
|
+
function findDirByPrefix(parentDir: string, idPrefix: string): string | null {
|
|
441
|
+
if (!existsSync(parentDir)) return null;
|
|
442
|
+
try {
|
|
443
|
+
const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
444
|
+
// Exact match first
|
|
445
|
+
const exact = entries.find(e => e.isDirectory() && e.name === idPrefix);
|
|
446
|
+
if (exact) return exact.name;
|
|
447
|
+
// Prefix match for legacy
|
|
448
|
+
const prefixed = entries.find(e => e.isDirectory() && e.name.startsWith(idPrefix + '-'));
|
|
449
|
+
return prefixed ? prefixed.name : null;
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Find a file by ID prefix and suffix within a directory.
|
|
457
|
+
* Matches ID-SUFFIX.md or ID-*-SUFFIX.md patterns.
|
|
458
|
+
*/
|
|
459
|
+
function findFileByPrefixAndSuffix(dir: string, idPrefix: string, suffix: string): string | null {
|
|
460
|
+
if (!existsSync(dir)) return null;
|
|
461
|
+
try {
|
|
462
|
+
const entries = readdirSync(dir);
|
|
463
|
+
// Direct: ID-SUFFIX.md
|
|
464
|
+
const target = `${idPrefix}-${suffix}.md`.toUpperCase();
|
|
465
|
+
const direct = entries.find(e => e.toUpperCase() === target);
|
|
466
|
+
if (direct) return direct;
|
|
467
|
+
// Legacy: ID-DESCRIPTOR-SUFFIX.md
|
|
468
|
+
const pattern = new RegExp(`^${idPrefix}-.*-${suffix}\\.md$`, 'i');
|
|
469
|
+
const match = entries.find(e => pattern.test(e));
|
|
470
|
+
return match ?? null;
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ─── Orchestrator ──────────────────────────────────────────────────────────
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Import all markdown artifacts from a .gsd/ directory into the database.
|
|
480
|
+
* Opens the DB if not already open. Wraps all imports in a single transaction.
|
|
481
|
+
* Returns counts of imported items for logging.
|
|
482
|
+
*
|
|
483
|
+
* Missing files are skipped gracefully — no errors produced.
|
|
484
|
+
*/
|
|
485
|
+
export function migrateFromMarkdown(gsdDir: string): {
|
|
486
|
+
decisions: number;
|
|
487
|
+
requirements: number;
|
|
488
|
+
artifacts: number;
|
|
489
|
+
} {
|
|
490
|
+
const dbPath = join(gsdDir, '.gsd', 'gsd.db');
|
|
491
|
+
|
|
492
|
+
// Open DB if not already open
|
|
493
|
+
if (!_getAdapter()) {
|
|
494
|
+
openDatabase(dbPath);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let decisions = 0;
|
|
498
|
+
let requirements = 0;
|
|
499
|
+
let artifacts = 0;
|
|
500
|
+
|
|
501
|
+
transaction(() => {
|
|
502
|
+
try {
|
|
503
|
+
decisions = importDecisions(gsdDir);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
process.stderr.write(`gsd-migrate: skipping decisions import: ${(err as Error).message}\n`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
requirements = importRequirements(gsdDir);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
process.stderr.write(`gsd-migrate: skipping requirements import: ${(err as Error).message}\n`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
artifacts = importHierarchyArtifacts(gsdDir);
|
|
516
|
+
} catch (err) {
|
|
517
|
+
process.stderr.write(`gsd-migrate: skipping artifacts import: ${(err as Error).message}\n`);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
process.stderr.write(
|
|
522
|
+
`gsd-migrate: imported ${decisions} decisions, ${requirements} requirements, ${artifacts} artifacts\n`,
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
return { decisions, requirements, artifacts };
|
|
526
|
+
}
|
|
@@ -17,6 +17,7 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
19
19
|
import { gsdRoot } from "./paths.js";
|
|
20
|
+
import { getAndClearSkills } from "./skill-telemetry.js";
|
|
20
21
|
|
|
21
22
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -39,6 +40,22 @@ export interface UnitMetrics {
|
|
|
39
40
|
toolCalls: number;
|
|
40
41
|
assistantMessages: number;
|
|
41
42
|
userMessages: number;
|
|
43
|
+
// Budget fields (optional — absent in pre-M009 metrics data)
|
|
44
|
+
contextWindowTokens?: number;
|
|
45
|
+
truncationSections?: number;
|
|
46
|
+
continueHereFired?: boolean;
|
|
47
|
+
promptCharCount?: number;
|
|
48
|
+
baselineCharCount?: number;
|
|
49
|
+
tier?: string; // complexity tier (light/standard/heavy) if dynamic routing active
|
|
50
|
+
modelDowngraded?: boolean; // true if dynamic routing used a cheaper model
|
|
51
|
+
skills?: string[]; // skill names available/loaded during this unit (#599)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Budget state passed to snapshotUnitMetrics for persistence in the metrics ledger. */
|
|
55
|
+
export interface BudgetInfo {
|
|
56
|
+
contextWindowTokens?: number;
|
|
57
|
+
truncationSections?: number;
|
|
58
|
+
continueHereFired?: boolean;
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
export interface MetricsLedger {
|
|
@@ -104,6 +121,7 @@ export function snapshotUnitMetrics(
|
|
|
104
121
|
unitId: string,
|
|
105
122
|
startedAt: number,
|
|
106
123
|
model: string,
|
|
124
|
+
opts?: { tier?: string; modelDowngraded?: boolean; contextWindowTokens?: number; truncationSections?: number; continueHereFired?: boolean; promptCharCount?: number; baselineCharCount?: number },
|
|
107
125
|
): UnitMetrics | null {
|
|
108
126
|
if (!ledger) return null;
|
|
109
127
|
|
|
@@ -156,8 +174,21 @@ export function snapshotUnitMetrics(
|
|
|
156
174
|
toolCalls,
|
|
157
175
|
assistantMessages,
|
|
158
176
|
userMessages,
|
|
177
|
+
...(opts?.tier ? { tier: opts.tier } : {}),
|
|
178
|
+
...(opts?.modelDowngraded !== undefined ? { modelDowngraded: opts.modelDowngraded } : {}),
|
|
179
|
+
...(opts?.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {}),
|
|
180
|
+
...(opts?.truncationSections !== undefined ? { truncationSections: opts.truncationSections } : {}),
|
|
181
|
+
...(opts?.continueHereFired !== undefined ? { continueHereFired: opts.continueHereFired } : {}),
|
|
182
|
+
...(opts?.promptCharCount != null ? { promptCharCount: opts.promptCharCount } : {}),
|
|
183
|
+
...(opts?.baselineCharCount != null ? { baselineCharCount: opts.baselineCharCount } : {}),
|
|
159
184
|
};
|
|
160
185
|
|
|
186
|
+
// Auto-capture skill telemetry (#599)
|
|
187
|
+
const skills = getAndClearSkills();
|
|
188
|
+
if (skills.length > 0) {
|
|
189
|
+
unit.skills = skills;
|
|
190
|
+
}
|
|
191
|
+
|
|
161
192
|
ledger.units.push(unit);
|
|
162
193
|
saveLedger(basePath, ledger);
|
|
163
194
|
|
|
@@ -194,6 +225,7 @@ export interface ModelAggregate {
|
|
|
194
225
|
units: number;
|
|
195
226
|
tokens: TokenCounts;
|
|
196
227
|
cost: number;
|
|
228
|
+
contextWindowTokens?: number;
|
|
197
229
|
}
|
|
198
230
|
|
|
199
231
|
export interface ProjectTotals {
|
|
@@ -204,6 +236,8 @@ export interface ProjectTotals {
|
|
|
204
236
|
toolCalls: number;
|
|
205
237
|
assistantMessages: number;
|
|
206
238
|
userMessages: number;
|
|
239
|
+
totalTruncationSections: number;
|
|
240
|
+
continueHereFiredCount: number;
|
|
207
241
|
}
|
|
208
242
|
|
|
209
243
|
function emptyTokens(): TokenCounts {
|
|
@@ -269,6 +303,9 @@ export function aggregateByModel(units: UnitMetrics[]): ModelAggregate[] {
|
|
|
269
303
|
agg.units++;
|
|
270
304
|
agg.tokens = addTokens(agg.tokens, u.tokens);
|
|
271
305
|
agg.cost += u.cost;
|
|
306
|
+
if (u.contextWindowTokens !== undefined && agg.contextWindowTokens === undefined) {
|
|
307
|
+
agg.contextWindowTokens = u.contextWindowTokens;
|
|
308
|
+
}
|
|
272
309
|
}
|
|
273
310
|
return Array.from(map.values()).sort((a, b) => b.cost - a.cost);
|
|
274
311
|
}
|
|
@@ -282,6 +319,8 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
|
|
|
282
319
|
toolCalls: 0,
|
|
283
320
|
assistantMessages: 0,
|
|
284
321
|
userMessages: 0,
|
|
322
|
+
totalTruncationSections: 0,
|
|
323
|
+
continueHereFiredCount: 0,
|
|
285
324
|
};
|
|
286
325
|
for (const u of units) {
|
|
287
326
|
totals.tokens = addTokens(totals.tokens, u.tokens);
|
|
@@ -290,10 +329,55 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
|
|
|
290
329
|
totals.toolCalls += u.toolCalls;
|
|
291
330
|
totals.assistantMessages += u.assistantMessages;
|
|
292
331
|
totals.userMessages += u.userMessages;
|
|
332
|
+
totals.totalTruncationSections += u.truncationSections ?? 0;
|
|
333
|
+
if (u.continueHereFired) totals.continueHereFiredCount++;
|
|
293
334
|
}
|
|
294
335
|
return totals;
|
|
295
336
|
}
|
|
296
337
|
|
|
338
|
+
// ─── Tier Aggregation ────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
export interface TierAggregate {
|
|
341
|
+
tier: string;
|
|
342
|
+
units: number;
|
|
343
|
+
tokens: TokenCounts;
|
|
344
|
+
cost: number;
|
|
345
|
+
downgraded: number; // units that were downgraded by dynamic routing
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function aggregateByTier(units: UnitMetrics[]): TierAggregate[] {
|
|
349
|
+
const map = new Map<string, TierAggregate>();
|
|
350
|
+
for (const u of units) {
|
|
351
|
+
const tier = u.tier ?? "unknown";
|
|
352
|
+
let agg = map.get(tier);
|
|
353
|
+
if (!agg) {
|
|
354
|
+
agg = { tier, units: 0, tokens: emptyTokens(), cost: 0, downgraded: 0 };
|
|
355
|
+
map.set(tier, agg);
|
|
356
|
+
}
|
|
357
|
+
agg.units++;
|
|
358
|
+
agg.tokens = addTokens(agg.tokens, u.tokens);
|
|
359
|
+
agg.cost += u.cost;
|
|
360
|
+
if (u.modelDowngraded) agg.downgraded++;
|
|
361
|
+
}
|
|
362
|
+
const order = ["light", "standard", "heavy", "unknown"];
|
|
363
|
+
return order.map(t => map.get(t)).filter((a): a is TierAggregate => !!a);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Format a summary of savings from dynamic routing.
|
|
368
|
+
* Returns empty string if no units were downgraded.
|
|
369
|
+
*/
|
|
370
|
+
export function formatTierSavings(units: UnitMetrics[]): string {
|
|
371
|
+
const downgraded = units.filter(u => u.modelDowngraded);
|
|
372
|
+
if (downgraded.length === 0) return "";
|
|
373
|
+
|
|
374
|
+
const downgradedCost = downgraded.reduce((sum, u) => sum + u.cost, 0);
|
|
375
|
+
const totalUnits = units.filter(u => u.tier).length;
|
|
376
|
+
const pct = totalUnits > 0 ? Math.round((downgraded.length / totalUnits) * 100) : 0;
|
|
377
|
+
|
|
378
|
+
return `Dynamic routing: ${downgraded.length}/${totalUnits} units downgraded (${pct}%), cost: ${formatCost(downgradedCost)}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
297
381
|
// ─── Formatting helpers ───────────────────────────────────────────────────────
|
|
298
382
|
|
|
299
383
|
export function formatCost(cost: number): string {
|