gsd-pi 2.78.1-dev.b6a389b66 → 2.78.1-dev.d8826a445
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/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -40
- package/dist/resources/extensions/gsd/auto.js +62 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/gsd-db.js +194 -0
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +117 -25
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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 +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +10 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +225 -47
- package/src/resources/extensions/gsd/auto.ts +79 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/gsd-db.ts +184 -0
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +154 -25
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// GSD-2 + Workspace handle tests: createWorkspace and scopeMilestone
|
|
2
|
+
|
|
3
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import {
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
symlinkSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
import { createWorkspace, scopeMilestone } from "../workspace.ts";
|
|
16
|
+
|
|
17
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function makeProjectDir(): string {
|
|
20
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-ws-test-")));
|
|
21
|
+
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
describe("createWorkspace", () => {
|
|
28
|
+
let projectDir: string;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
projectDir = makeProjectDir();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("from a project root produces mode=project and worktreeRoot=null", () => {
|
|
39
|
+
const ws = createWorkspace(projectDir);
|
|
40
|
+
assert.equal(ws.mode, "project");
|
|
41
|
+
assert.equal(ws.worktreeRoot, null);
|
|
42
|
+
assert.equal(ws.projectRoot, realpathSync(projectDir));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("from a worktree path produces mode=worktree, worktreeRoot=realpath, projectRoot=realpath of project", () => {
|
|
46
|
+
// Construct a worktree path: <projectDir>/.gsd/worktrees/M001
|
|
47
|
+
const worktreePath = join(projectDir, ".gsd", "worktrees", "M001");
|
|
48
|
+
mkdirSync(worktreePath, { recursive: true });
|
|
49
|
+
|
|
50
|
+
const ws = createWorkspace(worktreePath);
|
|
51
|
+
assert.equal(ws.mode, "worktree");
|
|
52
|
+
assert.equal(ws.worktreeRoot, realpathSync(worktreePath));
|
|
53
|
+
assert.equal(ws.projectRoot, realpathSync(projectDir));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("normalizes /foo and /foo/ to identical identityKey", () => {
|
|
57
|
+
const wsTrailing = createWorkspace(projectDir + "/");
|
|
58
|
+
const wsNoTrailing = createWorkspace(projectDir);
|
|
59
|
+
assert.equal(wsTrailing.identityKey, wsNoTrailing.identityKey);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("follows symlinks — identityKey matches realpath of target", (t) => {
|
|
63
|
+
const linkPath = join(tmpdir(), `gsd-ws-link-${Date.now()}`);
|
|
64
|
+
symlinkSync(projectDir, linkPath);
|
|
65
|
+
t.after(() => {
|
|
66
|
+
try { rmSync(linkPath); } catch { /* ignore */ }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const ws = createWorkspace(linkPath);
|
|
70
|
+
assert.equal(ws.identityKey, realpathSync(projectDir));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("GsdWorkspace and MilestoneScope are frozen", () => {
|
|
75
|
+
let projectDir: string;
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
projectDir = makeProjectDir();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(() => {
|
|
82
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("workspace is frozen", () => {
|
|
86
|
+
const ws = createWorkspace(projectDir);
|
|
87
|
+
assert.ok(Object.isFrozen(ws), "workspace should be frozen");
|
|
88
|
+
assert.throws(() => {
|
|
89
|
+
(ws as { mode: string }).mode = "worktree";
|
|
90
|
+
}, /Cannot assign/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("scope is frozen", () => {
|
|
94
|
+
const ws = createWorkspace(projectDir);
|
|
95
|
+
const scope = scopeMilestone(ws, "M001");
|
|
96
|
+
assert.ok(Object.isFrozen(scope), "scope should be frozen");
|
|
97
|
+
assert.throws(() => {
|
|
98
|
+
(scope as { milestoneId: string }).milestoneId = "M999";
|
|
99
|
+
}, /Cannot assign/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("contract inside workspace is frozen", () => {
|
|
103
|
+
const ws = createWorkspace(projectDir);
|
|
104
|
+
assert.ok(Object.isFrozen(ws.contract), "contract should be frozen");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("scopeMilestone path methods", () => {
|
|
109
|
+
let projectDir: string;
|
|
110
|
+
const MID = "M001";
|
|
111
|
+
|
|
112
|
+
beforeEach(() => {
|
|
113
|
+
projectDir = makeProjectDir();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(() => {
|
|
117
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("produces correct paths for a known milestone ID", () => {
|
|
121
|
+
const ws = createWorkspace(projectDir);
|
|
122
|
+
const scope = scopeMilestone(ws, MID);
|
|
123
|
+
const gsd = ws.contract.projectGsd;
|
|
124
|
+
|
|
125
|
+
assert.equal(scope.milestoneId, MID);
|
|
126
|
+
assert.equal(scope.contextFile(), join(gsd, "milestones", MID, `${MID}-CONTEXT.md`));
|
|
127
|
+
assert.equal(scope.roadmapFile(), join(gsd, "milestones", MID, `${MID}-ROADMAP.md`));
|
|
128
|
+
assert.equal(scope.stateFile(), join(gsd, "STATE.md"));
|
|
129
|
+
assert.equal(scope.dbPath(), ws.contract.projectDb);
|
|
130
|
+
assert.equal(scope.milestoneDir(), join(gsd, "milestones", MID));
|
|
131
|
+
assert.equal(scope.metaJson(), join(gsd, `${MID}-META.json`));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("two scopes from same workspace + same MID produce identical paths", () => {
|
|
135
|
+
const ws = createWorkspace(projectDir);
|
|
136
|
+
const scope1 = scopeMilestone(ws, MID);
|
|
137
|
+
const scope2 = scopeMilestone(ws, MID);
|
|
138
|
+
|
|
139
|
+
assert.equal(scope1.contextFile(), scope2.contextFile());
|
|
140
|
+
assert.equal(scope1.roadmapFile(), scope2.roadmapFile());
|
|
141
|
+
assert.equal(scope1.stateFile(), scope2.stateFile());
|
|
142
|
+
assert.equal(scope1.dbPath(), scope2.dbPath());
|
|
143
|
+
assert.equal(scope1.milestoneDir(), scope2.milestoneDir());
|
|
144
|
+
assert.equal(scope1.metaJson(), scope2.metaJson());
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("createWorkspace: contract.projectGsd is realpath-canonicalized when basePath is a symlink", () => {
|
|
149
|
+
let projectDir: string;
|
|
150
|
+
let linkPath: string;
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
projectDir = makeProjectDir();
|
|
154
|
+
linkPath = join(tmpdir(), `gsd-ws-symlink-${Date.now()}`);
|
|
155
|
+
symlinkSync(projectDir, linkPath);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
afterEach(() => {
|
|
159
|
+
try { rmSync(linkPath); } catch { /* ignore */ }
|
|
160
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("contract.projectGsd matches realpath of projectRoot when workspace is created via symlink", () => {
|
|
164
|
+
const ws = createWorkspace(linkPath);
|
|
165
|
+
|
|
166
|
+
const canonicalProjectRoot = realpathSync(projectDir);
|
|
167
|
+
|
|
168
|
+
// identityKey must be the realpath of the canonical project root
|
|
169
|
+
assert.equal(ws.identityKey, canonicalProjectRoot);
|
|
170
|
+
assert.equal(ws.projectRoot, canonicalProjectRoot);
|
|
171
|
+
|
|
172
|
+
// contract.projectGsd must start with the canonical project root —
|
|
173
|
+
// not with the symlink path. If the bug is present, contract.projectGsd
|
|
174
|
+
// would be linkPath + "/.gsd" instead of canonicalProjectRoot + "/.gsd".
|
|
175
|
+
assert.ok(
|
|
176
|
+
ws.contract.projectGsd.startsWith(canonicalProjectRoot),
|
|
177
|
+
`contract.projectGsd ("${ws.contract.projectGsd}") must be under the realpath'd projectRoot ("${canonicalProjectRoot}"), not the symlink path`,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("contract.projectDb matches realpath of projectRoot when workspace is created via symlink", () => {
|
|
182
|
+
const ws = createWorkspace(linkPath);
|
|
183
|
+
const canonicalProjectRoot = realpathSync(projectDir);
|
|
184
|
+
|
|
185
|
+
assert.ok(
|
|
186
|
+
ws.contract.projectDb.startsWith(canonicalProjectRoot),
|
|
187
|
+
`contract.projectDb ("${ws.contract.projectDb}") must be under the realpath'd projectRoot ("${canonicalProjectRoot}")`,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -24,38 +24,38 @@ import {
|
|
|
24
24
|
// ─── shouldBlockQueueExecution ────────────────────────────────────────────
|
|
25
25
|
|
|
26
26
|
test('shouldBlockQueueExecution: queue inactive → allow write to user source', (t) => {
|
|
27
|
-
t.after(() => clearDiscussionFlowState());
|
|
28
|
-
setQueuePhaseActive(false);
|
|
27
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
28
|
+
setQueuePhaseActive(false, process.cwd());
|
|
29
29
|
const r = shouldBlockQueueExecution('write', 'src/main.ts', false);
|
|
30
30
|
assert.strictEqual(r.block, false);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test('shouldBlockQueueExecution: queue active → block write to user source', (t) => {
|
|
34
|
-
t.after(() => clearDiscussionFlowState());
|
|
35
|
-
setQueuePhaseActive(true);
|
|
34
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
35
|
+
setQueuePhaseActive(true, process.cwd());
|
|
36
36
|
const r = shouldBlockQueueExecution('write', 'src/main.ts', true);
|
|
37
37
|
assert.strictEqual(r.block, true);
|
|
38
38
|
assert.ok(r.reason);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
test('shouldBlockQueueExecution: queue active → allow write to .gsd/ path', (t) => {
|
|
42
|
-
t.after(() => clearDiscussionFlowState());
|
|
43
|
-
setQueuePhaseActive(true);
|
|
42
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
43
|
+
setQueuePhaseActive(true, process.cwd());
|
|
44
44
|
const r = shouldBlockQueueExecution('write', '.gsd/milestones/M001/M001-CONTEXT.md', true);
|
|
45
45
|
assert.strictEqual(r.block, false);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test('shouldBlockQueueExecution: queue active → block mutating bash', (t) => {
|
|
49
|
-
t.after(() => clearDiscussionFlowState());
|
|
50
|
-
setQueuePhaseActive(true);
|
|
49
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
50
|
+
setQueuePhaseActive(true, process.cwd());
|
|
51
51
|
const r = shouldBlockQueueExecution('bash', 'npm run build', true);
|
|
52
52
|
assert.strictEqual(r.block, true);
|
|
53
53
|
assert.ok(r.reason);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test('shouldBlockQueueExecution: queue active → allow read-only bash', (t) => {
|
|
57
|
-
t.after(() => clearDiscussionFlowState());
|
|
58
|
-
setQueuePhaseActive(true);
|
|
57
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
58
|
+
setQueuePhaseActive(true, process.cwd());
|
|
59
59
|
const r = shouldBlockQueueExecution('bash', 'git log --oneline -5', true);
|
|
60
60
|
assert.strictEqual(r.block, false);
|
|
61
61
|
});
|
|
@@ -63,30 +63,30 @@ test('shouldBlockQueueExecution: queue active → allow read-only bash', (t) =>
|
|
|
63
63
|
// ─── shouldBlockPendingGate ───────────────────────────────────────────────
|
|
64
64
|
|
|
65
65
|
test('shouldBlockPendingGate: no pending gate → allow any tool', (t) => {
|
|
66
|
-
t.after(() => clearDiscussionFlowState());
|
|
67
|
-
clearPendingGate();
|
|
66
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
67
|
+
clearPendingGate(process.cwd());
|
|
68
68
|
const r = shouldBlockPendingGate('write', 'M001');
|
|
69
69
|
assert.strictEqual(r.block, false);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
test('shouldBlockPendingGate: pending gate → block write', (t) => {
|
|
73
|
-
t.after(() => clearDiscussionFlowState());
|
|
74
|
-
setPendingGate('depth_verification_M001');
|
|
73
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
74
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
75
75
|
const r = shouldBlockPendingGate('write', 'M001');
|
|
76
76
|
assert.strictEqual(r.block, true);
|
|
77
77
|
assert.ok(r.reason?.includes('depth_verification_M001'));
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test('shouldBlockPendingGate: pending gate → allow ask_user_questions', (t) => {
|
|
81
|
-
t.after(() => clearDiscussionFlowState());
|
|
82
|
-
setPendingGate('depth_verification_M001');
|
|
81
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
82
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
83
83
|
const r = shouldBlockPendingGate('ask_user_questions', 'M001');
|
|
84
84
|
assert.strictEqual(r.block, false);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test('shouldBlockPendingGate: pending gate → block read so approval question stays visible', (t) => {
|
|
88
|
-
t.after(() => clearDiscussionFlowState());
|
|
89
|
-
setPendingGate('depth_verification_M001');
|
|
88
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
89
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
90
90
|
const r = shouldBlockPendingGate('read', 'M001');
|
|
91
91
|
assert.strictEqual(r.block, true);
|
|
92
92
|
assert.ok(r.reason?.includes('already asked for user confirmation'));
|
|
@@ -95,31 +95,31 @@ test('shouldBlockPendingGate: pending gate → block read so approval question s
|
|
|
95
95
|
// ─── shouldBlockPendingGateBash ───────────────────────────────────────────
|
|
96
96
|
|
|
97
97
|
test('shouldBlockPendingGateBash: no pending gate → allow mutating bash', (t) => {
|
|
98
|
-
t.after(() => clearDiscussionFlowState());
|
|
99
|
-
clearPendingGate();
|
|
98
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
99
|
+
clearPendingGate(process.cwd());
|
|
100
100
|
const r = shouldBlockPendingGateBash('npm run build', 'M001');
|
|
101
101
|
assert.strictEqual(r.block, false);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
test('shouldBlockPendingGateBash: pending gate → block mutating bash', (t) => {
|
|
105
|
-
t.after(() => clearDiscussionFlowState());
|
|
106
|
-
setPendingGate('depth_verification_M001');
|
|
105
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
106
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
107
107
|
const r = shouldBlockPendingGateBash('npm run build', 'M001');
|
|
108
108
|
assert.strictEqual(r.block, true);
|
|
109
109
|
assert.ok(r.reason?.includes('depth_verification_M001'));
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test('shouldBlockPendingGateBash: pending gate → block read-only bash (cat)', (t) => {
|
|
113
|
-
t.after(() => clearDiscussionFlowState());
|
|
114
|
-
setPendingGate('depth_verification_M001');
|
|
113
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
114
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
115
115
|
const r = shouldBlockPendingGateBash('cat README.md', 'M001');
|
|
116
116
|
assert.strictEqual(r.block, true);
|
|
117
117
|
assert.ok(r.reason?.includes('already asked for user confirmation'));
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
test('shouldBlockPendingGateBash: pending gate → block read-only bash (git log)', (t) => {
|
|
121
|
-
t.after(() => clearDiscussionFlowState());
|
|
122
|
-
setPendingGate('depth_verification_M001');
|
|
121
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
122
|
+
setPendingGate('depth_verification_M001', process.cwd());
|
|
123
123
|
const r = shouldBlockPendingGateBash('git log --oneline -10', 'M001');
|
|
124
124
|
assert.strictEqual(r.block, true);
|
|
125
125
|
});
|
|
@@ -127,26 +127,26 @@ test('shouldBlockPendingGateBash: pending gate → block read-only bash (git log
|
|
|
127
127
|
// ─── shouldBlockContextWrite ──────────────────────────────────────────────
|
|
128
128
|
|
|
129
129
|
test('shouldBlockContextWrite: non-write tool → allow', (t) => {
|
|
130
|
-
t.after(() => clearDiscussionFlowState());
|
|
130
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
131
131
|
const r = shouldBlockContextWrite('read', '.gsd/milestones/M001/M001-CONTEXT.md', 'M001');
|
|
132
132
|
assert.strictEqual(r.block, false);
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test('shouldBlockContextWrite: write to non-CONTEXT file → allow', (t) => {
|
|
136
|
-
t.after(() => clearDiscussionFlowState());
|
|
136
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
137
137
|
const r = shouldBlockContextWrite('write', 'src/index.ts', 'M001');
|
|
138
138
|
assert.strictEqual(r.block, false);
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
test('shouldBlockContextWrite: write to CONTEXT.md without verification → block', (t) => {
|
|
142
|
-
t.after(() => clearDiscussionFlowState());
|
|
142
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
143
143
|
const r = shouldBlockContextWrite('write', '.gsd/milestones/M007/M007-CONTEXT.md', 'M007');
|
|
144
144
|
assert.strictEqual(r.block, true);
|
|
145
145
|
assert.ok(r.reason);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
test('shouldBlockContextWrite: write to CONTEXT.md after verification → allow', (t) => {
|
|
149
|
-
t.after(() => clearDiscussionFlowState());
|
|
149
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
150
150
|
markDepthVerified('M008');
|
|
151
151
|
const r = shouldBlockContextWrite('write', '.gsd/milestones/M008/M008-CONTEXT.md', 'M008');
|
|
152
152
|
assert.strictEqual(r.block, false);
|
|
@@ -155,33 +155,33 @@ test('shouldBlockContextWrite: write to CONTEXT.md after verification → allow'
|
|
|
155
155
|
// ─── shouldBlockContextArtifactSave ───────────────────────────────────────
|
|
156
156
|
|
|
157
157
|
test('shouldBlockContextArtifactSave: non-CONTEXT artifact type → allow', (t) => {
|
|
158
|
-
t.after(() => clearDiscussionFlowState());
|
|
158
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
159
159
|
const r = shouldBlockContextArtifactSave('CONTEXT-DRAFT', 'M001');
|
|
160
160
|
assert.strictEqual(r.block, false);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
test('shouldBlockContextArtifactSave: slice-level CONTEXT → allow', (t) => {
|
|
164
|
-
t.after(() => clearDiscussionFlowState());
|
|
164
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
165
165
|
const r = shouldBlockContextArtifactSave('CONTEXT', 'M001', 'S01');
|
|
166
166
|
assert.strictEqual(r.block, false);
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
test('shouldBlockContextArtifactSave: milestone CONTEXT without verification → block', (t) => {
|
|
170
|
-
t.after(() => clearDiscussionFlowState());
|
|
170
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
171
171
|
const r = shouldBlockContextArtifactSave('CONTEXT', 'M009');
|
|
172
172
|
assert.strictEqual(r.block, true);
|
|
173
173
|
assert.ok(r.reason?.includes('M009'));
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
test('shouldBlockContextArtifactSave: milestone CONTEXT after verification → allow', (t) => {
|
|
177
|
-
t.after(() => clearDiscussionFlowState());
|
|
177
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
178
178
|
markDepthVerified('M010');
|
|
179
179
|
const r = shouldBlockContextArtifactSave('CONTEXT', 'M010');
|
|
180
180
|
assert.strictEqual(r.block, false);
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
test('shouldBlockContextArtifactSave: CONTEXT with no milestoneId → block', (t) => {
|
|
184
|
-
t.after(() => clearDiscussionFlowState());
|
|
184
|
+
t.after(() => clearDiscussionFlowState(process.cwd()));
|
|
185
185
|
const r = shouldBlockContextArtifactSave('CONTEXT', null);
|
|
186
186
|
assert.strictEqual(r.block, true);
|
|
187
187
|
assert.ok(r.reason);
|