gsd-pi 2.38.0-dev.8f5c161 → 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/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/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-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-loop.js +538 -469
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +197 -19
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/commands.js +2 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +3 -0
- package/dist/resources/extensions/gsd/doctor.js +20 -1
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/files.js +46 -7
- 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 +22 -19
- 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-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +58 -0
- package/dist/resources/extensions/gsd/preferences.js +20 -9
- 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/execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- 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/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 +3 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +41 -22
- 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/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -2
- package/dist/resources/extensions/remote-questions/store.js +4 -2
- package/dist/resources/extensions/shared/frontmatter.js +1 -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/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-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-loop.ts +342 -304
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +242 -19
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/commands.ts +2 -2
- package/src/resources/extensions/gsd/doctor-providers.ts +4 -0
- package/src/resources/extensions/gsd/doctor.ts +22 -1
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/files.ts +49 -9
- 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 +21 -16
- 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-types.ts +4 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +50 -0
- package/src/resources/extensions/gsd/preferences.ts +23 -9
- 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/execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- 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/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 +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +38 -20
- 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/auto-loop.test.ts +106 -31
- 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/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/run-uat.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/types.ts +18 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +4 -2
- package/src/resources/extensions/remote-questions/store.ts +4 -2
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
|
@@ -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
|
+
});
|
|
@@ -29,7 +29,11 @@ const worktreePromptsDir = join(__dirname, '..', 'prompts');
|
|
|
29
29
|
function loadPromptFromWorktree(name: string, vars: Record<string, string> = {}): string {
|
|
30
30
|
const path = join(worktreePromptsDir, `${name}.md`);
|
|
31
31
|
let content = readFileSync(path, 'utf-8');
|
|
32
|
-
|
|
32
|
+
const effectiveVars = {
|
|
33
|
+
skillActivation: 'If no installed skill clearly matches this unit, skip explicit skill activation and continue with the required workflow.',
|
|
34
|
+
...vars,
|
|
35
|
+
};
|
|
36
|
+
for (const [key, value] of Object.entries(effectiveVars)) {
|
|
33
37
|
content = content.replaceAll(`{{${key}}}`, value);
|
|
34
38
|
}
|
|
35
39
|
return content.trim();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { loadSkills } from "@gsd/pi-coding-agent";
|
|
7
|
+
import { buildSkillActivationBlock } from "../auto-prompts.js";
|
|
8
|
+
import type { GSDPreferences } from "../preferences.js";
|
|
9
|
+
|
|
10
|
+
function makeTempBase(): string {
|
|
11
|
+
return mkdtempSync(join(tmpdir(), "gsd-skill-activation-"));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function cleanup(base: string): void {
|
|
15
|
+
rmSync(base, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function writeSkill(base: string, name: string, description: string): void {
|
|
19
|
+
const dir = join(base, "skills", name);
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
writeFileSync(join(dir, "SKILL.md"), `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadOnlyTestSkills(base: string): void {
|
|
25
|
+
loadSkills({ cwd: base, includeDefaults: false, skillPaths: [join(base, "skills")] });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildBlock(
|
|
29
|
+
base: string,
|
|
30
|
+
params: Partial<Parameters<typeof buildSkillActivationBlock>[0]> = {},
|
|
31
|
+
preferences: GSDPreferences = {},
|
|
32
|
+
): string {
|
|
33
|
+
return buildSkillActivationBlock({
|
|
34
|
+
base,
|
|
35
|
+
milestoneId: "M001",
|
|
36
|
+
sliceId: "S01",
|
|
37
|
+
...params,
|
|
38
|
+
preferences,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test("buildSkillActivationBlock matches installed skills from task context", () => {
|
|
43
|
+
const base = makeTempBase();
|
|
44
|
+
try {
|
|
45
|
+
writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
|
|
46
|
+
writeSkill(base, "swiftui", "Use for SwiftUI views, iOS layout, and Apple platform UI work.");
|
|
47
|
+
loadOnlyTestSkills(base);
|
|
48
|
+
|
|
49
|
+
const result = buildBlock(base, {
|
|
50
|
+
sliceTitle: "Build React dashboard",
|
|
51
|
+
taskId: "T01",
|
|
52
|
+
taskTitle: "Implement React settings panel",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.match(result, /<skill_activation>/);
|
|
56
|
+
assert.match(result, /Call Skill\('react'\)/);
|
|
57
|
+
assert.doesNotMatch(result, /swiftui/);
|
|
58
|
+
} finally {
|
|
59
|
+
cleanup(base);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("buildSkillActivationBlock includes always_use_skills from preferences", () => {
|
|
64
|
+
const base = makeTempBase();
|
|
65
|
+
try {
|
|
66
|
+
writeSkill(base, "testing", "Use for test setup, assertions, and verification patterns.");
|
|
67
|
+
loadOnlyTestSkills(base);
|
|
68
|
+
|
|
69
|
+
const result = buildBlock(base, { taskTitle: "Unrelated task title" }, {
|
|
70
|
+
always_use_skills: ["testing"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.match(result, /Call Skill\('testing'\)/);
|
|
74
|
+
} finally {
|
|
75
|
+
cleanup(base);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("buildSkillActivationBlock includes skill_rules matches and task-plan skills_used", () => {
|
|
80
|
+
const base = makeTempBase();
|
|
81
|
+
try {
|
|
82
|
+
writeSkill(base, "prisma", "Use for Prisma schema, migrations, and ORM queries.");
|
|
83
|
+
writeSkill(base, "accessibility", "Use for accessibility, aria attributes, and keyboard support.");
|
|
84
|
+
loadOnlyTestSkills(base);
|
|
85
|
+
|
|
86
|
+
const taskPlan = [
|
|
87
|
+
"---",
|
|
88
|
+
"skills_used:",
|
|
89
|
+
" - accessibility",
|
|
90
|
+
"---",
|
|
91
|
+
"# T01: Example",
|
|
92
|
+
].join("\n");
|
|
93
|
+
|
|
94
|
+
const result = buildBlock(base, {
|
|
95
|
+
taskTitle: "Update prisma schema",
|
|
96
|
+
taskPlanContent: taskPlan,
|
|
97
|
+
}, {
|
|
98
|
+
skill_rules: [{ when: "prisma database schema", use: ["prisma"] }],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.match(result, /Call Skill\('accessibility'\)/);
|
|
102
|
+
assert.match(result, /Call Skill\('prisma'\)/);
|
|
103
|
+
} finally {
|
|
104
|
+
cleanup(base);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("buildSkillActivationBlock honors avoid_skills", () => {
|
|
109
|
+
const base = makeTempBase();
|
|
110
|
+
try {
|
|
111
|
+
writeSkill(base, "react", "Use for React components and frontend UI work.");
|
|
112
|
+
loadOnlyTestSkills(base);
|
|
113
|
+
|
|
114
|
+
const result = buildBlock(base, {
|
|
115
|
+
taskTitle: "Implement React settings panel",
|
|
116
|
+
}, {
|
|
117
|
+
avoid_skills: ["react"],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
assert.equal(result, "");
|
|
121
|
+
} finally {
|
|
122
|
+
cleanup(base);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("buildSkillActivationBlock falls back cleanly when nothing matches", () => {
|
|
127
|
+
const base = makeTempBase();
|
|
128
|
+
try {
|
|
129
|
+
writeSkill(base, "swiftui", "Use for SwiftUI apps.");
|
|
130
|
+
loadOnlyTestSkills(base);
|
|
131
|
+
|
|
132
|
+
const result = buildBlock(base, {
|
|
133
|
+
taskTitle: "Plain text docs task",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
assert.equal(result, "");
|
|
137
|
+
} finally {
|
|
138
|
+
cleanup(base);
|
|
139
|
+
}
|
|
140
|
+
});
|