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,185 @@
|
|
|
1
|
+
// Debug Logger Tests
|
|
2
|
+
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
import { mkdtempSync, mkdirSync, readFileSync, existsSync, writeFileSync, readdirSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
enableDebug,
|
|
11
|
+
disableDebug,
|
|
12
|
+
isDebugEnabled,
|
|
13
|
+
getDebugLogPath,
|
|
14
|
+
debugLog,
|
|
15
|
+
debugTime,
|
|
16
|
+
debugCount,
|
|
17
|
+
debugPeak,
|
|
18
|
+
writeDebugSummary,
|
|
19
|
+
} from '../debug-logger.ts';
|
|
20
|
+
|
|
21
|
+
function createTempGsdDir(): string {
|
|
22
|
+
const tmp = mkdtempSync(join(tmpdir(), 'gsd-debug-test-'));
|
|
23
|
+
mkdirSync(join(tmp, '.gsd'), { recursive: true });
|
|
24
|
+
return tmp;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readLogLines(logPath: string): Record<string, unknown>[] {
|
|
28
|
+
const content = readFileSync(logPath, 'utf-8').trim();
|
|
29
|
+
if (!content) return [];
|
|
30
|
+
return content.split('\n').map(line => JSON.parse(line));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('enableDebug creates log file and sets enabled', () => {
|
|
34
|
+
const tmp = createTempGsdDir();
|
|
35
|
+
enableDebug(tmp);
|
|
36
|
+
|
|
37
|
+
assert.strictEqual(isDebugEnabled(), true);
|
|
38
|
+
const logPath = getDebugLogPath();
|
|
39
|
+
assert.ok(logPath, 'log path should be set');
|
|
40
|
+
// Normalize path separators for Windows compatibility
|
|
41
|
+
const normalized = logPath!.replace(/\\/g, '/');
|
|
42
|
+
assert.ok(normalized.includes('.gsd/debug/debug-'), 'log path should be in .gsd/debug/');
|
|
43
|
+
assert.ok(logPath!.endsWith('.log'), 'log path should end with .log');
|
|
44
|
+
|
|
45
|
+
disableDebug();
|
|
46
|
+
assert.strictEqual(isDebugEnabled(), false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('debugLog writes JSONL events', () => {
|
|
50
|
+
const tmp = createTempGsdDir();
|
|
51
|
+
enableDebug(tmp);
|
|
52
|
+
|
|
53
|
+
debugLog('test-event', { foo: 'bar', num: 42 });
|
|
54
|
+
debugLog('another-event');
|
|
55
|
+
|
|
56
|
+
const logPath = getDebugLogPath()!;
|
|
57
|
+
const lines = readLogLines(logPath);
|
|
58
|
+
|
|
59
|
+
assert.strictEqual(lines.length, 2);
|
|
60
|
+
assert.strictEqual(lines[0].event, 'test-event');
|
|
61
|
+
assert.strictEqual((lines[0] as any).foo, 'bar');
|
|
62
|
+
assert.strictEqual((lines[0] as any).num, 42);
|
|
63
|
+
assert.ok(lines[0].ts, 'should have timestamp');
|
|
64
|
+
assert.strictEqual(lines[1].event, 'another-event');
|
|
65
|
+
|
|
66
|
+
disableDebug();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('debugLog is no-op when disabled', () => {
|
|
70
|
+
assert.strictEqual(isDebugEnabled(), false);
|
|
71
|
+
// Should not throw
|
|
72
|
+
debugLog('should-not-appear', { data: 'test' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('debugTime measures elapsed time', async () => {
|
|
76
|
+
const tmp = createTempGsdDir();
|
|
77
|
+
enableDebug(tmp);
|
|
78
|
+
|
|
79
|
+
const stop = debugTime('timed-op');
|
|
80
|
+
// Small delay to ensure measurable time
|
|
81
|
+
await new Promise(r => setTimeout(r, 10));
|
|
82
|
+
stop({ extra: 'data' });
|
|
83
|
+
|
|
84
|
+
const logPath = getDebugLogPath()!;
|
|
85
|
+
const lines = readLogLines(logPath);
|
|
86
|
+
|
|
87
|
+
assert.strictEqual(lines.length, 1);
|
|
88
|
+
assert.strictEqual(lines[0].event, 'timed-op');
|
|
89
|
+
assert.ok((lines[0] as any).elapsed_ms >= 0, 'elapsed_ms should be non-negative');
|
|
90
|
+
assert.strictEqual((lines[0] as any).extra, 'data');
|
|
91
|
+
|
|
92
|
+
disableDebug();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('debugTime returns no-op when disabled', () => {
|
|
96
|
+
assert.strictEqual(isDebugEnabled(), false);
|
|
97
|
+
const stop = debugTime('should-not-appear');
|
|
98
|
+
stop({ data: 'test' }); // Should not throw
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('debugCount increments counters', () => {
|
|
102
|
+
const tmp = createTempGsdDir();
|
|
103
|
+
enableDebug(tmp);
|
|
104
|
+
|
|
105
|
+
debugCount('dispatches');
|
|
106
|
+
debugCount('dispatches');
|
|
107
|
+
debugCount('dispatches', 3);
|
|
108
|
+
|
|
109
|
+
// Counters are tested via writeDebugSummary
|
|
110
|
+
const logPath = writeDebugSummary()!;
|
|
111
|
+
const lines = readLogLines(logPath);
|
|
112
|
+
|
|
113
|
+
const summary = lines.find(l => l.event === 'debug-summary') as any;
|
|
114
|
+
assert.ok(summary, 'should have debug-summary event');
|
|
115
|
+
assert.strictEqual(summary.dispatches, 5);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('debugPeak tracks max values', () => {
|
|
119
|
+
const tmp = createTempGsdDir();
|
|
120
|
+
enableDebug(tmp);
|
|
121
|
+
|
|
122
|
+
debugPeak('ttsrPeakBuffer', 100);
|
|
123
|
+
debugPeak('ttsrPeakBuffer', 500);
|
|
124
|
+
debugPeak('ttsrPeakBuffer', 200); // Should not overwrite 500
|
|
125
|
+
|
|
126
|
+
const logPath = writeDebugSummary()!;
|
|
127
|
+
const lines = readLogLines(logPath);
|
|
128
|
+
|
|
129
|
+
const summary = lines.find(l => l.event === 'debug-summary') as any;
|
|
130
|
+
assert.strictEqual(summary.ttsrPeakBuffer, 500);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('writeDebugSummary includes all counters and disables debug', () => {
|
|
134
|
+
const tmp = createTempGsdDir();
|
|
135
|
+
enableDebug(tmp);
|
|
136
|
+
|
|
137
|
+
debugCount('deriveStateCalls', 10);
|
|
138
|
+
debugCount('deriveStateTotalMs', 80);
|
|
139
|
+
debugCount('ttsrChecks', 50);
|
|
140
|
+
debugCount('parseRoadmapCalls', 3);
|
|
141
|
+
debugCount('dispatches', 2);
|
|
142
|
+
|
|
143
|
+
const logPath = writeDebugSummary()!;
|
|
144
|
+
assert.ok(logPath, 'should return log path');
|
|
145
|
+
assert.strictEqual(isDebugEnabled(), false, 'should be disabled after summary');
|
|
146
|
+
|
|
147
|
+
const lines = readLogLines(logPath);
|
|
148
|
+
const summary = lines.find(l => l.event === 'debug-summary') as any;
|
|
149
|
+
assert.ok(summary);
|
|
150
|
+
assert.strictEqual(summary.deriveStateCalls, 10);
|
|
151
|
+
assert.strictEqual(summary.avgDeriveState_ms, 8);
|
|
152
|
+
assert.strictEqual(summary.ttsrChecks, 50);
|
|
153
|
+
assert.strictEqual(summary.dispatches, 2);
|
|
154
|
+
assert.ok(summary.totalElapsed_ms >= 0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('auto-prunes old debug logs', () => {
|
|
158
|
+
const tmp = createTempGsdDir();
|
|
159
|
+
const debugDir = join(tmp, '.gsd', 'debug');
|
|
160
|
+
mkdirSync(debugDir, { recursive: true });
|
|
161
|
+
|
|
162
|
+
// Create 6 old log files
|
|
163
|
+
for (let i = 0; i < 6; i++) {
|
|
164
|
+
writeFileSync(join(debugDir, `debug-2026-01-0${i + 1}.log`), 'old');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
enableDebug(tmp);
|
|
168
|
+
|
|
169
|
+
const files = readdirSync(debugDir).filter(f => f.startsWith('debug-') && f.endsWith('.log'));
|
|
170
|
+
// Should have at most MAX_DEBUG_LOGS (5) = 5 old + 1 new, but pruned to 5 total
|
|
171
|
+
// Actually: prunes to < 5 old, then creates 1 new = at most 5
|
|
172
|
+
assert.ok(files.length <= 6, `should have pruned old logs, got ${files.length}`);
|
|
173
|
+
|
|
174
|
+
disableDebug();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('disableDebug returns log path', () => {
|
|
178
|
+
const tmp = createTempGsdDir();
|
|
179
|
+
enableDebug(tmp);
|
|
180
|
+
|
|
181
|
+
const logPath = getDebugLogPath();
|
|
182
|
+
const returned = disableDebug();
|
|
183
|
+
assert.strictEqual(returned, logPath);
|
|
184
|
+
assert.strictEqual(getDebugLogPath(), null);
|
|
185
|
+
});
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
import { deriveState, invalidateStateCache } from '../state.ts';
|
|
6
|
+
import { openDatabase, closeDatabase, insertArtifact, isDbAvailable } from '../gsd-db.ts';
|
|
7
|
+
import { createTestContext } from './test-helpers.ts';
|
|
8
|
+
|
|
9
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
10
|
+
|
|
11
|
+
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function createFixtureBase(): string {
|
|
14
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-derive-db-'));
|
|
15
|
+
mkdirSync(join(base, '.gsd', 'milestones'), { recursive: true });
|
|
16
|
+
return base;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function writeFile(base: string, relativePath: string, content: string): void {
|
|
20
|
+
const full = join(base, '.gsd', relativePath);
|
|
21
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
22
|
+
writeFileSync(full, content);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function insertArtifactRow(relativePath: string, content: string, opts?: {
|
|
26
|
+
artifact_type?: string;
|
|
27
|
+
milestone_id?: string | null;
|
|
28
|
+
slice_id?: string | null;
|
|
29
|
+
task_id?: string | null;
|
|
30
|
+
}): void {
|
|
31
|
+
insertArtifact({
|
|
32
|
+
path: relativePath,
|
|
33
|
+
artifact_type: opts?.artifact_type ?? 'planning',
|
|
34
|
+
milestone_id: opts?.milestone_id ?? null,
|
|
35
|
+
slice_id: opts?.slice_id ?? null,
|
|
36
|
+
task_id: opts?.task_id ?? null,
|
|
37
|
+
full_content: content,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cleanup(base: string): void {
|
|
42
|
+
rmSync(base, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
46
|
+
// Test Groups
|
|
47
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
48
|
+
|
|
49
|
+
const ROADMAP_CONTENT = `# M001: Test Milestone
|
|
50
|
+
|
|
51
|
+
**Vision:** Test DB-backed derive state.
|
|
52
|
+
|
|
53
|
+
## Slices
|
|
54
|
+
|
|
55
|
+
- [ ] **S01: First Slice** \`risk:low\` \`depends:[]\`
|
|
56
|
+
> After this: Slice done.
|
|
57
|
+
|
|
58
|
+
- [ ] **S02: Second Slice** \`risk:low\` \`depends:[S01]\`
|
|
59
|
+
> After this: All done.
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const PLAN_CONTENT = `# S01: First Slice
|
|
63
|
+
|
|
64
|
+
**Goal:** Test executing.
|
|
65
|
+
**Demo:** Tests pass.
|
|
66
|
+
|
|
67
|
+
## Tasks
|
|
68
|
+
|
|
69
|
+
- [ ] **T01: First Task** \`est:10m\`
|
|
70
|
+
First task description.
|
|
71
|
+
|
|
72
|
+
- [x] **T02: Done Task** \`est:10m\`
|
|
73
|
+
Already done.
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const REQUIREMENTS_CONTENT = `# Requirements
|
|
77
|
+
|
|
78
|
+
## Active
|
|
79
|
+
|
|
80
|
+
### R001 — First Requirement
|
|
81
|
+
- Status: active
|
|
82
|
+
- Description: Something active.
|
|
83
|
+
|
|
84
|
+
### R002 — Second Requirement
|
|
85
|
+
- Status: active
|
|
86
|
+
- Description: Another active.
|
|
87
|
+
|
|
88
|
+
## Validated
|
|
89
|
+
|
|
90
|
+
### R003 — Validated
|
|
91
|
+
- Status: validated
|
|
92
|
+
- Description: Already validated.
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
async function main(): Promise<void> {
|
|
96
|
+
|
|
97
|
+
// ─── Test 1: DB-backed deriveState produces identical GSDState ─────────
|
|
98
|
+
console.log('\n=== derive-state-db: DB path matches file path ===');
|
|
99
|
+
{
|
|
100
|
+
const base = createFixtureBase();
|
|
101
|
+
try {
|
|
102
|
+
// Write files to disk (for file-only path)
|
|
103
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
104
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
105
|
+
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
106
|
+
writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
|
|
107
|
+
|
|
108
|
+
// Derive state from files only (no DB)
|
|
109
|
+
invalidateStateCache();
|
|
110
|
+
const fileState = await deriveState(base);
|
|
111
|
+
|
|
112
|
+
// Now open DB, insert matching artifacts
|
|
113
|
+
openDatabase(':memory:');
|
|
114
|
+
assertTrue(isDbAvailable(), 'db-match: DB is available after open');
|
|
115
|
+
|
|
116
|
+
insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
|
|
117
|
+
artifact_type: 'roadmap',
|
|
118
|
+
milestone_id: 'M001',
|
|
119
|
+
});
|
|
120
|
+
insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT, {
|
|
121
|
+
artifact_type: 'plan',
|
|
122
|
+
milestone_id: 'M001',
|
|
123
|
+
slice_id: 'S01',
|
|
124
|
+
});
|
|
125
|
+
insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
|
|
126
|
+
artifact_type: 'requirements',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Derive state from DB
|
|
130
|
+
invalidateStateCache();
|
|
131
|
+
const dbState = await deriveState(base);
|
|
132
|
+
|
|
133
|
+
// Field-by-field equality
|
|
134
|
+
assertEq(dbState.phase, fileState.phase, 'db-match: phase matches');
|
|
135
|
+
assertEq(dbState.activeMilestone?.id, fileState.activeMilestone?.id, 'db-match: activeMilestone.id matches');
|
|
136
|
+
assertEq(dbState.activeMilestone?.title, fileState.activeMilestone?.title, 'db-match: activeMilestone.title matches');
|
|
137
|
+
assertEq(dbState.activeSlice?.id, fileState.activeSlice?.id, 'db-match: activeSlice.id matches');
|
|
138
|
+
assertEq(dbState.activeSlice?.title, fileState.activeSlice?.title, 'db-match: activeSlice.title matches');
|
|
139
|
+
assertEq(dbState.activeTask?.id, fileState.activeTask?.id, 'db-match: activeTask.id matches');
|
|
140
|
+
assertEq(dbState.activeTask?.title, fileState.activeTask?.title, 'db-match: activeTask.title matches');
|
|
141
|
+
assertEq(dbState.blockers, fileState.blockers, 'db-match: blockers match');
|
|
142
|
+
assertEq(dbState.registry.length, fileState.registry.length, 'db-match: registry length matches');
|
|
143
|
+
assertEq(dbState.registry[0]?.status, fileState.registry[0]?.status, 'db-match: registry[0] status matches');
|
|
144
|
+
assertEq(dbState.requirements?.active, fileState.requirements?.active, 'db-match: requirements.active matches');
|
|
145
|
+
assertEq(dbState.requirements?.validated, fileState.requirements?.validated, 'db-match: requirements.validated matches');
|
|
146
|
+
assertEq(dbState.requirements?.total, fileState.requirements?.total, 'db-match: requirements.total matches');
|
|
147
|
+
assertEq(dbState.progress?.milestones?.done, fileState.progress?.milestones?.done, 'db-match: milestones.done matches');
|
|
148
|
+
assertEq(dbState.progress?.milestones?.total, fileState.progress?.milestones?.total, 'db-match: milestones.total matches');
|
|
149
|
+
assertEq(dbState.progress?.slices?.done, fileState.progress?.slices?.done, 'db-match: slices.done matches');
|
|
150
|
+
assertEq(dbState.progress?.slices?.total, fileState.progress?.slices?.total, 'db-match: slices.total matches');
|
|
151
|
+
assertEq(dbState.progress?.tasks?.done, fileState.progress?.tasks?.done, 'db-match: tasks.done matches');
|
|
152
|
+
assertEq(dbState.progress?.tasks?.total, fileState.progress?.tasks?.total, 'db-match: tasks.total matches');
|
|
153
|
+
|
|
154
|
+
closeDatabase();
|
|
155
|
+
} finally {
|
|
156
|
+
closeDatabase();
|
|
157
|
+
cleanup(base);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Test 2: Fallback when DB unavailable ─────────────────────────────
|
|
162
|
+
console.log('\n=== derive-state-db: fallback when DB unavailable ===');
|
|
163
|
+
{
|
|
164
|
+
const base = createFixtureBase();
|
|
165
|
+
try {
|
|
166
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
167
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
168
|
+
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
169
|
+
|
|
170
|
+
// No DB open — isDbAvailable() is false
|
|
171
|
+
assertTrue(!isDbAvailable(), 'fallback: DB is not available');
|
|
172
|
+
invalidateStateCache();
|
|
173
|
+
const state = await deriveState(base);
|
|
174
|
+
|
|
175
|
+
assertEq(state.phase, 'executing', 'fallback: phase is executing');
|
|
176
|
+
assertEq(state.activeMilestone?.id, 'M001', 'fallback: activeMilestone is M001');
|
|
177
|
+
assertEq(state.activeSlice?.id, 'S01', 'fallback: activeSlice is S01');
|
|
178
|
+
assertEq(state.activeTask?.id, 'T01', 'fallback: activeTask is T01');
|
|
179
|
+
} finally {
|
|
180
|
+
cleanup(base);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── Test 3: Empty DB falls back to file reads ────────────────────────
|
|
185
|
+
console.log('\n=== derive-state-db: empty DB falls back to files ===');
|
|
186
|
+
{
|
|
187
|
+
const base = createFixtureBase();
|
|
188
|
+
try {
|
|
189
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
190
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
191
|
+
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
192
|
+
|
|
193
|
+
// Open DB but insert nothing — empty artifacts table
|
|
194
|
+
openDatabase(':memory:');
|
|
195
|
+
assertTrue(isDbAvailable(), 'empty-db: DB is available');
|
|
196
|
+
|
|
197
|
+
invalidateStateCache();
|
|
198
|
+
const state = await deriveState(base);
|
|
199
|
+
|
|
200
|
+
// Should still work via cachedLoadFile → loadFile disk fallback
|
|
201
|
+
assertEq(state.phase, 'executing', 'empty-db: phase is executing');
|
|
202
|
+
assertEq(state.activeMilestone?.id, 'M001', 'empty-db: activeMilestone is M001');
|
|
203
|
+
assertEq(state.activeSlice?.id, 'S01', 'empty-db: activeSlice is S01');
|
|
204
|
+
assertEq(state.activeTask?.id, 'T01', 'empty-db: activeTask is T01');
|
|
205
|
+
|
|
206
|
+
closeDatabase();
|
|
207
|
+
} finally {
|
|
208
|
+
closeDatabase();
|
|
209
|
+
cleanup(base);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Test 4: Partial DB content fills gaps from disk ──────────────────
|
|
214
|
+
console.log('\n=== derive-state-db: partial DB fills gaps from disk ===');
|
|
215
|
+
{
|
|
216
|
+
const base = createFixtureBase();
|
|
217
|
+
try {
|
|
218
|
+
// Write all files to disk
|
|
219
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
220
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
221
|
+
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
222
|
+
writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
|
|
223
|
+
|
|
224
|
+
// Open DB but only insert the roadmap — plan and requirements missing from DB
|
|
225
|
+
openDatabase(':memory:');
|
|
226
|
+
insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
|
|
227
|
+
artifact_type: 'roadmap',
|
|
228
|
+
milestone_id: 'M001',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
invalidateStateCache();
|
|
232
|
+
const state = await deriveState(base);
|
|
233
|
+
|
|
234
|
+
// Should work: roadmap from DB, plan from disk fallback
|
|
235
|
+
assertEq(state.phase, 'executing', 'partial-db: phase is executing');
|
|
236
|
+
assertEq(state.activeMilestone?.id, 'M001', 'partial-db: activeMilestone is M001');
|
|
237
|
+
assertEq(state.activeSlice?.id, 'S01', 'partial-db: activeSlice is S01');
|
|
238
|
+
assertEq(state.activeTask?.id, 'T01', 'partial-db: activeTask is T01');
|
|
239
|
+
// Requirements loaded from disk fallback
|
|
240
|
+
assertEq(state.requirements?.active, 2, 'partial-db: requirements.active from disk');
|
|
241
|
+
assertEq(state.requirements?.validated, 1, 'partial-db: requirements.validated from disk');
|
|
242
|
+
assertEq(state.requirements?.total, 3, 'partial-db: requirements.total from disk');
|
|
243
|
+
|
|
244
|
+
closeDatabase();
|
|
245
|
+
} finally {
|
|
246
|
+
closeDatabase();
|
|
247
|
+
cleanup(base);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ─── Test 5: Requirements counting from DB content ────────────────────
|
|
252
|
+
console.log('\n=== derive-state-db: requirements from DB content ===');
|
|
253
|
+
{
|
|
254
|
+
const base = createFixtureBase();
|
|
255
|
+
try {
|
|
256
|
+
// Write minimal milestone dir (needed for milestone discovery)
|
|
257
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
|
|
258
|
+
// Do NOT write REQUIREMENTS.md to disk — only in DB
|
|
259
|
+
|
|
260
|
+
openDatabase(':memory:');
|
|
261
|
+
insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
|
|
262
|
+
artifact_type: 'requirements',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
invalidateStateCache();
|
|
266
|
+
const state = await deriveState(base);
|
|
267
|
+
|
|
268
|
+
// Requirements should come from DB
|
|
269
|
+
assertEq(state.requirements?.active, 2, 'req-from-db: requirements.active = 2');
|
|
270
|
+
assertEq(state.requirements?.validated, 1, 'req-from-db: requirements.validated = 1');
|
|
271
|
+
assertEq(state.requirements?.total, 3, 'req-from-db: requirements.total = 3');
|
|
272
|
+
|
|
273
|
+
closeDatabase();
|
|
274
|
+
} finally {
|
|
275
|
+
closeDatabase();
|
|
276
|
+
cleanup(base);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── Test 6: DB content with multi-milestone registry ─────────────────
|
|
281
|
+
console.log('\n=== derive-state-db: multi-milestone from DB ===');
|
|
282
|
+
{
|
|
283
|
+
const base = createFixtureBase();
|
|
284
|
+
|
|
285
|
+
const completedRoadmap = `# M001: First Milestone
|
|
286
|
+
|
|
287
|
+
**Vision:** Already done.
|
|
288
|
+
|
|
289
|
+
## Slices
|
|
290
|
+
|
|
291
|
+
- [x] **S01: Done** \`risk:low\` \`depends:[]\`
|
|
292
|
+
> After this: Done.
|
|
293
|
+
`;
|
|
294
|
+
const summaryContent = `# M001 Summary\n\nFirst milestone complete.`;
|
|
295
|
+
|
|
296
|
+
const activeRoadmap = `# M002: Second Milestone
|
|
297
|
+
|
|
298
|
+
**Vision:** Currently active.
|
|
299
|
+
|
|
300
|
+
## Slices
|
|
301
|
+
|
|
302
|
+
- [ ] **S01: In Progress** \`risk:low\` \`depends:[]\`
|
|
303
|
+
> After this: Done.
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Create milestone dirs on disk (needed for directory scanning)
|
|
308
|
+
// Also write roadmap files to disk — resolveMilestoneFile checks file existence
|
|
309
|
+
// The DB only provides content, not file discovery
|
|
310
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
|
|
311
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M002'), { recursive: true });
|
|
312
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', completedRoadmap);
|
|
313
|
+
writeFile(base, 'milestones/M001/M001-SUMMARY.md', summaryContent);
|
|
314
|
+
writeFile(base, 'milestones/M002/M002-ROADMAP.md', activeRoadmap);
|
|
315
|
+
|
|
316
|
+
// Put roadmap content in DB only
|
|
317
|
+
openDatabase(':memory:');
|
|
318
|
+
insertArtifactRow('milestones/M001/M001-ROADMAP.md', completedRoadmap, {
|
|
319
|
+
artifact_type: 'roadmap',
|
|
320
|
+
milestone_id: 'M001',
|
|
321
|
+
});
|
|
322
|
+
insertArtifactRow('milestones/M001/M001-SUMMARY.md', summaryContent, {
|
|
323
|
+
artifact_type: 'summary',
|
|
324
|
+
milestone_id: 'M001',
|
|
325
|
+
});
|
|
326
|
+
insertArtifactRow('milestones/M002/M002-ROADMAP.md', activeRoadmap, {
|
|
327
|
+
artifact_type: 'roadmap',
|
|
328
|
+
milestone_id: 'M002',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
invalidateStateCache();
|
|
332
|
+
const state = await deriveState(base);
|
|
333
|
+
|
|
334
|
+
assertEq(state.registry.length, 2, 'multi-ms-db: registry has 2 entries');
|
|
335
|
+
assertEq(state.registry[0]?.id, 'M001', 'multi-ms-db: registry[0] is M001');
|
|
336
|
+
assertEq(state.registry[0]?.status, 'complete', 'multi-ms-db: M001 is complete');
|
|
337
|
+
assertEq(state.registry[1]?.id, 'M002', 'multi-ms-db: registry[1] is M002');
|
|
338
|
+
assertEq(state.registry[1]?.status, 'active', 'multi-ms-db: M002 is active');
|
|
339
|
+
assertEq(state.activeMilestone?.id, 'M002', 'multi-ms-db: activeMilestone is M002');
|
|
340
|
+
assertEq(state.phase, 'planning', 'multi-ms-db: phase is planning (no plan for S01)');
|
|
341
|
+
|
|
342
|
+
closeDatabase();
|
|
343
|
+
} finally {
|
|
344
|
+
closeDatabase();
|
|
345
|
+
cleanup(base);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ─── Test 7: Cache invalidation works for DB path ─────────────────────
|
|
350
|
+
console.log('\n=== derive-state-db: cache invalidation ===');
|
|
351
|
+
{
|
|
352
|
+
const base = createFixtureBase();
|
|
353
|
+
try {
|
|
354
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT);
|
|
355
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT);
|
|
356
|
+
writeFile(base, 'milestones/M001/slices/S01/tasks/.gitkeep', '');
|
|
357
|
+
|
|
358
|
+
openDatabase(':memory:');
|
|
359
|
+
insertArtifactRow('milestones/M001/M001-ROADMAP.md', ROADMAP_CONTENT, {
|
|
360
|
+
artifact_type: 'roadmap',
|
|
361
|
+
milestone_id: 'M001',
|
|
362
|
+
});
|
|
363
|
+
insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', PLAN_CONTENT, {
|
|
364
|
+
artifact_type: 'plan',
|
|
365
|
+
milestone_id: 'M001',
|
|
366
|
+
slice_id: 'S01',
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
invalidateStateCache();
|
|
370
|
+
const state1 = await deriveState(base);
|
|
371
|
+
assertEq(state1.activeTask?.id, 'T01', 'cache-inv: first call gets T01');
|
|
372
|
+
|
|
373
|
+
// Simulate task completion by updating the plan in DB
|
|
374
|
+
const updatedPlan = PLAN_CONTENT.replace('- [ ] **T01:', '- [x] **T01:');
|
|
375
|
+
insertArtifactRow('milestones/M001/slices/S01/S01-PLAN.md', updatedPlan, {
|
|
376
|
+
artifact_type: 'plan',
|
|
377
|
+
milestone_id: 'M001',
|
|
378
|
+
slice_id: 'S01',
|
|
379
|
+
});
|
|
380
|
+
// Also update file on disk (cachedLoadFile may read from disk for some paths)
|
|
381
|
+
writeFile(base, 'milestones/M001/slices/S01/S01-PLAN.md', updatedPlan);
|
|
382
|
+
|
|
383
|
+
// Without invalidation, should return cached result (T01 still active)
|
|
384
|
+
const state2 = await deriveState(base);
|
|
385
|
+
assertEq(state2.activeTask?.id, 'T01', 'cache-inv: cached result still has T01');
|
|
386
|
+
|
|
387
|
+
// After invalidation, should pick up updated content
|
|
388
|
+
invalidateStateCache();
|
|
389
|
+
const state3 = await deriveState(base);
|
|
390
|
+
assertEq(state3.phase, 'summarizing', 'cache-inv: after invalidation, phase is summarizing (all tasks done)');
|
|
391
|
+
assertEq(state3.activeTask, null, 'cache-inv: activeTask is null after all done');
|
|
392
|
+
|
|
393
|
+
closeDatabase();
|
|
394
|
+
} finally {
|
|
395
|
+
closeDatabase();
|
|
396
|
+
cleanup(base);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
report();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
main().catch((error) => {
|
|
404
|
+
console.error(error);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const ROOT = new URL("../../../../../", import.meta.url);
|
|
2
|
+
|
|
3
|
+
export function resolve(specifier, context, nextResolve) {
|
|
4
|
+
// 1. Direct redirects to dist/ for specific packages
|
|
5
|
+
if (specifier === "../../packages/pi-coding-agent/src/index.js") {
|
|
6
|
+
specifier = new URL("packages/pi-coding-agent/dist/index.js", ROOT).href;
|
|
7
|
+
} else if (specifier === "@gsd/pi-ai/oauth") {
|
|
8
|
+
specifier = new URL("packages/pi-ai/dist/utils/oauth/index.js", ROOT).href;
|
|
9
|
+
} else if (specifier === "@gsd/pi-ai") {
|
|
10
|
+
specifier = new URL("packages/pi-ai/dist/index.js", ROOT).href;
|
|
11
|
+
} else if (specifier === "@gsd/pi-agent-core") {
|
|
12
|
+
specifier = new URL("packages/pi-agent-core/dist/index.js", ROOT).href;
|
|
13
|
+
}
|
|
14
|
+
// 2. Mapping .js to .ts for local imports when running tests from src/
|
|
15
|
+
else if (specifier.endsWith('.js') && (specifier.startsWith('./') || specifier.startsWith('../'))) {
|
|
16
|
+
if (context.parentURL && context.parentURL.includes('/src/')) {
|
|
17
|
+
specifier = specifier.replace(/\.js$/, '.ts');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return nextResolve(specifier, context);
|
|
22
|
+
}
|