gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.848dd4c
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 +30 -12
- package/dist/resources/extensions/gsd/auto-start.js +10 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
- package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
- package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
- package/src/resources/extensions/gsd/auto-start.ts +14 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
- package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
- package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
- package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
- package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
- package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
- package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
- package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
- package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
- package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
- package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
- package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
- package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
- package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
- package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
- package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
- package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
- package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
- package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
- package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
- package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
- package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
- package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
- package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
- package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
- package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
- package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
- package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_ssgManifest.js +0 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
1
3
|
import { mkdtempSync, mkdirSync, rmSync, realpathSync } from "node:fs";
|
|
2
4
|
import { join } from "node:path";
|
|
3
5
|
import { tmpdir } from "node:os";
|
|
4
6
|
import { spawnSync } from "node:child_process";
|
|
5
7
|
|
|
6
8
|
import { gsdRoot, _clearGsdRootCache } from "../paths.ts";
|
|
7
|
-
import { createTestContext } from "./test-helpers.ts";
|
|
8
|
-
|
|
9
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
10
|
-
|
|
11
9
|
/** Create a tmp dir and resolve symlinks + 8.3 short names (macOS /var→/private/var, Windows RUNNER~1→runneradmin). */
|
|
12
10
|
function tmp(): string {
|
|
13
11
|
const p = mkdtempSync(join(tmpdir(), "gsd-paths-test-"));
|
|
@@ -23,91 +21,78 @@ function initGit(dir: string): void {
|
|
|
23
21
|
spawnSync("git", ["commit", "--allow-empty", "-m", "init"], { cwd: dir });
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} finally { cleanup(root); }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
// Case 2: .gsd exists at git root, cwd is a subdirectory
|
|
41
|
-
const root = tmp();
|
|
42
|
-
try {
|
|
43
|
-
initGit(root);
|
|
44
|
-
mkdirSync(join(root, ".gsd"));
|
|
45
|
-
const sub = join(root, "src", "deep");
|
|
46
|
-
mkdirSync(sub, { recursive: true });
|
|
47
|
-
_clearGsdRootCache();
|
|
48
|
-
const result = gsdRoot(sub);
|
|
49
|
-
assertEq(result, join(root, ".gsd"), "git-root probe: finds .gsd at git root from subdirectory");
|
|
50
|
-
} finally { cleanup(root); }
|
|
51
|
-
}
|
|
24
|
+
describe('paths', () => {
|
|
25
|
+
test('Case 1: .gsd exists at basePath — fast path', () => {
|
|
26
|
+
const root = tmp();
|
|
27
|
+
try {
|
|
28
|
+
mkdirSync(join(root, ".gsd"));
|
|
29
|
+
_clearGsdRootCache();
|
|
30
|
+
const result = gsdRoot(root);
|
|
31
|
+
assert.deepStrictEqual(result, join(root, ".gsd"), "fast path: returns basePath/.gsd");
|
|
32
|
+
} finally { cleanup(root); }
|
|
33
|
+
});
|
|
52
34
|
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// git probe returns root (no .gsd there), so walk-up takes over and finds project/.gsd
|
|
66
|
-
const result = gsdRoot(deep);
|
|
67
|
-
assertEq(result, join(project, ".gsd"), "walk-up: finds .gsd in ancestor when git root has none");
|
|
68
|
-
} finally { cleanup(root); }
|
|
69
|
-
}
|
|
35
|
+
test('Case 2: .gsd exists at git root, cwd is a subdirectory', () => {
|
|
36
|
+
const root = tmp();
|
|
37
|
+
try {
|
|
38
|
+
initGit(root);
|
|
39
|
+
mkdirSync(join(root, ".gsd"));
|
|
40
|
+
const sub = join(root, "src", "deep");
|
|
41
|
+
mkdirSync(sub, { recursive: true });
|
|
42
|
+
_clearGsdRootCache();
|
|
43
|
+
const result = gsdRoot(sub);
|
|
44
|
+
assert.deepStrictEqual(result, join(root, ".gsd"), "git-root probe: finds .gsd at git root from subdirectory");
|
|
45
|
+
} finally { cleanup(root); }
|
|
46
|
+
});
|
|
70
47
|
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
}
|
|
48
|
+
test('Case 3: .gsd in an ancestor — walk-up finds it', () => {
|
|
49
|
+
const root = tmp();
|
|
50
|
+
try {
|
|
51
|
+
initGit(root);
|
|
52
|
+
const project = join(root, "project");
|
|
53
|
+
mkdirSync(join(project, ".gsd"), { recursive: true });
|
|
54
|
+
const deep = join(project, "src", "deep");
|
|
55
|
+
mkdirSync(deep, { recursive: true });
|
|
56
|
+
_clearGsdRootCache();
|
|
57
|
+
const result = gsdRoot(deep);
|
|
58
|
+
assert.deepStrictEqual(result, join(project, ".gsd"), "walk-up: finds .gsd in ancestor when git root has none");
|
|
59
|
+
} finally { cleanup(root); }
|
|
60
|
+
});
|
|
85
61
|
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
}
|
|
62
|
+
test('Case 4: .gsd nowhere — fallback returns original basePath/.gsd', () => {
|
|
63
|
+
const root = tmp();
|
|
64
|
+
try {
|
|
65
|
+
initGit(root);
|
|
66
|
+
const sub = join(root, "src");
|
|
67
|
+
mkdirSync(sub, { recursive: true });
|
|
68
|
+
_clearGsdRootCache();
|
|
69
|
+
const result = gsdRoot(sub);
|
|
70
|
+
assert.deepStrictEqual(result, join(sub, ".gsd"), "fallback: returns basePath/.gsd when .gsd not found anywhere");
|
|
71
|
+
} finally { cleanup(root); }
|
|
72
|
+
});
|
|
98
73
|
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} finally { cleanup(outer); }
|
|
111
|
-
}
|
|
74
|
+
test('Case 5: cache — second call returns same value without re-probing', () => {
|
|
75
|
+
const root = tmp();
|
|
76
|
+
try {
|
|
77
|
+
mkdirSync(join(root, ".gsd"));
|
|
78
|
+
_clearGsdRootCache();
|
|
79
|
+
const first = gsdRoot(root);
|
|
80
|
+
const second = gsdRoot(root);
|
|
81
|
+
assert.deepStrictEqual(first, second, "cache: same result returned on second call");
|
|
82
|
+
assert.ok(first === second, "cache: identity check (same string)");
|
|
83
|
+
} finally { cleanup(root); }
|
|
84
|
+
});
|
|
112
85
|
|
|
113
|
-
|
|
86
|
+
test('Case 6: .gsd at basePath takes precedence over ancestor .gsd', () => {
|
|
87
|
+
const outer = tmp();
|
|
88
|
+
try {
|
|
89
|
+
initGit(outer);
|
|
90
|
+
mkdirSync(join(outer, ".gsd"));
|
|
91
|
+
const inner = join(outer, "nested");
|
|
92
|
+
mkdirSync(join(inner, ".gsd"), { recursive: true });
|
|
93
|
+
_clearGsdRootCache();
|
|
94
|
+
const result = gsdRoot(inner);
|
|
95
|
+
assert.deepStrictEqual(result, join(inner, ".gsd"), "precedence: nearest .gsd wins over ancestor");
|
|
96
|
+
} finally { cleanup(outer); }
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// GSD Extension — Hook Engine Tests (Post-Unit, Pre-Dispatch, State Persistence)
|
|
2
2
|
|
|
3
|
+
import { describe, test } from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
3
5
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
4
6
|
import { join } from "node:path";
|
|
5
7
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { createTestContext } from "./test-helpers.ts";
|
|
7
8
|
import {
|
|
8
9
|
checkPostUnitHooks,
|
|
9
10
|
getActiveHook,
|
|
@@ -20,8 +21,6 @@ import {
|
|
|
20
21
|
triggerHookManually,
|
|
21
22
|
} from "../post-unit-hooks.ts";
|
|
22
23
|
|
|
23
|
-
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
24
|
-
|
|
25
24
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
26
25
|
|
|
27
26
|
function createFixtureBase(): string {
|
|
@@ -36,14 +35,14 @@ function createFixtureBase(): string {
|
|
|
36
35
|
|
|
37
36
|
// ─── resolveHookArtifactPath ───────────────────────────────────────────────
|
|
38
37
|
|
|
39
|
-
console.log("\n=== resolveHookArtifactPath ===");
|
|
40
38
|
|
|
41
|
-
{
|
|
39
|
+
describe('post-unit-hooks', () => {
|
|
40
|
+
test('resolveHookArtifactPath', () => {
|
|
42
41
|
const base = "/project";
|
|
43
42
|
|
|
44
43
|
// Task-level
|
|
45
44
|
const taskPath = resolveHookArtifactPath(base, "M001/S01/T01", "REVIEW-PASS.md");
|
|
46
|
-
|
|
45
|
+
assert.deepStrictEqual(
|
|
47
46
|
taskPath,
|
|
48
47
|
join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-REVIEW-PASS.md"),
|
|
49
48
|
"task-level artifact path",
|
|
@@ -51,7 +50,7 @@ console.log("\n=== resolveHookArtifactPath ===");
|
|
|
51
50
|
|
|
52
51
|
// Slice-level
|
|
53
52
|
const slicePath = resolveHookArtifactPath(base, "M001/S01", "REVIEW-PASS.md");
|
|
54
|
-
|
|
53
|
+
assert.deepStrictEqual(
|
|
55
54
|
slicePath,
|
|
56
55
|
join(base, ".gsd", "milestones", "M001", "slices", "S01", "REVIEW-PASS.md"),
|
|
57
56
|
"slice-level artifact path",
|
|
@@ -59,129 +58,106 @@ console.log("\n=== resolveHookArtifactPath ===");
|
|
|
59
58
|
|
|
60
59
|
// Milestone-level
|
|
61
60
|
const milestonePath = resolveHookArtifactPath(base, "M001", "REVIEW-PASS.md");
|
|
62
|
-
|
|
61
|
+
assert.deepStrictEqual(
|
|
63
62
|
milestonePath,
|
|
64
63
|
join(base, ".gsd", "milestones", "M001", "REVIEW-PASS.md"),
|
|
65
64
|
"milestone-level artifact path",
|
|
66
65
|
);
|
|
67
|
-
}
|
|
66
|
+
});
|
|
68
67
|
|
|
69
68
|
// ─── resetHookState ────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
console.log("\n=== resetHookState ===");
|
|
72
|
-
|
|
73
|
-
{
|
|
69
|
+
test('resetHookState', () => {
|
|
74
70
|
resetHookState();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
71
|
+
assert.deepStrictEqual(getActiveHook(), null, "no active hook after reset");
|
|
72
|
+
assert.ok(!isRetryPending(), "no retry pending after reset");
|
|
73
|
+
assert.deepStrictEqual(consumeRetryTrigger(), null, "no retry trigger after reset");
|
|
74
|
+
});
|
|
79
75
|
|
|
80
76
|
// ─── checkPostUnitHooks with no hooks configured ───────────────────────────
|
|
81
|
-
|
|
82
|
-
console.log("\n=== No hooks configured ===");
|
|
83
|
-
|
|
84
|
-
{
|
|
77
|
+
test('No hooks configured', () => {
|
|
85
78
|
resetHookState();
|
|
86
79
|
const base = createFixtureBase();
|
|
87
80
|
try {
|
|
88
81
|
const result = checkPostUnitHooks("execute-task", "M001/S01/T01", base);
|
|
89
|
-
|
|
82
|
+
assert.deepStrictEqual(result, null, "returns null when no hooks configured");
|
|
90
83
|
} finally {
|
|
91
84
|
rmSync(base, { recursive: true, force: true });
|
|
92
85
|
}
|
|
93
|
-
}
|
|
86
|
+
});
|
|
94
87
|
|
|
95
88
|
// ─── Hook units don't trigger hooks (no hook-on-hook) ──────────────────────
|
|
96
|
-
|
|
97
|
-
console.log("\n=== Hook-on-hook prevention ===");
|
|
98
|
-
|
|
99
|
-
{
|
|
89
|
+
test('Hook-on-hook prevention', () => {
|
|
100
90
|
resetHookState();
|
|
101
91
|
const base = createFixtureBase();
|
|
102
92
|
try {
|
|
103
93
|
const result = checkPostUnitHooks("hook/code-review", "M001/S01/T01", base);
|
|
104
|
-
|
|
94
|
+
assert.deepStrictEqual(result, null, "hook units don't trigger other hooks");
|
|
105
95
|
} finally {
|
|
106
96
|
rmSync(base, { recursive: true, force: true });
|
|
107
97
|
}
|
|
108
|
-
}
|
|
98
|
+
});
|
|
109
99
|
|
|
110
100
|
// ─── consumeRetryTrigger clears state ──────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
console.log("\n=== consumeRetryTrigger clears state ===");
|
|
113
|
-
|
|
114
|
-
{
|
|
101
|
+
test('consumeRetryTrigger clears state', () => {
|
|
115
102
|
resetHookState();
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
103
|
+
assert.deepStrictEqual(consumeRetryTrigger(), null, "no trigger initially");
|
|
104
|
+
assert.ok(!isRetryPending(), "no retry initially");
|
|
105
|
+
});
|
|
119
106
|
|
|
120
107
|
// ─── Variable substitution in prompts ──────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
console.log("\n=== Variable substitution ===");
|
|
123
|
-
|
|
124
|
-
{
|
|
108
|
+
test('Variable substitution', () => {
|
|
125
109
|
const base = "/project";
|
|
126
110
|
|
|
127
111
|
// 3-part ID
|
|
128
112
|
const path3 = resolveHookArtifactPath(base, "M002/S03/T05", "result.md");
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
assert.ok(path3.includes("M002"), "3-part ID extracts milestoneId");
|
|
114
|
+
assert.ok(path3.includes("S03"), "3-part ID extracts sliceId");
|
|
115
|
+
assert.ok(path3.includes("T05"), "3-part ID extracts taskId");
|
|
116
|
+
assert.ok(path3.includes("milestones"), "3-part ID includes milestones/ segment");
|
|
133
117
|
|
|
134
118
|
// 2-part ID
|
|
135
119
|
const path2 = resolveHookArtifactPath(base, "M002/S03", "result.md");
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
assert.ok(path2.includes("M002"), "2-part ID extracts milestoneId");
|
|
121
|
+
assert.ok(path2.includes("S03"), "2-part ID extracts sliceId");
|
|
122
|
+
assert.ok(path2.includes("milestones"), "2-part ID includes milestones/ segment");
|
|
139
123
|
|
|
140
124
|
// 1-part ID
|
|
141
125
|
const path1 = resolveHookArtifactPath(base, "M002", "result.md");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
126
|
+
assert.ok(path1.includes("M002"), "1-part ID extracts milestoneId");
|
|
127
|
+
assert.ok(path1.includes("milestones"), "1-part ID includes milestones/ segment");
|
|
128
|
+
});
|
|
145
129
|
|
|
146
130
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
147
131
|
// Phase 2: Pre-Dispatch Hook Tests
|
|
148
132
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
-
|
|
150
|
-
console.log("\n=== Pre-dispatch: no hooks configured ===");
|
|
151
|
-
|
|
152
|
-
{
|
|
133
|
+
test('Pre-dispatch: no hooks configured', () => {
|
|
153
134
|
const base = createFixtureBase();
|
|
154
135
|
try {
|
|
155
136
|
const result = runPreDispatchHooks("execute-task", "M001/S01/T01", "original prompt", base);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
137
|
+
assert.deepStrictEqual(result.action, "proceed", "proceeds when no hooks");
|
|
138
|
+
assert.deepStrictEqual(result.prompt, "original prompt", "prompt unchanged");
|
|
139
|
+
assert.deepStrictEqual(result.firedHooks.length, 0, "no hooks fired");
|
|
159
140
|
} finally {
|
|
160
141
|
rmSync(base, { recursive: true, force: true });
|
|
161
142
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
console.log("\n=== Pre-dispatch: hook units bypass ===");
|
|
143
|
+
});
|
|
165
144
|
|
|
166
|
-
{
|
|
145
|
+
test('Pre-dispatch: hook units bypass', () => {
|
|
167
146
|
const base = createFixtureBase();
|
|
168
147
|
try {
|
|
169
148
|
const result = runPreDispatchHooks("hook/review", "M001/S01/T01", "hook prompt", base);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
149
|
+
assert.deepStrictEqual(result.action, "proceed", "hook units always proceed");
|
|
150
|
+
assert.deepStrictEqual(result.prompt, "hook prompt", "hook prompt unchanged");
|
|
151
|
+
assert.deepStrictEqual(result.firedHooks.length, 0, "no hooks fired for hook units");
|
|
173
152
|
} finally {
|
|
174
153
|
rmSync(base, { recursive: true, force: true });
|
|
175
154
|
}
|
|
176
|
-
}
|
|
155
|
+
});
|
|
177
156
|
|
|
178
157
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
179
158
|
// Phase 3: State Persistence Tests
|
|
180
159
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
-
|
|
182
|
-
console.log("\n=== State persistence: persist and restore ===");
|
|
183
|
-
|
|
184
|
-
{
|
|
160
|
+
test('State persistence: persist and restore', () => {
|
|
185
161
|
const base = createFixtureBase();
|
|
186
162
|
try {
|
|
187
163
|
resetHookState();
|
|
@@ -189,19 +165,17 @@ console.log("\n=== State persistence: persist and restore ===");
|
|
|
189
165
|
// Persist empty state
|
|
190
166
|
persistHookState(base);
|
|
191
167
|
const filePath = join(base, ".gsd", "hook-state.json");
|
|
192
|
-
|
|
168
|
+
assert.ok(existsSync(filePath), "hook-state.json created");
|
|
193
169
|
|
|
194
170
|
const content = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
195
|
-
|
|
196
|
-
|
|
171
|
+
assert.deepStrictEqual(typeof content.savedAt, "string", "savedAt is a string");
|
|
172
|
+
assert.deepStrictEqual(Object.keys(content.cycleCounts).length, 0, "empty cycle counts");
|
|
197
173
|
} finally {
|
|
198
174
|
rmSync(base, { recursive: true, force: true });
|
|
199
175
|
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
console.log("\n=== State persistence: restore from disk ===");
|
|
176
|
+
});
|
|
203
177
|
|
|
204
|
-
{
|
|
178
|
+
test('State persistence: restore from disk', () => {
|
|
205
179
|
const base = createFixtureBase();
|
|
206
180
|
try {
|
|
207
181
|
resetHookState();
|
|
@@ -222,16 +196,14 @@ console.log("\n=== State persistence: restore from disk ===");
|
|
|
222
196
|
// Verify by persisting and reading back
|
|
223
197
|
persistHookState(base);
|
|
224
198
|
const restored = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
225
|
-
|
|
226
|
-
|
|
199
|
+
assert.deepStrictEqual(restored.cycleCounts["review/execute-task/M001/S01/T01"], 2, "cycle count restored for review");
|
|
200
|
+
assert.deepStrictEqual(restored.cycleCounts["simplify/execute-task/M001/S01/T02"], 1, "cycle count restored for simplify");
|
|
227
201
|
} finally {
|
|
228
202
|
rmSync(base, { recursive: true, force: true });
|
|
229
203
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
console.log("\n=== State persistence: clear ===");
|
|
204
|
+
});
|
|
233
205
|
|
|
234
|
-
{
|
|
206
|
+
test('State persistence: clear', () => {
|
|
235
207
|
const base = createFixtureBase();
|
|
236
208
|
try {
|
|
237
209
|
resetHookState();
|
|
@@ -246,77 +218,65 @@ console.log("\n=== State persistence: clear ===");
|
|
|
246
218
|
clearPersistedHookState(base);
|
|
247
219
|
|
|
248
220
|
const cleared = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
249
|
-
|
|
221
|
+
assert.deepStrictEqual(Object.keys(cleared.cycleCounts).length, 0, "cycle counts cleared");
|
|
250
222
|
} finally {
|
|
251
223
|
rmSync(base, { recursive: true, force: true });
|
|
252
224
|
}
|
|
253
|
-
}
|
|
225
|
+
});
|
|
254
226
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
{
|
|
227
|
+
test('State persistence: restore handles missing file', () => {
|
|
258
228
|
const base = createFixtureBase();
|
|
259
229
|
try {
|
|
260
230
|
resetHookState();
|
|
261
231
|
// Should not throw
|
|
262
232
|
restoreHookState(base);
|
|
263
|
-
|
|
233
|
+
assert.deepStrictEqual(getActiveHook(), null, "no active hook after restore from missing file");
|
|
264
234
|
} finally {
|
|
265
235
|
rmSync(base, { recursive: true, force: true });
|
|
266
236
|
}
|
|
267
|
-
}
|
|
237
|
+
});
|
|
268
238
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
{
|
|
239
|
+
test('State persistence: restore handles corrupt file', () => {
|
|
272
240
|
const base = createFixtureBase();
|
|
273
241
|
try {
|
|
274
242
|
resetHookState();
|
|
275
243
|
writeFileSync(join(base, ".gsd", "hook-state.json"), "not json", "utf-8");
|
|
276
244
|
// Should not throw
|
|
277
245
|
restoreHookState(base);
|
|
278
|
-
|
|
246
|
+
assert.deepStrictEqual(getActiveHook(), null, "no active hook after corrupt restore");
|
|
279
247
|
} finally {
|
|
280
248
|
rmSync(base, { recursive: true, force: true });
|
|
281
249
|
}
|
|
282
|
-
}
|
|
250
|
+
});
|
|
283
251
|
|
|
284
252
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
285
253
|
// Phase 3: Hook Status Reporting Tests
|
|
286
254
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
287
|
-
|
|
288
|
-
console.log("\n=== Hook status: no hooks ===");
|
|
289
|
-
|
|
290
|
-
{
|
|
255
|
+
test('Hook status: no hooks', () => {
|
|
291
256
|
resetHookState();
|
|
292
257
|
const entries = getHookStatus();
|
|
293
258
|
// No preferences file = no hooks
|
|
294
|
-
|
|
259
|
+
assert.deepStrictEqual(entries.length, 0, "no entries when no hooks configured");
|
|
295
260
|
|
|
296
261
|
const formatted = formatHookStatus();
|
|
297
|
-
|
|
298
|
-
}
|
|
262
|
+
assert.match(formatted, /No hooks configured/, "status message says no hooks");
|
|
263
|
+
});
|
|
299
264
|
|
|
300
265
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
301
266
|
// Phase 4: Manual Hook Trigger Tests
|
|
302
267
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
303
|
-
|
|
304
|
-
console.log("\n=== triggerHookManually: hook not found ===");
|
|
305
|
-
|
|
306
|
-
{
|
|
268
|
+
test('triggerHookManually: hook not found', () => {
|
|
307
269
|
resetHookState();
|
|
308
270
|
const base = createFixtureBase();
|
|
309
271
|
try {
|
|
310
272
|
const result = triggerHookManually("nonexistent-hook", "execute-task", "M001/S01/T01", base);
|
|
311
|
-
|
|
273
|
+
assert.deepStrictEqual(result, null, "returns null when hook not found");
|
|
312
274
|
} finally {
|
|
313
275
|
rmSync(base, { recursive: true, force: true });
|
|
314
276
|
}
|
|
315
|
-
}
|
|
277
|
+
});
|
|
316
278
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
{
|
|
279
|
+
test('triggerHookManually: with configured hook', () => {
|
|
320
280
|
resetHookState();
|
|
321
281
|
const base = createFixtureBase();
|
|
322
282
|
try {
|
|
@@ -325,16 +285,16 @@ console.log("\n=== triggerHookManually: with configured hook ===");
|
|
|
325
285
|
const result = triggerHookManually("code-review", "execute-task", "M001/S01/T01", base);
|
|
326
286
|
// Result depends on whether code-review hook is configured in preferences
|
|
327
287
|
// The function should either return null or a valid HookDispatchResult
|
|
328
|
-
|
|
288
|
+
assert.ok(result === null || typeof result === "object", "returns null or object");
|
|
329
289
|
if (result) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
290
|
+
assert.deepStrictEqual(result.hookName, "code-review", "hook name in result");
|
|
291
|
+
assert.deepStrictEqual(result.unitType, "hook/code-review", "unit type is hook-prefixed");
|
|
292
|
+
assert.deepStrictEqual(result.unitId, "M001/S01/T01", "unit ID preserved");
|
|
293
|
+
assert.ok(typeof result.prompt === "string", "prompt is a string");
|
|
334
294
|
}
|
|
335
295
|
} finally {
|
|
336
296
|
rmSync(base, { recursive: true, force: true });
|
|
337
297
|
}
|
|
338
|
-
}
|
|
298
|
+
});
|
|
339
299
|
|
|
340
|
-
|
|
300
|
+
});
|