gsd-pi 2.19.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -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,8 +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;
|
|
42
49
|
tier?: string; // complexity tier (light/standard/heavy) if dynamic routing active
|
|
43
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;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
export interface MetricsLedger {
|
|
@@ -106,7 +121,7 @@ export function snapshotUnitMetrics(
|
|
|
106
121
|
unitId: string,
|
|
107
122
|
startedAt: number,
|
|
108
123
|
model: string,
|
|
109
|
-
|
|
124
|
+
opts?: { tier?: string; modelDowngraded?: boolean; contextWindowTokens?: number; truncationSections?: number; continueHereFired?: boolean; promptCharCount?: number; baselineCharCount?: number },
|
|
110
125
|
): UnitMetrics | null {
|
|
111
126
|
if (!ledger) return null;
|
|
112
127
|
|
|
@@ -159,10 +174,21 @@ export function snapshotUnitMetrics(
|
|
|
159
174
|
toolCalls,
|
|
160
175
|
assistantMessages,
|
|
161
176
|
userMessages,
|
|
162
|
-
...(
|
|
163
|
-
...(
|
|
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 } : {}),
|
|
164
184
|
};
|
|
165
185
|
|
|
186
|
+
// Auto-capture skill telemetry (#599)
|
|
187
|
+
const skills = getAndClearSkills();
|
|
188
|
+
if (skills.length > 0) {
|
|
189
|
+
unit.skills = skills;
|
|
190
|
+
}
|
|
191
|
+
|
|
166
192
|
ledger.units.push(unit);
|
|
167
193
|
saveLedger(basePath, ledger);
|
|
168
194
|
|
|
@@ -199,6 +225,7 @@ export interface ModelAggregate {
|
|
|
199
225
|
units: number;
|
|
200
226
|
tokens: TokenCounts;
|
|
201
227
|
cost: number;
|
|
228
|
+
contextWindowTokens?: number;
|
|
202
229
|
}
|
|
203
230
|
|
|
204
231
|
export interface ProjectTotals {
|
|
@@ -209,6 +236,8 @@ export interface ProjectTotals {
|
|
|
209
236
|
toolCalls: number;
|
|
210
237
|
assistantMessages: number;
|
|
211
238
|
userMessages: number;
|
|
239
|
+
totalTruncationSections: number;
|
|
240
|
+
continueHereFiredCount: number;
|
|
212
241
|
}
|
|
213
242
|
|
|
214
243
|
function emptyTokens(): TokenCounts {
|
|
@@ -274,6 +303,9 @@ export function aggregateByModel(units: UnitMetrics[]): ModelAggregate[] {
|
|
|
274
303
|
agg.units++;
|
|
275
304
|
agg.tokens = addTokens(agg.tokens, u.tokens);
|
|
276
305
|
agg.cost += u.cost;
|
|
306
|
+
if (u.contextWindowTokens !== undefined && agg.contextWindowTokens === undefined) {
|
|
307
|
+
agg.contextWindowTokens = u.contextWindowTokens;
|
|
308
|
+
}
|
|
277
309
|
}
|
|
278
310
|
return Array.from(map.values()).sort((a, b) => b.cost - a.cost);
|
|
279
311
|
}
|
|
@@ -287,6 +319,8 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
|
|
|
287
319
|
toolCalls: 0,
|
|
288
320
|
assistantMessages: 0,
|
|
289
321
|
userMessages: 0,
|
|
322
|
+
totalTruncationSections: 0,
|
|
323
|
+
continueHereFiredCount: 0,
|
|
290
324
|
};
|
|
291
325
|
for (const u of units) {
|
|
292
326
|
totals.tokens = addTokens(totals.tokens, u.tokens);
|
|
@@ -295,6 +329,8 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
|
|
|
295
329
|
totals.toolCalls += u.toolCalls;
|
|
296
330
|
totals.assistantMessages += u.assistantMessages;
|
|
297
331
|
totals.userMessages += u.userMessages;
|
|
332
|
+
totals.totalTruncationSections += u.truncationSections ?? 0;
|
|
333
|
+
if (u.continueHereFired) totals.continueHereFiredCount++;
|
|
298
334
|
}
|
|
299
335
|
return totals;
|
|
300
336
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// GSD Extension — Desktop Notification Helper
|
|
2
2
|
// Cross-platform desktop notifications for auto-mode events.
|
|
3
|
-
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
4
3
|
|
|
5
4
|
import { execFileSync } from "node:child_process";
|
|
6
5
|
import type { NotificationPreferences } from "./types.js";
|