gsd-pi 2.80.0-dev.2848d9009 → 2.80.0-dev.2be7caf18
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 +3 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +32 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
- package/dist/resources/extensions/gsd/auto.js +7 -2
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/guided-flow.js +33 -6
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
- 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/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/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +66 -0
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +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 +3 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -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 +25 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +18 -8
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +20 -8
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +50 -8
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
- package/src/resources/extensions/gsd/auto.ts +11 -2
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/guided-flow.ts +39 -6
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → 3EDDd9ULaybSupPoA_vf-}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vaQ47E8aNlbbNEDoeME6G → 3EDDd9ULaybSupPoA_vf-}/_ssgManifest.js +0 -0
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
isLockProcessAlive,
|
|
31
31
|
} from "../crash-recovery.ts";
|
|
32
32
|
import { normalizeRealPath } from "../paths.ts";
|
|
33
|
+
import { writeUnitRuntimeRecord } from "../unit-runtime.ts";
|
|
33
34
|
|
|
34
35
|
function makeBase(): string {
|
|
35
36
|
const base = mkdtempSync(join(tmpdir(), "gsd-crash-recovery-"));
|
|
@@ -102,6 +103,27 @@ test("readCrashLock synthesizes LockData from a stale dead worker (no dispatches
|
|
|
102
103
|
assert.ok(lock!.startedAt, "startedAt populated from workers.started_at");
|
|
103
104
|
});
|
|
104
105
|
|
|
106
|
+
test("readCrashLock falls back to latest in-flight runtime record when dispatch claim is missing", (t) => {
|
|
107
|
+
const base = makeBase();
|
|
108
|
+
t.after(() => cleanup(base));
|
|
109
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
110
|
+
const projectRoot = normalizeRealPath(base);
|
|
111
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
112
|
+
writeUnitRuntimeRecord(base, "execute-task", "M008/S04/T02", 1778069087937, {
|
|
113
|
+
phase: "dispatched",
|
|
114
|
+
lastProgressAt: 1778069087937,
|
|
115
|
+
lastProgressKind: "dispatch",
|
|
116
|
+
});
|
|
117
|
+
setWorkerPid(workerId, 99999);
|
|
118
|
+
expireWorker(workerId);
|
|
119
|
+
|
|
120
|
+
const lock = readCrashLock(base);
|
|
121
|
+
assert.ok(lock, "stale worker surfaced as a crash lock");
|
|
122
|
+
assert.equal(lock!.unitType, "execute-task");
|
|
123
|
+
assert.equal(lock!.unitId, "M008/S04/T02");
|
|
124
|
+
assert.equal(lock!.unitStartedAt, new Date(1778069087937).toISOString());
|
|
125
|
+
});
|
|
126
|
+
|
|
105
127
|
test("readCrashLock includes the most recent dispatch as unitType/unitId", (t) => {
|
|
106
128
|
const base = makeBase();
|
|
107
129
|
t.after(() => cleanup(base));
|
|
@@ -108,6 +108,21 @@ test('executeExecSearch: returns helpful empty-state message when no matches', (
|
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
test('executeExecSearch: returns disabled error when context_mode.enabled=false', () => {
|
|
112
|
+
const base = freshBase();
|
|
113
|
+
try {
|
|
114
|
+
writeRun(base, 'should-not-surface', { stdout: 'hidden\n' });
|
|
115
|
+
const result = executeExecSearch(
|
|
116
|
+
{ query: 'hidden' },
|
|
117
|
+
{ baseDir: base, preferences: { context_mode: { enabled: false } } },
|
|
118
|
+
);
|
|
119
|
+
assert.equal(result.isError, true);
|
|
120
|
+
assert.equal((result.details as { error?: string }).error, 'context_mode_disabled');
|
|
121
|
+
} finally {
|
|
122
|
+
cleanup(base);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
111
126
|
test('executeExecSearch: includes stdout_path and preview in details', () => {
|
|
112
127
|
const base = freshBase();
|
|
113
128
|
try {
|
|
@@ -7,6 +7,7 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { EXEC_DEFAULTS, runExecSandbox, type ExecSandboxOptions } from '../exec-sandbox.ts';
|
|
8
8
|
import { buildExecOptions, executeGsdExec } from '../tools/exec-tool.ts';
|
|
9
9
|
import { isContextModeEnabled } from '../preferences-types.ts';
|
|
10
|
+
import { validatePreferences } from '../preferences-validation.ts';
|
|
10
11
|
|
|
11
12
|
function freshBase(): string {
|
|
12
13
|
return mkdtempSync(join(tmpdir(), 'gsd-exec-test-'));
|
|
@@ -174,6 +175,52 @@ test('executeGsdExec: runs when enabled explicitly set to true', async () => {
|
|
|
174
175
|
}
|
|
175
176
|
});
|
|
176
177
|
|
|
178
|
+
test('executeGsdExec: forwards custom exec_env_allowlist from preferences', async () => {
|
|
179
|
+
const base = freshBase();
|
|
180
|
+
try {
|
|
181
|
+
const result = await executeGsdExec(
|
|
182
|
+
{
|
|
183
|
+
runtime: 'bash',
|
|
184
|
+
script: 'printf "allowed=%s blocked=%s\\n" "$GSD_ALLOWED" "$GSD_BLOCKED"',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
baseDir: base,
|
|
188
|
+
preferences: {
|
|
189
|
+
context_mode: {
|
|
190
|
+
enabled: true,
|
|
191
|
+
exec_env_allowlist: ['GSD_ALLOWED'],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
env: {
|
|
195
|
+
PATH: '/usr/bin:/bin',
|
|
196
|
+
HOME: '/tmp',
|
|
197
|
+
GSD_ALLOWED: 'yes',
|
|
198
|
+
GSD_BLOCKED: 'no',
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
assert.ok(!result.isError);
|
|
203
|
+
assert.match(result.content[0].text, /allowed=yes blocked=/);
|
|
204
|
+
assert.doesNotMatch(result.content[0].text, /blocked=no/);
|
|
205
|
+
} finally {
|
|
206
|
+
cleanup(base);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('executeGsdExec: enforces per-call timeout override end-to-end', async () => {
|
|
211
|
+
const base = freshBase();
|
|
212
|
+
try {
|
|
213
|
+
const result = await executeGsdExec(
|
|
214
|
+
{ runtime: 'bash', script: 'sleep 2', timeout_ms: 1 },
|
|
215
|
+
{ baseDir: base, preferences: { context_mode: { enabled: true, exec_timeout_ms: 10_000 } } },
|
|
216
|
+
);
|
|
217
|
+
assert.equal(result.details.timed_out, true);
|
|
218
|
+
assert.equal(result.isError, true);
|
|
219
|
+
} finally {
|
|
220
|
+
cleanup(base);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
177
224
|
test('executeGsdExec: rejects empty script', async () => {
|
|
178
225
|
const base = freshBase();
|
|
179
226
|
try {
|
|
@@ -188,6 +235,24 @@ test('executeGsdExec: rejects empty script', async () => {
|
|
|
188
235
|
}
|
|
189
236
|
});
|
|
190
237
|
|
|
238
|
+
test('validatePreferences: rejects invalid context_mode preference values', () => {
|
|
239
|
+
const result = validatePreferences({
|
|
240
|
+
context_mode: {
|
|
241
|
+
enabled: 'false',
|
|
242
|
+
exec_timeout_ms: 999,
|
|
243
|
+
exec_stdout_cap_bytes: 1,
|
|
244
|
+
exec_digest_chars: -1,
|
|
245
|
+
exec_env_allowlist: ['GOOD_NAME', 'bad-name'],
|
|
246
|
+
},
|
|
247
|
+
} as any);
|
|
248
|
+
assert.ok(result.errors.length > 0);
|
|
249
|
+
assert.ok(result.errors.includes('context_mode.enabled must be a boolean'));
|
|
250
|
+
assert.ok(result.errors.includes('context_mode.exec_timeout_ms must be a number between 1000 and 600000'));
|
|
251
|
+
assert.ok(result.errors.includes('context_mode.exec_stdout_cap_bytes must be a number between 4096 and 16777216'));
|
|
252
|
+
assert.ok(result.errors.includes('context_mode.exec_digest_chars must be a number between 0 and 4000'));
|
|
253
|
+
assert.ok(result.errors.includes('context_mode.exec_env_allowlist must be an array of valid env var names'));
|
|
254
|
+
});
|
|
255
|
+
|
|
191
256
|
test('isContextModeEnabled: defaults to true; only explicit false disables', () => {
|
|
192
257
|
assert.equal(isContextModeEnabled(undefined), true, 'undefined prefs → on');
|
|
193
258
|
assert.equal(isContextModeEnabled(null), true, 'null prefs → on');
|
|
@@ -13,6 +13,9 @@ import { parseRoadmap, parsePlan } from '../parsers-legacy.ts';
|
|
|
13
13
|
import { parseSummary } from '../files.ts';
|
|
14
14
|
import { deriveState } from '../state.ts';
|
|
15
15
|
import { invalidateAllCaches } from '../cache.ts';
|
|
16
|
+
import { ensureDbOpen } from '../bootstrap/dynamic-tools.ts';
|
|
17
|
+
import { closeDatabase, getAllMilestones } from '../gsd-db.ts';
|
|
18
|
+
import { importWrittenMigrationToDb } from '../migrate/command.ts';
|
|
16
19
|
import type {
|
|
17
20
|
GSDProject,
|
|
18
21
|
GSDMilestone,
|
|
@@ -250,6 +253,51 @@ test('Scenario 1: Incomplete project — write, parse, deriveState', async () =>
|
|
|
250
253
|
}
|
|
251
254
|
});
|
|
252
255
|
|
|
256
|
+
test('Scenario 1b: written migration imports into authoritative DB state', async () => {
|
|
257
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-import-'));
|
|
258
|
+
try {
|
|
259
|
+
const project = buildIncompleteProject();
|
|
260
|
+
await writeGSDDirectory(project, base);
|
|
261
|
+
|
|
262
|
+
assert.equal(await ensureDbOpen(base), true, 'db import: ensureDbOpen creates authoritative DB');
|
|
263
|
+
|
|
264
|
+
invalidateAllCaches();
|
|
265
|
+
const before = await deriveState(base);
|
|
266
|
+
assert.equal(before.activeMilestone, null, 'db import: markdown-only migration is invisible before DB import');
|
|
267
|
+
|
|
268
|
+
const imported = await importWrittenMigrationToDb(base);
|
|
269
|
+
assert.deepStrictEqual(imported.hierarchy, { milestones: 1, slices: 2, tasks: 3 }, 'db import: hierarchy counts');
|
|
270
|
+
|
|
271
|
+
invalidateAllCaches();
|
|
272
|
+
const after = await deriveState(base);
|
|
273
|
+
assert.deepStrictEqual(after.phase, 'executing', 'db import: deriveState sees imported DB hierarchy');
|
|
274
|
+
assert.deepStrictEqual(after.activeMilestone?.id, 'M001', 'db import: active milestone');
|
|
275
|
+
assert.deepStrictEqual(after.activeSlice?.id, 'S02', 'db import: active slice');
|
|
276
|
+
assert.deepStrictEqual(after.activeTask?.id, 'T03', 'db import: active task');
|
|
277
|
+
} finally {
|
|
278
|
+
closeDatabase();
|
|
279
|
+
rmSync(base, { recursive: true, force: true });
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('Scenario 1c: DB import verification fails when preview counts do not match', async () => {
|
|
284
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-check-'));
|
|
285
|
+
try {
|
|
286
|
+
const project = buildIncompleteProject();
|
|
287
|
+
await writeGSDDirectory(project, base);
|
|
288
|
+
|
|
289
|
+
const preview = generatePreview(project);
|
|
290
|
+
await assert.rejects(
|
|
291
|
+
() => importWrittenMigrationToDb(base, { ...preview, totalTasks: preview.totalTasks + 1 }),
|
|
292
|
+
/migration DB import verification failed: tasks 3\/4/,
|
|
293
|
+
);
|
|
294
|
+
assert.deepStrictEqual(getAllMilestones(), [], 'db import: failed verification rolls back hierarchy rewrite');
|
|
295
|
+
} finally {
|
|
296
|
+
closeDatabase();
|
|
297
|
+
rmSync(base, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
253
301
|
// ─── Scenario 2: Fully complete project ────────────────────────────────
|
|
254
302
|
|
|
255
303
|
test('Scenario 2: Fully complete project — deriveState phase', async () => {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { ensureDbOpen } from "../bootstrap/dynamic-tools.ts";
|
|
8
|
+
import {
|
|
9
|
+
_getAdapter,
|
|
10
|
+
closeDatabase,
|
|
11
|
+
getAllMilestones,
|
|
12
|
+
getSliceTasks,
|
|
13
|
+
} from "../gsd-db.ts";
|
|
14
|
+
import {
|
|
15
|
+
autoImportMarkdownHierarchyIfDbMismatch,
|
|
16
|
+
countMarkdownHierarchy,
|
|
17
|
+
} from "../migration-auto-check.ts";
|
|
18
|
+
import { writeGSDDirectory } from "../migrate/writer.ts";
|
|
19
|
+
import type { GSDProject } from "../migrate/types.ts";
|
|
20
|
+
|
|
21
|
+
function makeBase(): string {
|
|
22
|
+
return mkdtempSync(join(tmpdir(), "gsd-migration-auto-check-"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanup(base: string): void {
|
|
26
|
+
closeDatabase();
|
|
27
|
+
rmSync(base, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function projectFixture(): GSDProject {
|
|
31
|
+
return {
|
|
32
|
+
projectContent: "# Legacy Project\n",
|
|
33
|
+
decisionsContent: "",
|
|
34
|
+
requirements: [],
|
|
35
|
+
milestones: [
|
|
36
|
+
{
|
|
37
|
+
id: "M001",
|
|
38
|
+
title: "Legacy Milestone",
|
|
39
|
+
vision: "Carry forward previous work",
|
|
40
|
+
successCriteria: ["Existing task is visible"],
|
|
41
|
+
research: null,
|
|
42
|
+
boundaryMap: [],
|
|
43
|
+
slices: [
|
|
44
|
+
{
|
|
45
|
+
id: "S01",
|
|
46
|
+
title: "Legacy Slice",
|
|
47
|
+
risk: "medium",
|
|
48
|
+
depends: [],
|
|
49
|
+
done: false,
|
|
50
|
+
demo: "Legacy slice demo",
|
|
51
|
+
goal: "Legacy slice demo",
|
|
52
|
+
research: null,
|
|
53
|
+
summary: null,
|
|
54
|
+
tasks: [
|
|
55
|
+
{
|
|
56
|
+
id: "T01",
|
|
57
|
+
title: "Legacy Task",
|
|
58
|
+
description: "Task carried from markdown",
|
|
59
|
+
done: false,
|
|
60
|
+
estimate: "",
|
|
61
|
+
files: ["src/index.ts"],
|
|
62
|
+
mustHaves: [],
|
|
63
|
+
summary: null,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("migration auto-check imports markdown hierarchy when DB is empty", async () => {
|
|
74
|
+
const base = makeBase();
|
|
75
|
+
try {
|
|
76
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
77
|
+
assert.deepEqual(countMarkdownHierarchy(base), { milestones: 1, slices: 1, tasks: 1 });
|
|
78
|
+
|
|
79
|
+
assert.equal(await ensureDbOpen(base), true);
|
|
80
|
+
assert.equal(getAllMilestones().length, 0, "fresh authoritative DB starts empty");
|
|
81
|
+
|
|
82
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
83
|
+
assert.equal(result.action, "imported");
|
|
84
|
+
assert.equal(result.reason, "db-empty");
|
|
85
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
86
|
+
assert.equal(getAllMilestones().length, 1);
|
|
87
|
+
assert.equal(getSliceTasks("M001", "S01").length, 1);
|
|
88
|
+
} finally {
|
|
89
|
+
cleanup(base);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("migration auto-check repairs DB hierarchy count mismatch", async () => {
|
|
94
|
+
const base = makeBase();
|
|
95
|
+
try {
|
|
96
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
97
|
+
await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
98
|
+
|
|
99
|
+
_getAdapter()!.prepare("DELETE FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?").run("M001", "S01", "T01");
|
|
100
|
+
assert.equal(getSliceTasks("M001", "S01").length, 0, "test fixture simulates stale DB task count");
|
|
101
|
+
|
|
102
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
103
|
+
assert.equal(result.action, "imported");
|
|
104
|
+
assert.equal(result.reason, "count-mismatch");
|
|
105
|
+
assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 0 });
|
|
106
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
107
|
+
assert.equal(getSliceTasks("M001", "S01").length, 1);
|
|
108
|
+
} finally {
|
|
109
|
+
cleanup(base);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("migration auto-check leaves matching DB hierarchy alone", async () => {
|
|
114
|
+
const base = makeBase();
|
|
115
|
+
try {
|
|
116
|
+
await writeGSDDirectory(projectFixture(), base);
|
|
117
|
+
await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
118
|
+
|
|
119
|
+
const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
|
|
120
|
+
assert.equal(result.action, "none");
|
|
121
|
+
assert.equal(result.reason, "in-sync");
|
|
122
|
+
assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
|
|
123
|
+
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
|
|
124
|
+
} finally {
|
|
125
|
+
cleanup(base);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -172,6 +172,56 @@ test('handlePlanSlice rejects invalid payloads', async () => {
|
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
+
test('handlePlanSlice rejects absolute task IO paths outside the active worktree', async () => {
|
|
176
|
+
const base = makeTmpBase();
|
|
177
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
seedParentSlice();
|
|
181
|
+
const outside = join(tmpdir(), 'outside-checkout', 'index.html');
|
|
182
|
+
const result = await handlePlanSlice({
|
|
183
|
+
...validParams(),
|
|
184
|
+
tasks: [
|
|
185
|
+
{
|
|
186
|
+
...validParams().tasks[0],
|
|
187
|
+
inputs: [outside],
|
|
188
|
+
expectedOutput: [outside],
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
}, base);
|
|
192
|
+
|
|
193
|
+
assert.ok('error' in result);
|
|
194
|
+
assert.match(result.error, /validation failed: tasks\[0\]\.inputs contains absolute path outside working directory/);
|
|
195
|
+
assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid planning IO must not persist tasks');
|
|
196
|
+
} finally {
|
|
197
|
+
cleanup(base);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('handlePlanSlice accepts absolute task IO paths inside the active worktree', async () => {
|
|
202
|
+
const base = makeTmpBase();
|
|
203
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
seedParentSlice();
|
|
207
|
+
const inside = join(base, 'index.html');
|
|
208
|
+
const result = await handlePlanSlice({
|
|
209
|
+
...validParams(),
|
|
210
|
+
tasks: [
|
|
211
|
+
{
|
|
212
|
+
...validParams().tasks[0],
|
|
213
|
+
inputs: [inside],
|
|
214
|
+
expectedOutput: [inside],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
}, base);
|
|
218
|
+
|
|
219
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
220
|
+
} finally {
|
|
221
|
+
cleanup(base);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
175
225
|
test('handlePlanSlice rejects missing parent slice', async () => {
|
|
176
226
|
const base = makeTmpBase();
|
|
177
227
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -79,6 +79,27 @@ test('handlePlanTask rejects invalid payloads', async () => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
|
|
83
|
+
const base = makeTmpBase();
|
|
84
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
seedParent();
|
|
88
|
+
const outside = join(tmpdir(), 'outside-checkout', 'index.html');
|
|
89
|
+
const result = await handlePlanTask({
|
|
90
|
+
...validParams(),
|
|
91
|
+
inputs: [outside],
|
|
92
|
+
expectedOutput: [outside],
|
|
93
|
+
}, base);
|
|
94
|
+
|
|
95
|
+
assert.ok('error' in result);
|
|
96
|
+
assert.match(result.error, /validation failed: inputs contains absolute path outside working directory/);
|
|
97
|
+
assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid planning IO must not persist the task');
|
|
98
|
+
} finally {
|
|
99
|
+
cleanup(base);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
82
103
|
test('handlePlanTask rejects missing parent slice', async () => {
|
|
83
104
|
const base = makeTmpBase();
|
|
84
105
|
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const promptsDir = join(__dirname, "..", "prompts");
|
|
11
|
+
|
|
12
|
+
test("prompt templates do not reference legacy milestone-root .gsd paths", () => {
|
|
13
|
+
const offenders: string[] = [];
|
|
14
|
+
for (const file of readdirSync(promptsDir)) {
|
|
15
|
+
if (!file.endsWith(".md")) continue;
|
|
16
|
+
const content = readFileSync(join(promptsDir, file), "utf-8");
|
|
17
|
+
const legacyPatterns = [
|
|
18
|
+
/\.gsd\/\{\{(?:milestoneId|mid)\}\}\//g,
|
|
19
|
+
/\.gsd\/<milestone-id>\//g,
|
|
20
|
+
/\.gsd\/<ID>\//g,
|
|
21
|
+
];
|
|
22
|
+
for (const pattern of legacyPatterns) {
|
|
23
|
+
if (pattern.test(content)) {
|
|
24
|
+
offenders.push(`${file}: ${pattern.source}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
assert.deepEqual(
|
|
30
|
+
offenders,
|
|
31
|
+
[],
|
|
32
|
+
"Milestone artifacts must use .gsd/milestones/<MID>/..., not legacy .gsd/<MID>/...",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("quick task prompt delegates commit policy to quick.ts", () => {
|
|
37
|
+
const content = readFileSync(join(promptsDir, "quick-task.md"), "utf-8");
|
|
38
|
+
assert.match(content, /\{\{commitInstruction\}\}/);
|
|
39
|
+
assert.doesNotMatch(content, /Stage only relevant files/);
|
|
40
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { buildQuickCommitInstruction } from "../quick.ts";
|
|
8
|
+
|
|
9
|
+
function git(cwd: string, args: string[]): void {
|
|
10
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
11
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("quick task commit instruction does not ask agents to stage external .gsd quick files", { skip: process.platform === "win32" }, () => {
|
|
15
|
+
const tempRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-quick-ext-")));
|
|
16
|
+
const repo = join(tempRoot, "repo");
|
|
17
|
+
const externalGsd = join(tempRoot, "state");
|
|
18
|
+
mkdirSync(repo);
|
|
19
|
+
mkdirSync(externalGsd);
|
|
20
|
+
|
|
21
|
+
const previousCwd = process.cwd();
|
|
22
|
+
try {
|
|
23
|
+
git(repo, ["init"]);
|
|
24
|
+
git(repo, ["config", "user.email", "test@example.com"]);
|
|
25
|
+
git(repo, ["config", "user.name", "Test User"]);
|
|
26
|
+
writeFileSync(join(repo, "README.md"), "# Test\n", "utf-8");
|
|
27
|
+
git(repo, ["add", "README.md"]);
|
|
28
|
+
git(repo, ["commit", "-m", "init"]);
|
|
29
|
+
symlinkSync(externalGsd, join(repo, ".gsd"), "dir");
|
|
30
|
+
|
|
31
|
+
const instruction = buildQuickCommitInstruction(repo, join(repo, ".gsd"));
|
|
32
|
+
|
|
33
|
+
assert.match(instruction, /do not stage or commit `\.gsd\/quick\/\.\.\.`/);
|
|
34
|
+
assert.match(instruction, /nothing in the project repo to commit/);
|
|
35
|
+
assert.match(instruction, /Write the quick summary file directly/);
|
|
36
|
+
} finally {
|
|
37
|
+
process.chdir(previousCwd);
|
|
38
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -59,6 +59,26 @@ test("guided-flow complete branch offers a chooser for next milestone or status"
|
|
|
59
59
|
assert.match(branchChunk, /dispatchWorkflow\(pi, await prepareAndBuildDiscussPrompt\(/, "complete branch should dispatch the prepared discuss prompt");
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
test("guided-flow quick task choices prompt for text and route through /gsd quick", () => {
|
|
63
|
+
const guidedFlowSource = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
|
|
64
|
+
|
|
65
|
+
assert.match(
|
|
66
|
+
guidedFlowSource,
|
|
67
|
+
/async function runQuickTaskChoice\([\s\S]*ctx\.ui\.input\("Quick task"[\s\S]*await import\("\.\/quick\.js"\)[\s\S]*await handleQuick\(task,\s*ctx,\s*pi\)/,
|
|
68
|
+
"quick task chooser should prompt for task text and route through handleQuick",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const notifyOnlyPattern = /if \(choice === "quick_task"\) \{\s*ctx\.ui\.notify\("Run \/gsd quick <task>/;
|
|
72
|
+
assert.doesNotMatch(
|
|
73
|
+
guidedFlowSource,
|
|
74
|
+
notifyOnlyPattern,
|
|
75
|
+
"quick task chooser must not merely print instructions after selection",
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const quickChoiceCalls = guidedFlowSource.match(/if \(choice === "quick_task"\) \{\s*await runQuickTaskChoice\(ctx,\s*pi\);/g) ?? [];
|
|
79
|
+
assert.equal(quickChoiceCalls.length, 3, "all guided-flow quick task choices should dispatch the helper");
|
|
80
|
+
});
|
|
81
|
+
|
|
62
82
|
test("dispatcher routes multi-word freeform /gsd input through /gsd do", () => {
|
|
63
83
|
const dispatcherSource = readFileSync(join(import.meta.dirname, "..", "commands", "dispatcher.ts"), "utf-8");
|
|
64
84
|
|
|
@@ -96,6 +96,23 @@ test("auto bootstrap validates blocked directories before touching .gsd migratio
|
|
|
96
96
|
);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("fresh start registers the auto worker before bootstrap enters worktree flow (#5405)", () => {
|
|
100
|
+
const autoSrc = readGsdFile("auto.ts");
|
|
101
|
+
const startAutoIdx = autoSrc.indexOf("export async function startAuto(");
|
|
102
|
+
const startAutoBody = autoSrc.slice(startAutoIdx);
|
|
103
|
+
|
|
104
|
+
const preBootstrapRegisterIdx = startAutoBody.indexOf("registerAutoWorkerForSession(s, base);");
|
|
105
|
+
const bootstrapCallIdx = startAutoBody.indexOf("const ready = await bootstrapAutoSession(");
|
|
106
|
+
|
|
107
|
+
assert.ok(startAutoIdx > -1, "startAuto should exist");
|
|
108
|
+
assert.ok(preBootstrapRegisterIdx > -1, "startAuto should register worker before bootstrap");
|
|
109
|
+
assert.ok(bootstrapCallIdx > -1, "startAuto should call bootstrapAutoSession");
|
|
110
|
+
assert.ok(
|
|
111
|
+
preBootstrapRegisterIdx < bootstrapCallIdx,
|
|
112
|
+
"worker registration must happen before bootstrap so enterMilestone can claim milestone leases on first entry",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
99
116
|
test("startAutoDetached reports failures asynchronously (#3733)", () => {
|
|
100
117
|
const autoSrc = readGsdFile("auto.ts");
|
|
101
118
|
|
|
@@ -44,4 +44,13 @@ describe('status opens DB before deriveState (#3691)', () => {
|
|
|
44
44
|
assert.match(quickSrc, /getIsolationMode\(\)\s*!==\s*"none"/,
|
|
45
45
|
'quick.ts should compare isolation mode against "none"');
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
test('quick task prompt handles external .gsd without staging quick files', () => {
|
|
49
|
+
assert.match(quickSrc, /isExternalGsdRoot/,
|
|
50
|
+
'quick.ts should detect whether .gsd resolves outside the project repo');
|
|
51
|
+
assert.match(quickSrc, /do not stage or commit `\.gsd\/quick\/\.\.\.`/,
|
|
52
|
+
'external-state quick tasks must tell the agent not to stage .gsd/quick files');
|
|
53
|
+
assert.match(quickSrc, /nothing in the project repo to commit/,
|
|
54
|
+
'external-state quick tasks should allow summary-only work without a git commit');
|
|
55
|
+
});
|
|
47
56
|
});
|