gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.98b44dc
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 +15 -11
- package/dist/app-paths.js +1 -1
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- 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/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +636 -594
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
- package/dist/resources/extensions/gsd/auto-start.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +4 -2
- 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-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +20 -1
- 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 +48 -9
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +22 -11
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- 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-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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 +42 -23
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -1
- package/dist/resources/extensions/remote-questions/store.js +4 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/package.json +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/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/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- 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 +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +526 -545
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
- package/src/resources/extensions/gsd/auto-start.ts +11 -1
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +5 -3
- 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-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor.ts +22 -1
- 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 +51 -11
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +25 -11
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- 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-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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 +39 -21
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- 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/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -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/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/types.ts +18 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +5 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
- 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
|
@@ -80,66 +80,28 @@ test("buildHealthLines: initialized state shows continue setup copy", () => {
|
|
|
80
80
|
]);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("buildHealthLines: active state
|
|
84
|
-
const lines = buildHealthLines(activeData({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
milestones: { done: 0, total: 1 },
|
|
89
|
-
slices: { done: 0, total: 3 },
|
|
90
|
-
tasks: { done: 0, total: 5 },
|
|
91
|
-
},
|
|
92
|
-
}));
|
|
93
|
-
|
|
94
|
-
assert.equal(lines.length, 2);
|
|
95
|
-
assert.equal(lines[0], " GSD Executing - Plan S01");
|
|
96
|
-
assert.match(lines[1]!, /Progress: M 0\/1 · S 0\/3 · T 0\/5/);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("buildHealthLines: active state keeps issues secondary", () => {
|
|
100
|
-
const lines = buildHealthLines(activeData({
|
|
101
|
-
executionStatus: "Planning",
|
|
102
|
-
executionTarget: "Execute T03",
|
|
103
|
-
providerIssue: "✗ Anthropic (Claude) key missing",
|
|
104
|
-
environmentWarningCount: 1,
|
|
105
|
-
budgetSpent: 0.42,
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
assert.equal(lines.length, 2);
|
|
109
|
-
assert.equal(lines[0], " GSD Planning - Execute T03");
|
|
110
|
-
assert.match(lines[1]!, /✗ Anthropic \(Claude\) key missing/);
|
|
111
|
-
assert.match(lines[1]!, /Env: 1 warning/);
|
|
112
|
-
assert.match(lines[1]!, /Spent: 42\.0¢/);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("buildHealthLines: blocked state explains wait reason", () => {
|
|
116
|
-
const lines = buildHealthLines(activeData({
|
|
117
|
-
executionStatus: "Blocked",
|
|
118
|
-
executionTarget: "waiting on unmet deps: M001",
|
|
119
|
-
blocker: "M002 is waiting on unmet deps: M001",
|
|
120
|
-
}));
|
|
121
|
-
|
|
122
|
-
assert.equal(lines[0], " GSD Blocked - waiting on unmet deps: M001");
|
|
83
|
+
test("buildHealthLines: active state with ledger-driven spend shows spent summary", () => {
|
|
84
|
+
const lines = buildHealthLines(activeData({ budgetSpent: 0.42 }));
|
|
85
|
+
assert.equal(lines.length, 1);
|
|
86
|
+
assert.match(lines[0]!, /● System OK/);
|
|
87
|
+
assert.match(lines[0]!, /Spent: 42\.0¢/);
|
|
123
88
|
});
|
|
124
89
|
|
|
125
|
-
test("buildHealthLines:
|
|
126
|
-
const lines = buildHealthLines(activeData({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
|
-
assert.deepEqual(lines, [" GSD Paused - waiting to resume"]);
|
|
90
|
+
test("buildHealthLines: active state with budget ceiling shows percent summary", () => {
|
|
91
|
+
const lines = buildHealthLines(activeData({ budgetSpent: 2.5, budgetCeiling: 10 }));
|
|
92
|
+
assert.equal(lines.length, 1);
|
|
93
|
+
assert.match(lines[0]!, /Budget: \$2\.50\/\$10\.00 \(25%\)/);
|
|
132
94
|
});
|
|
133
95
|
|
|
134
|
-
test("buildHealthLines: active state with
|
|
96
|
+
test("buildHealthLines: active state with issues reports issue summary", () => {
|
|
135
97
|
const lines = buildHealthLines(activeData({
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
budgetSpent: 2.5,
|
|
139
|
-
budgetCeiling: 10,
|
|
98
|
+
providerIssue: "✗ OpenAI key missing",
|
|
99
|
+
environmentErrorCount: 1,
|
|
140
100
|
}));
|
|
141
|
-
assert.equal(lines.length,
|
|
142
|
-
assert.match(lines[
|
|
101
|
+
assert.equal(lines.length, 1);
|
|
102
|
+
assert.match(lines[0]!, /✗ 2 issues/);
|
|
103
|
+
assert.match(lines[0]!, /✗ OpenAI key missing/);
|
|
104
|
+
assert.match(lines[0]!, /Env: 1 error/);
|
|
143
105
|
});
|
|
144
106
|
|
|
145
107
|
test("detectHealthWidgetProjectState: metrics file alone does not imply project", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseRoadmap, parsePlan, parseSummary, parseContinue, parseRequirementCounts, parseSecretsManifest, formatSecretsManifest } from '../files.ts';
|
|
1
|
+
import { parseRoadmap, parsePlan, parseTaskPlanFile, parseSummary, parseContinue, parseRequirementCounts, parseSecretsManifest, formatSecretsManifest } from '../files.ts';
|
|
2
2
|
import { createTestContext } from './test-helpers.ts';
|
|
3
3
|
|
|
4
4
|
const { assertEq, assertTrue, report } = createTestContext();
|
|
@@ -241,7 +241,15 @@ console.log('\n=== parseRoadmap: missing risk defaults to low ===');
|
|
|
241
241
|
|
|
242
242
|
console.log('\n=== parsePlan: full plan ===');
|
|
243
243
|
{
|
|
244
|
-
const content =
|
|
244
|
+
const content = `---
|
|
245
|
+
estimated_steps: 6
|
|
246
|
+
estimated_files: 3
|
|
247
|
+
skills_used:
|
|
248
|
+
- typescript
|
|
249
|
+
- testing
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
# S01: Parser Test Suite
|
|
245
253
|
|
|
246
254
|
**Goal:** All 5 parsers have test coverage with edge cases.
|
|
247
255
|
**Demo:** \`node --test tests/parsers.test.ts\` passes with zero failures.
|
|
@@ -267,6 +275,13 @@ console.log('\n=== parsePlan: full plan ===');
|
|
|
267
275
|
- \`files.ts\` — update parseSummary
|
|
268
276
|
`;
|
|
269
277
|
|
|
278
|
+
const taskPlan = parseTaskPlanFile(content);
|
|
279
|
+
assertEq(taskPlan.frontmatter.estimated_steps, 6, 'task plan frontmatter estimated_steps');
|
|
280
|
+
assertEq(taskPlan.frontmatter.estimated_files, 3, 'task plan frontmatter estimated_files');
|
|
281
|
+
assertEq(taskPlan.frontmatter.skills_used.length, 2, 'task plan frontmatter skills_used count');
|
|
282
|
+
assertEq(taskPlan.frontmatter.skills_used[0], 'typescript', 'first task plan skill');
|
|
283
|
+
assertEq(taskPlan.frontmatter.skills_used[1], 'testing', 'second task plan skill');
|
|
284
|
+
|
|
270
285
|
const p = parsePlan(content);
|
|
271
286
|
|
|
272
287
|
assertEq(p.id, 'S01', 'plan id');
|
|
@@ -295,6 +310,97 @@ console.log('\n=== parsePlan: full plan ===');
|
|
|
295
310
|
assertTrue(p.filesLikelyTouched[0].includes('tests/parsers.test.ts'), 'first file');
|
|
296
311
|
}
|
|
297
312
|
|
|
313
|
+
console.log('\n=== parseTaskPlanFile: defaults missing frontmatter fields ===');
|
|
314
|
+
{
|
|
315
|
+
const content = `# T01: Minimal task plan
|
|
316
|
+
|
|
317
|
+
## Description
|
|
318
|
+
|
|
319
|
+
No frontmatter here.
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
const taskPlan = parseTaskPlanFile(content);
|
|
323
|
+
assertEq(taskPlan.frontmatter.estimated_steps, undefined, 'estimated_steps defaults undefined');
|
|
324
|
+
assertEq(taskPlan.frontmatter.estimated_files, undefined, 'estimated_files defaults undefined');
|
|
325
|
+
assertEq(taskPlan.frontmatter.skills_used.length, 0, 'skills_used defaults empty array');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
console.log('\n=== parseTaskPlanFile: accepts scalar skills_used and numeric strings ===');
|
|
329
|
+
{
|
|
330
|
+
const content = `---
|
|
331
|
+
estimated_steps: "9"
|
|
332
|
+
estimated_files: "4"
|
|
333
|
+
skills_used: react-best-practices
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
# T02: Scalar skill handoff
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
const taskPlan = parseTaskPlanFile(content);
|
|
340
|
+
assertEq(taskPlan.frontmatter.estimated_steps, 9, 'string estimated_steps parsed');
|
|
341
|
+
assertEq(taskPlan.frontmatter.estimated_files, 4, 'string estimated_files parsed');
|
|
342
|
+
assertEq(taskPlan.frontmatter.skills_used.length, 1, 'scalar skills_used normalized to array');
|
|
343
|
+
assertEq(taskPlan.frontmatter.skills_used[0], 'react-best-practices', 'scalar skill preserved');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('\n=== parseTaskPlanFile: filters blank skills_used items ===');
|
|
347
|
+
{
|
|
348
|
+
const content = `---
|
|
349
|
+
skills_used:
|
|
350
|
+
- react
|
|
351
|
+
-
|
|
352
|
+
- testing
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
# T03: Blank skills filtered
|
|
356
|
+
`;
|
|
357
|
+
|
|
358
|
+
const taskPlan = parseTaskPlanFile(content);
|
|
359
|
+
assertEq(taskPlan.frontmatter.skills_used.length, 2, 'blank skill entries removed');
|
|
360
|
+
assertEq(taskPlan.frontmatter.skills_used[0], 'react', 'first remaining skill');
|
|
361
|
+
assertEq(taskPlan.frontmatter.skills_used[1], 'testing', 'second remaining skill');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
console.log('\n=== parseTaskPlanFile: invalid numeric frontmatter ignored ===');
|
|
365
|
+
{
|
|
366
|
+
const content = `---
|
|
367
|
+
estimated_steps: many
|
|
368
|
+
estimated_files: unknown
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
# T04: Invalid estimates
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
const taskPlan = parseTaskPlanFile(content);
|
|
375
|
+
assertEq(taskPlan.frontmatter.estimated_steps, undefined, 'invalid estimated_steps ignored');
|
|
376
|
+
assertEq(taskPlan.frontmatter.estimated_files, undefined, 'invalid estimated_files ignored');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log('\n=== parseTaskPlanFile: parsePlan ignores task-plan frontmatter ===');
|
|
380
|
+
{
|
|
381
|
+
const content = `---
|
|
382
|
+
estimated_steps: 2
|
|
383
|
+
estimated_files: 1
|
|
384
|
+
skills_used:
|
|
385
|
+
- react
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
# S11: Frontmatter Compatible
|
|
389
|
+
|
|
390
|
+
**Goal:** Plan parser ignores task-plan handoff metadata.
|
|
391
|
+
**Demo:** Slice content still parses.
|
|
392
|
+
|
|
393
|
+
## Tasks
|
|
394
|
+
|
|
395
|
+
- [ ] **T01: Compatible task** \`est:5m\`
|
|
396
|
+
Description.
|
|
397
|
+
`;
|
|
398
|
+
|
|
399
|
+
const p = parsePlan(content);
|
|
400
|
+
assertEq(p.id, 'S11', 'plan id still parsed with frontmatter');
|
|
401
|
+
assertEq(p.tasks.length, 1, 'task still parsed with frontmatter');
|
|
402
|
+
}
|
|
403
|
+
|
|
298
404
|
console.log('\n=== parsePlan: multi-line task description concatenation ===');
|
|
299
405
|
{
|
|
300
406
|
const content = `# S02: Multi-line Test
|
|
@@ -324,16 +430,36 @@ console.log('\n=== parsePlan: multi-line task description concatenation ===');
|
|
|
324
430
|
const p = parsePlan(content);
|
|
325
431
|
|
|
326
432
|
assertEq(p.tasks.length, 2, 'two tasks');
|
|
327
|
-
// Multi-line descriptions should be concatenated with spaces
|
|
328
433
|
assertTrue(p.tasks[0].description.includes('First line'), 'T01 desc has first line');
|
|
329
434
|
assertTrue(p.tasks[0].description.includes('Second line'), 'T01 desc has second line');
|
|
330
435
|
assertTrue(p.tasks[0].description.includes('Third line'), 'T01 desc has third line');
|
|
331
|
-
// Verify concatenation with space separator
|
|
332
436
|
assertTrue(p.tasks[0].description.includes('description. Second'), 'lines joined with space');
|
|
333
|
-
|
|
334
437
|
assertEq(p.tasks[1].description, 'Just one line.', 'T02 single-line desc');
|
|
335
438
|
}
|
|
336
439
|
|
|
440
|
+
console.log('\n=== parsePlan: frontmatter does not pollute task descriptions ===');
|
|
441
|
+
{
|
|
442
|
+
const content = `---
|
|
443
|
+
estimated_steps: 2
|
|
444
|
+
estimated_files: 1
|
|
445
|
+
skills_used:
|
|
446
|
+
- react
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
# S12: Frontmatter + multiline
|
|
450
|
+
|
|
451
|
+
## Tasks
|
|
452
|
+
|
|
453
|
+
- [ ] **T01: Multi-line Task** \`est:30m\`
|
|
454
|
+
First line of description.
|
|
455
|
+
Second line of description.
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
const p = parsePlan(content);
|
|
459
|
+
assertEq(p.tasks.length, 1, 'one task parsed with frontmatter');
|
|
460
|
+
assertEq(p.tasks[0].description, 'First line of description. Second line of description.', 'frontmatter excluded from description');
|
|
461
|
+
}
|
|
462
|
+
|
|
337
463
|
console.log('\n=== parsePlan: task with missing estimate ===');
|
|
338
464
|
{
|
|
339
465
|
const content = `# S03: No Estimate
|
|
@@ -351,12 +477,10 @@ console.log('\n=== parsePlan: task with missing estimate ===');
|
|
|
351
477
|
`;
|
|
352
478
|
|
|
353
479
|
const p = parsePlan(content);
|
|
354
|
-
|
|
355
480
|
assertEq(p.tasks.length, 2, 'two tasks parsed');
|
|
356
481
|
assertEq(p.tasks[0].id, 'T01', 'T01 id');
|
|
357
482
|
assertEq(p.tasks[0].title, 'No Estimate Task', 'T01 title without estimate');
|
|
358
483
|
assertEq(p.tasks[0].done, false, 'T01 not done');
|
|
359
|
-
// The estimate backtick text appears in description if present, but parser doesn't crash without it
|
|
360
484
|
assertEq(p.tasks[1].id, 'T02', 'T02 id');
|
|
361
485
|
}
|
|
362
486
|
|
|
@@ -379,7 +503,6 @@ console.log('\n=== parsePlan: empty tasks section ===');
|
|
|
379
503
|
`;
|
|
380
504
|
|
|
381
505
|
const p = parsePlan(content);
|
|
382
|
-
|
|
383
506
|
assertEq(p.id, 'S04', 'plan id with empty tasks');
|
|
384
507
|
assertEq(p.tasks.length, 0, 'no tasks');
|
|
385
508
|
assertEq(p.mustHaves.length, 1, 'one must-have');
|
|
@@ -398,7 +521,6 @@ console.log('\n=== parsePlan: no H1 ===');
|
|
|
398
521
|
`;
|
|
399
522
|
|
|
400
523
|
const p = parsePlan(content);
|
|
401
|
-
|
|
402
524
|
assertEq(p.id, '', 'empty id without H1');
|
|
403
525
|
assertEq(p.title, '', 'empty title without H1');
|
|
404
526
|
assertEq(p.goal, 'A plan without a heading.', 'goal still parsed');
|
|
@@ -408,8 +530,6 @@ console.log('\n=== parsePlan: no H1 ===');
|
|
|
408
530
|
|
|
409
531
|
console.log('\n=== parsePlan: task estimate backtick in description ===');
|
|
410
532
|
{
|
|
411
|
-
// The `est:45m` text appears after the bold closing but before the description lines
|
|
412
|
-
// It should end up as part of the description or be ignored gracefully
|
|
413
533
|
const content = `# S05: Estimate Handling
|
|
414
534
|
|
|
415
535
|
**Goal:** Test estimate text handling.
|
|
@@ -425,9 +545,6 @@ console.log('\n=== parsePlan: task estimate backtick in description ===');
|
|
|
425
545
|
assertEq(p.tasks.length, 1, 'one task');
|
|
426
546
|
assertEq(p.tasks[0].id, 'T01', 'task id');
|
|
427
547
|
assertEq(p.tasks[0].title, 'With Estimate', 'title excludes estimate');
|
|
428
|
-
// The `est:45m` backtick text after ** is not part of the title or description
|
|
429
|
-
// It's on the same line after the regex match captures, so it's in the remainder
|
|
430
|
-
// The description should be the continuation lines
|
|
431
548
|
assertTrue(p.tasks[0].description.includes('Main description'), 'description from continuation line');
|
|
432
549
|
}
|
|
433
550
|
|
|
@@ -26,8 +26,21 @@ const BASE_VARS = {
|
|
|
26
26
|
inlinedContext: "--- test inlined context ---",
|
|
27
27
|
dependencySummaries: "", executorContextConstraints: "",
|
|
28
28
|
sourceFilePaths: "- **Requirements**: `.gsd/REQUIREMENTS.md`",
|
|
29
|
+
skillActivation: "Load the relevant skills.",
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const DEFAULT_SKILL_ACTIVATION = "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.";
|
|
33
|
+
|
|
34
|
+
function loadPromptWithDefaultSkillActivation(name: string, vars: Record<string, string> = {}): string {
|
|
35
|
+
return loadPrompt(name, { skillActivation: DEFAULT_SKILL_ACTIVATION, ...vars });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function promptUsesSkillActivation(name: string): boolean {
|
|
39
|
+
const path = join(worktreePromptsDir, `${name}.md`);
|
|
40
|
+
const content = readFileSync(path, "utf-8");
|
|
41
|
+
return content.includes("{{skillActivation}}");
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
test("plan-slice prompt: commit instruction says do not commit (external state)", () => {
|
|
32
45
|
const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally." });
|
|
33
46
|
assert.ok(result.includes("Do not commit planning artifacts"));
|
|
@@ -40,3 +53,199 @@ test("plan-slice prompt: all variables substituted", () => {
|
|
|
40
53
|
assert.ok(result.includes("M001"));
|
|
41
54
|
assert.ok(result.includes("S01"));
|
|
42
55
|
});
|
|
56
|
+
|
|
57
|
+
test("domain-work prompts use skillActivation placeholder", () => {
|
|
58
|
+
const prompts = [
|
|
59
|
+
"research-milestone",
|
|
60
|
+
"plan-milestone",
|
|
61
|
+
"research-slice",
|
|
62
|
+
"plan-slice",
|
|
63
|
+
"execute-task",
|
|
64
|
+
"guided-research-slice",
|
|
65
|
+
"guided-plan-milestone",
|
|
66
|
+
"guided-plan-slice",
|
|
67
|
+
"guided-execute-task",
|
|
68
|
+
"guided-resume-task",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (const name of prompts) {
|
|
72
|
+
assert.ok(promptUsesSkillActivation(name), `${name}.md should contain {{skillActivation}}`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("skillActivation default leaves no unresolved placeholder", () => {
|
|
77
|
+
const result = loadPromptWithDefaultSkillActivation("execute-task", {
|
|
78
|
+
workingDirectory: "/tmp/test-project",
|
|
79
|
+
milestoneId: "M001",
|
|
80
|
+
sliceId: "S01",
|
|
81
|
+
sliceTitle: "Test Slice",
|
|
82
|
+
taskId: "T01",
|
|
83
|
+
taskTitle: "Implement feature",
|
|
84
|
+
planPath: ".gsd/milestones/M001/slices/S01/S01-PLAN.md",
|
|
85
|
+
taskPlanPath: ".gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md",
|
|
86
|
+
taskPlanInline: "Task plan",
|
|
87
|
+
slicePlanExcerpt: "Slice excerpt",
|
|
88
|
+
carryForwardSection: "Carry forward",
|
|
89
|
+
resumeSection: "Resume",
|
|
90
|
+
priorTaskLines: "- (no prior tasks)",
|
|
91
|
+
taskSummaryPath: "/tmp/test-project/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md",
|
|
92
|
+
inlinedTemplates: "Template",
|
|
93
|
+
verificationBudget: "~10K chars",
|
|
94
|
+
overridesSection: "",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
98
|
+
assert.ok(result.includes(DEFAULT_SKILL_ACTIVATION));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("custom skillActivation is substituted into execute-task", () => {
|
|
102
|
+
const result = loadPrompt("execute-task", {
|
|
103
|
+
workingDirectory: "/tmp/test-project",
|
|
104
|
+
milestoneId: "M001",
|
|
105
|
+
sliceId: "S01",
|
|
106
|
+
sliceTitle: "Test Slice",
|
|
107
|
+
taskId: "T01",
|
|
108
|
+
taskTitle: "Implement feature",
|
|
109
|
+
planPath: ".gsd/milestones/M001/slices/S01/S01-PLAN.md",
|
|
110
|
+
taskPlanPath: ".gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md",
|
|
111
|
+
taskPlanInline: "Task plan",
|
|
112
|
+
slicePlanExcerpt: "Slice excerpt",
|
|
113
|
+
carryForwardSection: "Carry forward",
|
|
114
|
+
resumeSection: "Resume",
|
|
115
|
+
priorTaskLines: "- (no prior tasks)",
|
|
116
|
+
taskSummaryPath: "/tmp/test-project/.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md",
|
|
117
|
+
inlinedTemplates: "Template",
|
|
118
|
+
verificationBudget: "~10K chars",
|
|
119
|
+
overridesSection: "",
|
|
120
|
+
skillActivation: "Load React and accessibility skills first.",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
assert.ok(result.includes("Load React and accessibility skills first."));
|
|
124
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("guided execute prompt substitutes skillActivation", () => {
|
|
128
|
+
const result = loadPrompt("guided-execute-task", {
|
|
129
|
+
milestoneId: "M001",
|
|
130
|
+
sliceId: "S01",
|
|
131
|
+
taskId: "T01",
|
|
132
|
+
taskTitle: "Implement feature",
|
|
133
|
+
inlinedTemplates: "Template",
|
|
134
|
+
skillActivation: "Load React skill first.",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
assert.ok(result.includes("Load React skill first."));
|
|
138
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("guided resume prompt substitutes skillActivation", () => {
|
|
142
|
+
const result = loadPrompt("guided-resume-task", {
|
|
143
|
+
milestoneId: "M001",
|
|
144
|
+
sliceId: "S01",
|
|
145
|
+
skillActivation: "Load debugging skill first.",
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
assert.ok(result.includes("Load debugging skill first."));
|
|
149
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("research-milestone prompt substitutes skillActivation", () => {
|
|
153
|
+
const result = loadPrompt("research-milestone", {
|
|
154
|
+
workingDirectory: "/tmp/test-project",
|
|
155
|
+
milestoneId: "M001",
|
|
156
|
+
milestoneTitle: "Test Milestone",
|
|
157
|
+
milestonePath: ".gsd/milestones/M001",
|
|
158
|
+
contextPath: ".gsd/milestones/M001/M001-CONTEXT.md",
|
|
159
|
+
outputPath: "/tmp/test-project/.gsd/milestones/M001/M001-RESEARCH.md",
|
|
160
|
+
inlinedContext: "Context",
|
|
161
|
+
skillDiscoveryMode: "manual",
|
|
162
|
+
skillDiscoveryInstructions: " Discover skills manually.",
|
|
163
|
+
skillActivation: "Load research skills first.",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
assert.ok(result.includes("Load research skills first."));
|
|
167
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("research-slice prompt substitutes skillActivation", () => {
|
|
171
|
+
const result = loadPrompt("research-slice", {
|
|
172
|
+
workingDirectory: "/tmp/test-project",
|
|
173
|
+
milestoneId: "M001",
|
|
174
|
+
sliceId: "S01",
|
|
175
|
+
sliceTitle: "Test Slice",
|
|
176
|
+
slicePath: ".gsd/milestones/M001/slices/S01",
|
|
177
|
+
roadmapPath: ".gsd/milestones/M001/M001-ROADMAP.md",
|
|
178
|
+
contextPath: ".gsd/milestones/M001/M001-CONTEXT.md",
|
|
179
|
+
milestoneResearchPath: ".gsd/milestones/M001/M001-RESEARCH.md",
|
|
180
|
+
outputPath: "/tmp/test-project/.gsd/milestones/M001/slices/S01/S01-RESEARCH.md",
|
|
181
|
+
inlinedContext: "Context",
|
|
182
|
+
dependencySummaries: "",
|
|
183
|
+
skillDiscoveryMode: "manual",
|
|
184
|
+
skillDiscoveryInstructions: " Discover skills manually.",
|
|
185
|
+
skillActivation: "Load slice research skills first.",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
assert.ok(result.includes("Load slice research skills first."));
|
|
189
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("plan-milestone prompt substitutes skillActivation", () => {
|
|
193
|
+
const result = loadPrompt("plan-milestone", {
|
|
194
|
+
workingDirectory: "/tmp/test-project",
|
|
195
|
+
milestoneId: "M001",
|
|
196
|
+
milestoneTitle: "Test Milestone",
|
|
197
|
+
milestonePath: ".gsd/milestones/M001",
|
|
198
|
+
contextPath: ".gsd/milestones/M001/M001-CONTEXT.md",
|
|
199
|
+
researchPath: ".gsd/milestones/M001/M001-RESEARCH.md",
|
|
200
|
+
researchOutputPath: "/tmp/test-project/.gsd/milestones/M001/M001-RESEARCH.md",
|
|
201
|
+
outputPath: "/tmp/test-project/.gsd/milestones/M001/M001-ROADMAP.md",
|
|
202
|
+
secretsOutputPath: "/tmp/test-project/.gsd/milestones/M001/M001-SECRETS.md",
|
|
203
|
+
inlinedContext: "Context",
|
|
204
|
+
sourceFilePaths: "- source",
|
|
205
|
+
skillDiscoveryMode: "manual",
|
|
206
|
+
skillDiscoveryInstructions: " Discover skills manually.",
|
|
207
|
+
skillActivation: "Load milestone planning skills first.",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
assert.ok(result.includes("Load milestone planning skills first."));
|
|
211
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("guided plan milestone prompt substitutes skillActivation", () => {
|
|
215
|
+
const result = loadPrompt("guided-plan-milestone", {
|
|
216
|
+
milestoneId: "M001",
|
|
217
|
+
milestoneTitle: "Test Milestone",
|
|
218
|
+
secretsOutputPath: ".gsd/milestones/M001/M001-SECRETS.md",
|
|
219
|
+
inlinedTemplates: "Templates",
|
|
220
|
+
skillActivation: "Load guided planning skills first.",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
assert.ok(result.includes("Load guided planning skills first."));
|
|
224
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("guided plan slice prompt substitutes skillActivation", () => {
|
|
228
|
+
const result = loadPrompt("guided-plan-slice", {
|
|
229
|
+
milestoneId: "M001",
|
|
230
|
+
sliceId: "S01",
|
|
231
|
+
sliceTitle: "Test Slice",
|
|
232
|
+
inlinedTemplates: "Templates",
|
|
233
|
+
skillActivation: "Load guided slice planning skills first.",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
assert.ok(result.includes("Load guided slice planning skills first."));
|
|
237
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("guided research slice prompt substitutes skillActivation", () => {
|
|
241
|
+
const result = loadPrompt("guided-research-slice", {
|
|
242
|
+
milestoneId: "M001",
|
|
243
|
+
sliceId: "S01",
|
|
244
|
+
sliceTitle: "Test Slice",
|
|
245
|
+
inlinedTemplates: "Templates",
|
|
246
|
+
skillActivation: "Load guided research skills first.",
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
assert.ok(result.includes("Load guided research skills first."));
|
|
250
|
+
assert.ok(!result.includes("{{skillActivation}}"));
|
|
251
|
+
});
|
|
@@ -208,30 +208,25 @@ test("git fields comprehensive validation", () => {
|
|
|
208
208
|
assert.equal(preferences.git?.isolation, "branch");
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
-
test("auto_visualize, auto_report,
|
|
211
|
+
test("auto_visualize, auto_report, context_selection validate correctly", () => {
|
|
212
212
|
const { preferences, errors } = validatePreferences({
|
|
213
213
|
auto_visualize: true,
|
|
214
214
|
auto_report: false,
|
|
215
|
-
compression_strategy: "compress",
|
|
216
215
|
context_selection: "smart",
|
|
217
216
|
});
|
|
218
217
|
assert.equal(errors.length, 0);
|
|
219
218
|
assert.equal(preferences.auto_visualize, true);
|
|
220
219
|
assert.equal(preferences.auto_report, false);
|
|
221
|
-
assert.equal(preferences.compression_strategy, "compress");
|
|
222
220
|
assert.equal(preferences.context_selection, "smart");
|
|
223
221
|
});
|
|
224
222
|
|
|
225
|
-
test("auto_visualize, auto_report,
|
|
223
|
+
test("auto_visualize, auto_report, context_selection reject invalid values", () => {
|
|
226
224
|
const { errors: e1 } = validatePreferences({ auto_visualize: "yes" as never });
|
|
227
225
|
assert.ok(e1.some(e => e.includes("auto_visualize")));
|
|
228
226
|
|
|
229
227
|
const { errors: e2 } = validatePreferences({ auto_report: 1 as never });
|
|
230
228
|
assert.ok(e2.some(e => e.includes("auto_report")));
|
|
231
229
|
|
|
232
|
-
const { errors: e3 } = validatePreferences({ compression_strategy: "shrink" as never });
|
|
233
|
-
assert.ok(e3.some(e => e.includes("compression_strategy")));
|
|
234
|
-
|
|
235
230
|
const { errors: e4 } = validatePreferences({ context_selection: "partial" as never });
|
|
236
231
|
assert.ok(e4.some(e => e.includes("context_selection")));
|
|
237
232
|
});
|
|
@@ -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
|
+
});
|
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
import { externalGsdRoot, ensureGsdSymlink } from "../repo-identity.ts";
|
|
6
|
+
import { repoIdentity, externalGsdRoot, ensureGsdSymlink, validateProjectId } from "../repo-identity.ts";
|
|
7
7
|
import { createTestContext } from "./test-helpers.ts";
|
|
8
8
|
|
|
9
9
|
const { assertEq, assertTrue, report } = createTestContext();
|
|
@@ -57,6 +57,26 @@ async function main(): Promise<void> {
|
|
|
57
57
|
assertEq(preservedDirState, join(worktreePath, ".gsd"), "worktree .gsd directory is left in place for sync-based refresh");
|
|
58
58
|
assertTrue(lstatSync(join(worktreePath, ".gsd")).isDirectory(), "worktree .gsd directory remains a directory");
|
|
59
59
|
assertTrue(existsSync(join(worktreePath, ".gsd", "milestones", "stale.txt")), "existing worktree .gsd directory contents remain available for sync logic");
|
|
60
|
+
|
|
61
|
+
console.log("\n=== GSD_PROJECT_ID overrides computed repo hash ===");
|
|
62
|
+
process.env.GSD_PROJECT_ID = "my-project";
|
|
63
|
+
assertEq(repoIdentity(base), "my-project", "repoIdentity returns GSD_PROJECT_ID when set");
|
|
64
|
+
assertEq(externalGsdRoot(base), join(stateDir, "projects", "my-project"), "externalGsdRoot uses GSD_PROJECT_ID");
|
|
65
|
+
delete process.env.GSD_PROJECT_ID;
|
|
66
|
+
|
|
67
|
+
console.log("\n=== GSD_PROJECT_ID falls back to hash when unset ===");
|
|
68
|
+
const hashIdentity = repoIdentity(base);
|
|
69
|
+
assertTrue(/^[0-9a-f]{12}$/.test(hashIdentity), "repoIdentity returns 12-char hex hash when GSD_PROJECT_ID is unset");
|
|
70
|
+
|
|
71
|
+
console.log("\n=== validateProjectId rejects invalid values ===");
|
|
72
|
+
for (const invalid of ["has spaces", "path/traversal", "dot..dot", "back\\slash"]) {
|
|
73
|
+
assertTrue(!validateProjectId(invalid), `validateProjectId rejects invalid value: "${invalid}"`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log("\n=== validateProjectId accepts valid values ===");
|
|
77
|
+
for (const valid of ["my-project", "foo_bar", "abc123", "A-Z_0-9"]) {
|
|
78
|
+
assertTrue(validateProjectId(valid), `validateProjectId accepts valid value: "${valid}"`);
|
|
79
|
+
}
|
|
60
80
|
} finally {
|
|
61
81
|
delete process.env.GSD_STATE_DIR;
|
|
62
82
|
rmSync(base, { recursive: true, force: true });
|