gsd-pi 2.37.1 → 2.38.0-dev.29edcdc
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 +1 -1
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +75 -10
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +111 -68
- package/dist/resources/extensions/gsd/auto-prompts.js +114 -45
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +24 -3
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +47 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +3 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +100 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +92 -42
- package/src/resources/extensions/gsd/auto-prompts.ts +150 -48
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +26 -4
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +50 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +9 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const promptsDir = join(process.cwd(), "src/resources/extensions/gsd/prompts");
|
|
7
|
+
|
|
8
|
+
function readPrompt(name: string): string {
|
|
9
|
+
return readFileSync(join(promptsDir, `${name}.md`), "utf-8");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test("reactive-execute prompt keeps task summaries with subagents and avoids batch commits", () => {
|
|
13
|
+
const prompt = readPrompt("reactive-execute");
|
|
14
|
+
assert.match(prompt, /subagent-written summary as authoritative/i);
|
|
15
|
+
assert.match(prompt, /Do NOT create a batch commit/i);
|
|
16
|
+
assert.doesNotMatch(prompt, /\*\*Write task summaries\*\*/i);
|
|
17
|
+
assert.doesNotMatch(prompt, /\*\*Commit\*\* all changes/i);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("run-uat prompt branches on dynamic UAT mode and supports runtime evidence", () => {
|
|
21
|
+
const prompt = readPrompt("run-uat");
|
|
22
|
+
assert.match(prompt, /\*\*Detected UAT mode:\*\*\s*`\{\{uatType\}\}`/);
|
|
23
|
+
assert.match(prompt, /uatType:\s*\{\{uatType\}\}/);
|
|
24
|
+
assert.match(prompt, /live-runtime/);
|
|
25
|
+
assert.match(prompt, /browser\/runtime\/network/i);
|
|
26
|
+
assert.match(prompt, /NEEDS-HUMAN/);
|
|
27
|
+
assert.doesNotMatch(prompt, /uatType:\s*artifact-driven/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("workflow-start prompt defaults to autonomy instead of per-phase confirmation", () => {
|
|
31
|
+
const prompt = readPrompt("workflow-start");
|
|
32
|
+
assert.match(prompt, /Keep moving by default/i);
|
|
33
|
+
assert.match(prompt, /Decision gates, not ceremony/i);
|
|
34
|
+
assert.doesNotMatch(prompt, /confirm with the user before proceeding/i);
|
|
35
|
+
assert.doesNotMatch(prompt, /Gate between phases/i);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("discuss prompt allows implementation questions when they materially matter", () => {
|
|
39
|
+
const prompt = readPrompt("discuss");
|
|
40
|
+
assert.match(prompt, /Lead with experience, but ask implementation when it materially matters/i);
|
|
41
|
+
assert.match(prompt, /one gate, not two/i);
|
|
42
|
+
assert.doesNotMatch(prompt, /Questions must be about the experience, not the implementation/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("guided discussion prompts avoid wrap-up prompts after every round", () => {
|
|
46
|
+
const milestonePrompt = readPrompt("guided-discuss-milestone");
|
|
47
|
+
const slicePrompt = readPrompt("guided-discuss-slice");
|
|
48
|
+
assert.match(milestonePrompt, /Do \*\*not\*\* ask a meta "ready to wrap up\?" question after every round/i);
|
|
49
|
+
assert.match(slicePrompt, /Do \*\*not\*\* ask a meta "ready to wrap up\?" question after every round/i);
|
|
50
|
+
assert.doesNotMatch(milestonePrompt, /I think I have a solid picture of this milestone\. Ready to wrap up/i);
|
|
51
|
+
assert.doesNotMatch(slicePrompt, /I think I have a solid picture of this slice\. Ready to wrap up/i);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("guided-resume-task prompt preserves recovery state until work is superseded", () => {
|
|
55
|
+
const prompt = readPrompt("guided-resume-task");
|
|
56
|
+
assert.match(prompt, /Do \*\*not\*\* delete the continue file immediately/i);
|
|
57
|
+
assert.match(prompt, /successfully completed or you have written a newer summary\/continue artifact/i);
|
|
58
|
+
assert.doesNotMatch(prompt, /Delete the continue file after reading it/i);
|
|
59
|
+
});
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import {
|
|
7
|
+
loadSliceTaskIO,
|
|
8
|
+
deriveTaskGraph,
|
|
9
|
+
isGraphAmbiguous,
|
|
10
|
+
getReadyTasks,
|
|
11
|
+
chooseNonConflictingSubset,
|
|
12
|
+
loadReactiveState,
|
|
13
|
+
saveReactiveState,
|
|
14
|
+
clearReactiveState,
|
|
15
|
+
} from "../reactive-graph.ts";
|
|
16
|
+
import { validatePreferences } from "../preferences-validation.ts";
|
|
17
|
+
import type { ReactiveExecutionState } from "../types.ts";
|
|
18
|
+
|
|
19
|
+
// ─── Preference Validation ────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
test("reactive_execution validation accepts valid config", () => {
|
|
22
|
+
const result = validatePreferences({
|
|
23
|
+
reactive_execution: {
|
|
24
|
+
enabled: true,
|
|
25
|
+
max_parallel: 4,
|
|
26
|
+
isolation_mode: "same-tree",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
assert.equal(result.errors.length, 0);
|
|
30
|
+
assert.deepEqual(result.preferences.reactive_execution, {
|
|
31
|
+
enabled: true,
|
|
32
|
+
max_parallel: 4,
|
|
33
|
+
isolation_mode: "same-tree",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("reactive_execution validation rejects max_parallel out of range", () => {
|
|
38
|
+
const result = validatePreferences({
|
|
39
|
+
reactive_execution: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
max_parallel: 10,
|
|
42
|
+
isolation_mode: "same-tree",
|
|
43
|
+
} as any,
|
|
44
|
+
});
|
|
45
|
+
assert.ok(result.errors.some((e) => e.includes("max_parallel")));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("reactive_execution validation rejects invalid isolation_mode", () => {
|
|
49
|
+
const result = validatePreferences({
|
|
50
|
+
reactive_execution: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
max_parallel: 2,
|
|
53
|
+
isolation_mode: "separate-branch",
|
|
54
|
+
} as any,
|
|
55
|
+
});
|
|
56
|
+
assert.ok(result.errors.some((e) => e.includes("isolation_mode")));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("reactive_execution validation warns on unknown keys", () => {
|
|
60
|
+
const result = validatePreferences({
|
|
61
|
+
reactive_execution: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
max_parallel: 2,
|
|
64
|
+
isolation_mode: "same-tree",
|
|
65
|
+
unknown_thing: true,
|
|
66
|
+
} as any,
|
|
67
|
+
});
|
|
68
|
+
assert.equal(result.errors.length, 0);
|
|
69
|
+
assert.ok(result.warnings.some((w) => w.includes("unknown_thing")));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── Dispatch Rule Matching Logic ─────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
test("reactive dispatch requires enabled config and multiple ready tasks", async () => {
|
|
75
|
+
// Build a minimal filesystem with a slice plan and task plans
|
|
76
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-dispatch-"));
|
|
77
|
+
try {
|
|
78
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
79
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
80
|
+
|
|
81
|
+
// Slice plan with 3 tasks
|
|
82
|
+
writeFileSync(
|
|
83
|
+
join(gsd, "S01-PLAN.md"),
|
|
84
|
+
[
|
|
85
|
+
"# S01: Test Slice",
|
|
86
|
+
"",
|
|
87
|
+
"**Goal:** Test reactive execution",
|
|
88
|
+
"**Demo:** All three tasks run in parallel",
|
|
89
|
+
"",
|
|
90
|
+
"## Tasks",
|
|
91
|
+
"",
|
|
92
|
+
"- [ ] **T01: First** `est:15m`",
|
|
93
|
+
" Create initial types",
|
|
94
|
+
"- [ ] **T02: Second** `est:15m`",
|
|
95
|
+
" Create models",
|
|
96
|
+
"- [ ] **T03: Third** `est:15m`",
|
|
97
|
+
" Create service layer",
|
|
98
|
+
"",
|
|
99
|
+
].join("\n"),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Task plans with non-overlapping IO (all independent)
|
|
103
|
+
writeFileSync(
|
|
104
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
105
|
+
[
|
|
106
|
+
"# T01: First",
|
|
107
|
+
"",
|
|
108
|
+
"## Description",
|
|
109
|
+
"Create types.",
|
|
110
|
+
"",
|
|
111
|
+
"## Inputs",
|
|
112
|
+
"",
|
|
113
|
+
"- `src/config.json` — Config schema",
|
|
114
|
+
"",
|
|
115
|
+
"## Expected Output",
|
|
116
|
+
"",
|
|
117
|
+
"- `src/types.ts` — Type definitions",
|
|
118
|
+
].join("\n"),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(gsd, "tasks", "T02-PLAN.md"),
|
|
123
|
+
[
|
|
124
|
+
"# T02: Second",
|
|
125
|
+
"",
|
|
126
|
+
"## Description",
|
|
127
|
+
"Create models.",
|
|
128
|
+
"",
|
|
129
|
+
"## Inputs",
|
|
130
|
+
"",
|
|
131
|
+
"- `src/schema.json` — Schema file",
|
|
132
|
+
"",
|
|
133
|
+
"## Expected Output",
|
|
134
|
+
"",
|
|
135
|
+
"- `src/models.ts` — Model definitions",
|
|
136
|
+
].join("\n"),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
writeFileSync(
|
|
140
|
+
join(gsd, "tasks", "T03-PLAN.md"),
|
|
141
|
+
[
|
|
142
|
+
"# T03: Third",
|
|
143
|
+
"",
|
|
144
|
+
"## Description",
|
|
145
|
+
"Create service.",
|
|
146
|
+
"",
|
|
147
|
+
"## Inputs",
|
|
148
|
+
"",
|
|
149
|
+
"- `src/api.json` — API spec",
|
|
150
|
+
"",
|
|
151
|
+
"## Expected Output",
|
|
152
|
+
"",
|
|
153
|
+
"- `src/service.ts` — Service layer",
|
|
154
|
+
].join("\n"),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Load IO and build graph
|
|
158
|
+
const basePath = repo;
|
|
159
|
+
const taskIO = await loadSliceTaskIO(basePath, "M001", "S01");
|
|
160
|
+
assert.equal(taskIO.length, 3);
|
|
161
|
+
|
|
162
|
+
const graph = deriveTaskGraph(taskIO);
|
|
163
|
+
assert.equal(isGraphAmbiguous(graph), false, "Graph should not be ambiguous");
|
|
164
|
+
|
|
165
|
+
// All independent → all should be ready
|
|
166
|
+
const ready = getReadyTasks(graph, new Set(), new Set());
|
|
167
|
+
assert.equal(ready.length, 3);
|
|
168
|
+
|
|
169
|
+
// Choose subset with max_parallel=2
|
|
170
|
+
const selected = chooseNonConflictingSubset(ready, graph, 2, new Set());
|
|
171
|
+
assert.equal(selected.length, 2);
|
|
172
|
+
assert.deepEqual(selected, ["T01", "T02"]);
|
|
173
|
+
} finally {
|
|
174
|
+
rmSync(repo, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("reactive dispatch falls back when graph is ambiguous (task without IO)", async () => {
|
|
179
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-ambiguous-"));
|
|
180
|
+
try {
|
|
181
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
182
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
183
|
+
|
|
184
|
+
writeFileSync(
|
|
185
|
+
join(gsd, "S01-PLAN.md"),
|
|
186
|
+
[
|
|
187
|
+
"# S01: Test",
|
|
188
|
+
"",
|
|
189
|
+
"**Goal:** Test",
|
|
190
|
+
"**Demo:** Test",
|
|
191
|
+
"",
|
|
192
|
+
"## Tasks",
|
|
193
|
+
"",
|
|
194
|
+
"- [ ] **T01: A** `est:15m`",
|
|
195
|
+
"- [ ] **T02: B** `est:15m`",
|
|
196
|
+
"",
|
|
197
|
+
].join("\n"),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// T01 has IO, T02 has NO IO sections → ambiguous
|
|
201
|
+
writeFileSync(
|
|
202
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
203
|
+
"# T01: A\n\n## Inputs\n\n- `src/a.ts`\n\n## Expected Output\n\n- `src/b.ts`\n",
|
|
204
|
+
);
|
|
205
|
+
writeFileSync(
|
|
206
|
+
join(gsd, "tasks", "T02-PLAN.md"),
|
|
207
|
+
"# T02: B\n\n## Description\n\nNo IO sections.\n",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const taskIO = await loadSliceTaskIO(repo, "M001", "S01");
|
|
211
|
+
const graph = deriveTaskGraph(taskIO);
|
|
212
|
+
assert.equal(isGraphAmbiguous(graph), true, "Graph should be ambiguous");
|
|
213
|
+
} finally {
|
|
214
|
+
rmSync(repo, { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("single ready task falls through to sequential", async () => {
|
|
219
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-single-"));
|
|
220
|
+
try {
|
|
221
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
222
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
223
|
+
|
|
224
|
+
writeFileSync(
|
|
225
|
+
join(gsd, "S01-PLAN.md"),
|
|
226
|
+
[
|
|
227
|
+
"# S01: Linear",
|
|
228
|
+
"",
|
|
229
|
+
"**Goal:** Linear chain",
|
|
230
|
+
"**Demo:** Sequential",
|
|
231
|
+
"",
|
|
232
|
+
"## Tasks",
|
|
233
|
+
"",
|
|
234
|
+
"- [ ] **T01: First** `est:15m`",
|
|
235
|
+
"- [ ] **T02: Second** `est:15m`",
|
|
236
|
+
"",
|
|
237
|
+
].join("\n"),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
writeFileSync(
|
|
241
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
242
|
+
"# T01: First\n\n## Inputs\n\n- `src/config.json`\n\n## Expected Output\n\n- `src/a.ts`\n",
|
|
243
|
+
);
|
|
244
|
+
writeFileSync(
|
|
245
|
+
join(gsd, "tasks", "T02-PLAN.md"),
|
|
246
|
+
"# T02: Second\n\n## Inputs\n\n- `src/a.ts`\n\n## Expected Output\n\n- `src/b.ts`\n",
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const taskIO = await loadSliceTaskIO(repo, "M001", "S01");
|
|
250
|
+
const graph = deriveTaskGraph(taskIO);
|
|
251
|
+
const ready = getReadyTasks(graph, new Set(), new Set());
|
|
252
|
+
// Only T01 is ready (T02 depends on T01)
|
|
253
|
+
assert.equal(ready.length, 1);
|
|
254
|
+
assert.deepEqual(ready, ["T01"]);
|
|
255
|
+
} finally {
|
|
256
|
+
rmSync(repo, { recursive: true, force: true });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ─── State Persistence ────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
test("saveReactiveState and loadReactiveState round-trip", () => {
|
|
263
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-state-"));
|
|
264
|
+
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
|
265
|
+
try {
|
|
266
|
+
const state: ReactiveExecutionState = {
|
|
267
|
+
sliceId: "S01",
|
|
268
|
+
completed: ["T01", "T02"],
|
|
269
|
+
dispatched: ["T03"],
|
|
270
|
+
graphSnapshot: { taskCount: 4, edgeCount: 2, readySetSize: 1, ambiguous: false },
|
|
271
|
+
updatedAt: "2025-01-01T00:00:00Z",
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
saveReactiveState(repo, "M001", "S01", state);
|
|
275
|
+
const loaded = loadReactiveState(repo, "M001", "S01");
|
|
276
|
+
assert.deepEqual(loaded, state);
|
|
277
|
+
} finally {
|
|
278
|
+
rmSync(repo, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("clearReactiveState removes the file", () => {
|
|
283
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-clear-"));
|
|
284
|
+
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
|
285
|
+
try {
|
|
286
|
+
const state: ReactiveExecutionState = {
|
|
287
|
+
sliceId: "S01",
|
|
288
|
+
completed: [],
|
|
289
|
+
dispatched: ["T01", "T02"],
|
|
290
|
+
graphSnapshot: { taskCount: 2, edgeCount: 0, readySetSize: 2, ambiguous: false },
|
|
291
|
+
updatedAt: "2025-01-01T00:00:00Z",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
saveReactiveState(repo, "M001", "S01", state);
|
|
295
|
+
assert.ok(existsSync(join(repo, ".gsd", "runtime", "M001-S01-reactive.json")));
|
|
296
|
+
|
|
297
|
+
clearReactiveState(repo, "M001", "S01");
|
|
298
|
+
assert.ok(!existsSync(join(repo, ".gsd", "runtime", "M001-S01-reactive.json")));
|
|
299
|
+
} finally {
|
|
300
|
+
rmSync(repo, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("loadReactiveState returns null when no file exists", () => {
|
|
305
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-nofile-"));
|
|
306
|
+
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
|
307
|
+
try {
|
|
308
|
+
const loaded = loadReactiveState(repo, "M001", "S01");
|
|
309
|
+
assert.equal(loaded, null);
|
|
310
|
+
} finally {
|
|
311
|
+
rmSync(repo, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("completed tasks are not re-dispatched on next iteration", async () => {
|
|
316
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-reentry-"));
|
|
317
|
+
try {
|
|
318
|
+
const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
|
|
319
|
+
mkdirSync(join(gsd, "tasks"), { recursive: true });
|
|
320
|
+
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
|
321
|
+
|
|
322
|
+
writeFileSync(
|
|
323
|
+
join(gsd, "S01-PLAN.md"),
|
|
324
|
+
[
|
|
325
|
+
"# S01: Reentry Test",
|
|
326
|
+
"",
|
|
327
|
+
"**Goal:** Test re-entry",
|
|
328
|
+
"**Demo:** Correct resumption",
|
|
329
|
+
"",
|
|
330
|
+
"## Tasks",
|
|
331
|
+
"",
|
|
332
|
+
"- [x] **T01: Done** `est:15m`",
|
|
333
|
+
"- [ ] **T02: Pending** `est:15m`",
|
|
334
|
+
"- [ ] **T03: Also Pending** `est:15m`",
|
|
335
|
+
"",
|
|
336
|
+
].join("\n"),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
writeFileSync(
|
|
340
|
+
join(gsd, "tasks", "T01-PLAN.md"),
|
|
341
|
+
"# T01: Done\n\n## Inputs\n\n- `src/config.json`\n\n## Expected Output\n\n- `src/a.ts`\n",
|
|
342
|
+
);
|
|
343
|
+
writeFileSync(
|
|
344
|
+
join(gsd, "tasks", "T02-PLAN.md"),
|
|
345
|
+
"# T02: Pending\n\n## Inputs\n\n- `src/a.ts`\n\n## Expected Output\n\n- `src/b.ts`\n",
|
|
346
|
+
);
|
|
347
|
+
writeFileSync(
|
|
348
|
+
join(gsd, "tasks", "T03-PLAN.md"),
|
|
349
|
+
"# T03: Also Pending\n\n## Inputs\n\n- `src/a.ts`\n\n## Expected Output\n\n- `src/c.ts`\n",
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const taskIO = await loadSliceTaskIO(repo, "M001", "S01");
|
|
353
|
+
const graph = deriveTaskGraph(taskIO);
|
|
354
|
+
|
|
355
|
+
// T01 is done, T02 and T03 depend on T01
|
|
356
|
+
const completed = new Set(["T01"]);
|
|
357
|
+
const ready = getReadyTasks(graph, completed, new Set());
|
|
358
|
+
// Both T02 and T03 should be ready (T01 is complete)
|
|
359
|
+
assert.deepEqual(ready, ["T02", "T03"]);
|
|
360
|
+
|
|
361
|
+
// Simulate T02 completes, re-derive
|
|
362
|
+
completed.add("T02");
|
|
363
|
+
const ready2 = getReadyTasks(graph, completed, new Set());
|
|
364
|
+
// Only T03 should be ready
|
|
365
|
+
assert.deepEqual(ready2, ["T03"]);
|
|
366
|
+
} finally {
|
|
367
|
+
rmSync(repo, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ─── Batch Verification ───────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
test("verifyExpectedArtifact: reactive-execute passes when all dispatched summaries exist", async () => {
|
|
374
|
+
const { verifyExpectedArtifact } = await import("../auto-recovery.ts");
|
|
375
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-verify-pass-"));
|
|
376
|
+
try {
|
|
377
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
378
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
379
|
+
writeFileSync(join(tasksDir, "T02-SUMMARY.md"), "---\nid: T02\n---\n# T02: Done\n");
|
|
380
|
+
writeFileSync(join(tasksDir, "T03-SUMMARY.md"), "---\nid: T03\n---\n# T03: Done\n");
|
|
381
|
+
|
|
382
|
+
const result = verifyExpectedArtifact("reactive-execute", "M001/S01/reactive+T02,T03", repo);
|
|
383
|
+
assert.equal(result, true, "Should pass when all dispatched task summaries exist");
|
|
384
|
+
} finally {
|
|
385
|
+
rmSync(repo, { recursive: true, force: true });
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("verifyExpectedArtifact: reactive-execute fails when a dispatched summary is missing", async () => {
|
|
390
|
+
const { verifyExpectedArtifact } = await import("../auto-recovery.ts");
|
|
391
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-verify-fail-"));
|
|
392
|
+
try {
|
|
393
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
394
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
395
|
+
// Only T02 has a summary, T03 does not
|
|
396
|
+
writeFileSync(join(tasksDir, "T02-SUMMARY.md"), "---\nid: T02\n---\n# T02: Done\n");
|
|
397
|
+
|
|
398
|
+
const result = verifyExpectedArtifact("reactive-execute", "M001/S01/reactive+T02,T03", repo);
|
|
399
|
+
assert.equal(result, false, "Should fail when dispatched task T03 summary is missing");
|
|
400
|
+
} finally {
|
|
401
|
+
rmSync(repo, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("verifyExpectedArtifact: reactive-execute fails even with pre-existing summaries from other tasks", async () => {
|
|
406
|
+
const { verifyExpectedArtifact } = await import("../auto-recovery.ts");
|
|
407
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-verify-preexisting-"));
|
|
408
|
+
try {
|
|
409
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
410
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
411
|
+
// T01 summary exists from before, but T02 and T03 were dispatched
|
|
412
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# T01: Prior\n");
|
|
413
|
+
|
|
414
|
+
const result = verifyExpectedArtifact("reactive-execute", "M001/S01/reactive+T02,T03", repo);
|
|
415
|
+
assert.equal(result, false, "Pre-existing T01 summary should not satisfy T02,T03 batch");
|
|
416
|
+
} finally {
|
|
417
|
+
rmSync(repo, { recursive: true, force: true });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("verifyExpectedArtifact: reactive-execute legacy format (no batch IDs) falls back", async () => {
|
|
422
|
+
const { verifyExpectedArtifact } = await import("../auto-recovery.ts");
|
|
423
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-verify-legacy-"));
|
|
424
|
+
try {
|
|
425
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
426
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
427
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# T01\n");
|
|
428
|
+
|
|
429
|
+
// Legacy format without +batch suffix
|
|
430
|
+
const result = verifyExpectedArtifact("reactive-execute", "M001/S01/reactive", repo);
|
|
431
|
+
assert.equal(result, true, "Legacy format should fall back to any-summary check");
|
|
432
|
+
} finally {
|
|
433
|
+
rmSync(repo, { recursive: true, force: true });
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("unitId batch encoding round-trips correctly", () => {
|
|
438
|
+
const mid = "M001";
|
|
439
|
+
const sid = "S01";
|
|
440
|
+
const selected = ["T02", "T03", "T05"];
|
|
441
|
+
const unitId = `${mid}/${sid}/reactive+${selected.join(",")}`;
|
|
442
|
+
|
|
443
|
+
// Parse it back
|
|
444
|
+
const parts = unitId.split("/");
|
|
445
|
+
assert.equal(parts[0], "M001");
|
|
446
|
+
assert.equal(parts[1], "S01");
|
|
447
|
+
const batchPart = parts[2];
|
|
448
|
+
const plusIdx = batchPart.indexOf("+");
|
|
449
|
+
assert.ok(plusIdx > 0, "Should have + separator");
|
|
450
|
+
const batchIds = batchPart.slice(plusIdx + 1).split(",");
|
|
451
|
+
assert.deepEqual(batchIds, ["T02", "T03", "T05"]);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// ─── Dependency-Based Carry-Forward ───────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
test("getDependencyTaskSummaryPaths returns only dependency summaries", async () => {
|
|
457
|
+
const { getDependencyTaskSummaryPaths } = await import("../auto-prompts.ts");
|
|
458
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-depcarry-"));
|
|
459
|
+
try {
|
|
460
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
461
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
462
|
+
// T01, T02, T03 all have summaries
|
|
463
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# T01\n");
|
|
464
|
+
writeFileSync(join(tasksDir, "T02-SUMMARY.md"), "---\nid: T02\n---\n# T02\n");
|
|
465
|
+
writeFileSync(join(tasksDir, "T03-SUMMARY.md"), "---\nid: T03\n---\n# T03\n");
|
|
466
|
+
|
|
467
|
+
// T04 depends only on T01 and T03 — should NOT get T02
|
|
468
|
+
const paths = await getDependencyTaskSummaryPaths("M001", "S01", "T04", ["T01", "T03"], repo);
|
|
469
|
+
assert.equal(paths.length, 2, "Should get exactly 2 dependency summaries");
|
|
470
|
+
assert.ok(paths.some((p) => p.includes("T01-SUMMARY")), "Should include T01");
|
|
471
|
+
assert.ok(paths.some((p) => p.includes("T03-SUMMARY")), "Should include T03");
|
|
472
|
+
assert.ok(!paths.some((p) => p.includes("T02-SUMMARY")), "Should NOT include T02");
|
|
473
|
+
} finally {
|
|
474
|
+
rmSync(repo, { recursive: true, force: true });
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("getDependencyTaskSummaryPaths falls back to order-based for root tasks", async () => {
|
|
479
|
+
const { getDependencyTaskSummaryPaths } = await import("../auto-prompts.ts");
|
|
480
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-depcarry-root-"));
|
|
481
|
+
try {
|
|
482
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
483
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
484
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# T01\n");
|
|
485
|
+
|
|
486
|
+
// T02 has no dependencies (root task) — should fall back to order-based
|
|
487
|
+
const paths = await getDependencyTaskSummaryPaths("M001", "S01", "T02", [], repo);
|
|
488
|
+
assert.equal(paths.length, 1, "Root task should get order-based prior summaries");
|
|
489
|
+
assert.ok(paths[0].includes("T01-SUMMARY"), "Should include T01 via order fallback");
|
|
490
|
+
} finally {
|
|
491
|
+
rmSync(repo, { recursive: true, force: true });
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("getDependencyTaskSummaryPaths handles missing dependency summaries gracefully", async () => {
|
|
496
|
+
const { getDependencyTaskSummaryPaths } = await import("../auto-prompts.ts");
|
|
497
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-reactive-depcarry-missing-"));
|
|
498
|
+
try {
|
|
499
|
+
const tasksDir = join(repo, ".gsd", "milestones", "M001", "slices", "S01", "tasks");
|
|
500
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
501
|
+
// Only T01 has a summary, T02 does not
|
|
502
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# T01\n");
|
|
503
|
+
|
|
504
|
+
// T03 depends on T01 and T02, but T02 summary doesn't exist
|
|
505
|
+
const paths = await getDependencyTaskSummaryPaths("M001", "S01", "T03", ["T01", "T02"], repo);
|
|
506
|
+
assert.equal(paths.length, 1, "Should only return existing dependency summaries");
|
|
507
|
+
assert.ok(paths[0].includes("T01-SUMMARY"), "Should include T01 (exists)");
|
|
508
|
+
} finally {
|
|
509
|
+
rmSync(repo, { recursive: true, force: true });
|
|
510
|
+
}
|
|
511
|
+
});
|