gentle-pi 0.3.4 → 0.3.5
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 +14 -15
- package/assets/agents/sdd-apply.md +4 -5
- package/assets/agents/sdd-archive.md +2 -3
- package/assets/agents/sdd-design.md +2 -4
- package/assets/agents/sdd-explore.md +2 -4
- package/assets/agents/sdd-init.md +2 -4
- package/assets/agents/sdd-onboard.md +2 -4
- package/assets/agents/sdd-proposal.md +2 -4
- package/assets/agents/sdd-spec.md +2 -3
- package/assets/agents/sdd-sync.md +2 -3
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +4 -5
- package/assets/orchestrator.md +14 -13
- package/extensions/gentle-ai.ts +83 -27
- package/extensions/skill-registry.ts +62 -103
- package/lib/sdd-preflight.ts +11 -5
- package/package.json +1 -1
- package/skills/gentle-ai/SKILL.md +1 -1
- package/skills/judgment-day/SKILL.md +1 -1
- package/skills/judgment-day/references/prompts-and-formats.md +6 -6
- package/skills/skill-registry/SKILL.md +51 -0
- package/tests/runtime-harness.mjs +70 -21
- package/tests/skill-registry.test.ts +93 -55
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { discoverAndLoadExtensions } from "@earendil-works/pi-coding-agent";
|
|
@@ -138,7 +138,9 @@ async function loadExtensions(pi) {
|
|
|
138
138
|
|
|
139
139
|
async function run() {
|
|
140
140
|
const globalConfigHome = await tempWorkspace();
|
|
141
|
+
const globalAgentHome = await tempWorkspace();
|
|
141
142
|
process.env.GENTLE_PI_CONFIG_HOME = globalConfigHome;
|
|
143
|
+
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
142
144
|
const globalModelsPath = join(globalConfigHome, "models.json");
|
|
143
145
|
const { pi, hooks, commands, flags } = createPi();
|
|
144
146
|
await loadExtensions(pi);
|
|
@@ -152,6 +154,16 @@ async function run() {
|
|
|
152
154
|
assert.ok(hooks.has("before_agent_start"), "missing before_agent_start hook");
|
|
153
155
|
assert.ok(hooks.has("tool_call"), "missing tool_call hook");
|
|
154
156
|
|
|
157
|
+
for (const entry of await readdir(join(ROOT, "assets", "agents"))) {
|
|
158
|
+
if (!entry.endsWith(".md")) continue;
|
|
159
|
+
const agentPrompt = await readFile(join(ROOT, "assets", "agents", entry), "utf8");
|
|
160
|
+
assert.doesNotMatch(
|
|
161
|
+
agentPrompt,
|
|
162
|
+
/inheritProjectContext:\s*true/,
|
|
163
|
+
`${entry} must not inherit parent project context by default`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
155
167
|
const discovered = await discoverAndLoadExtensions(["./extensions"], ROOT);
|
|
156
168
|
assert.deepEqual(
|
|
157
169
|
discovered.errors,
|
|
@@ -165,6 +177,11 @@ async function run() {
|
|
|
165
177
|
const promptResult = await promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
|
|
166
178
|
assert.match(promptResult.systemPrompt, /base/);
|
|
167
179
|
assert.match(promptResult.systemPrompt, /el Gentleman/);
|
|
180
|
+
const subagentPromptResult = await promptHook(
|
|
181
|
+
{ agentName: "worker", systemPrompt: "worker base" },
|
|
182
|
+
createCtx(promptCwd),
|
|
183
|
+
);
|
|
184
|
+
assert.equal(subagentPromptResult.systemPrompt, "worker base");
|
|
168
185
|
assert.equal(
|
|
169
186
|
existsSync(join(promptCwd, ".pi", "agents", "sdd-apply.md")),
|
|
170
187
|
false,
|
|
@@ -196,13 +213,15 @@ async function run() {
|
|
|
196
213
|
assert.equal(
|
|
197
214
|
existsSync(join(noUiCwd, ".pi", "agents", "sdd-apply.md")),
|
|
198
215
|
false,
|
|
199
|
-
"session_start must not install SDD agents
|
|
216
|
+
"session_start must not install project-local SDD agents",
|
|
200
217
|
);
|
|
201
218
|
assert.equal(
|
|
202
219
|
existsSync(join(noUiCwd, ".pi", "chains", "sdd-full.chain.md")),
|
|
203
220
|
false,
|
|
204
|
-
"session_start must not install SDD chains
|
|
221
|
+
"session_start must not install project-local SDD chains",
|
|
205
222
|
);
|
|
223
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
224
|
+
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
206
225
|
} finally {
|
|
207
226
|
await rm(noUiCwd, { recursive: true, force: true });
|
|
208
227
|
}
|
|
@@ -287,15 +306,14 @@ async function run() {
|
|
|
287
306
|
await inputHook({ text: "/sdd-plan this change", source: "interactive" }, ctx),
|
|
288
307
|
{ action: "continue" },
|
|
289
308
|
);
|
|
290
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")),
|
|
291
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "
|
|
292
|
-
assert.equal(existsSync(join(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
297
|
-
assert.
|
|
298
|
-
assert.match(lazyAppliedAgent, /thinking: high/);
|
|
309
|
+
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
310
|
+
assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
311
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
312
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), true);
|
|
313
|
+
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
314
|
+
const lazySettings = JSON.parse(await readFile(join(lazySddCwd, ".pi", "settings.json"), "utf8"));
|
|
315
|
+
assert.equal(lazySettings.subagents.agentOverrides["sdd-apply"].model, "openai/gpt-5");
|
|
316
|
+
assert.equal(lazySettings.subagents.agentOverrides["sdd-apply"].thinking, "high");
|
|
299
317
|
assert.equal(ctx.ui.selections.length, 3);
|
|
300
318
|
assert.deepEqual(ctx.ui.selections[1].options, ["openspec"]);
|
|
301
319
|
assert.match(ctx.ui.notifications.at(-1).message, /SDD preflight complete/);
|
|
@@ -306,6 +324,15 @@ async function run() {
|
|
|
306
324
|
const promptResult = await promptHook({ systemPrompt: "base" }, ctx);
|
|
307
325
|
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
308
326
|
assert.match(promptResult.systemPrompt, /Execution mode: interactive/);
|
|
327
|
+
const workerPromptResult = await promptHook(
|
|
328
|
+
{ agentName: "worker", systemPrompt: "worker base" },
|
|
329
|
+
ctx,
|
|
330
|
+
);
|
|
331
|
+
assert.equal(
|
|
332
|
+
workerPromptResult.systemPrompt,
|
|
333
|
+
"worker base",
|
|
334
|
+
"non-SDD subagents must not receive parent harness or SDD preflight prompts",
|
|
335
|
+
);
|
|
309
336
|
} finally {
|
|
310
337
|
await rm(lazySddCwd, { recursive: true, force: true });
|
|
311
338
|
await rm(globalModelsPath, { force: true });
|
|
@@ -315,7 +342,8 @@ async function run() {
|
|
|
315
342
|
try {
|
|
316
343
|
const ctx = createCtx(commandSddCwd, true, "command-session");
|
|
317
344
|
await commands.get("gentle-ai:sdd-preflight").handler("", ctx);
|
|
318
|
-
assert.equal(existsSync(join(commandSddCwd, ".pi", "agents", "sdd-apply.md")),
|
|
345
|
+
assert.equal(existsSync(join(commandSddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
346
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
319
347
|
assert.equal(ctx.ui.selections.length, 3);
|
|
320
348
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
321
349
|
assert.equal(ctx.ui.selections.length, 3, "manual preflight command should reuse session choices");
|
|
@@ -333,13 +361,25 @@ async function run() {
|
|
|
333
361
|
},
|
|
334
362
|
ctx,
|
|
335
363
|
);
|
|
336
|
-
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "agents", "sdd-apply.md")),
|
|
337
|
-
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "chains", "sdd-full.chain.md")),
|
|
364
|
+
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
365
|
+
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
366
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
367
|
+
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
338
368
|
assert.equal(ctx.ui.selections.length, 3);
|
|
339
369
|
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
370
|
+
assert.doesNotMatch(
|
|
371
|
+
promptResult.systemPrompt,
|
|
372
|
+
/el Gentleman Identity and Harness/,
|
|
373
|
+
"SDD executor startup must not receive the parent orchestrator prompt",
|
|
374
|
+
);
|
|
375
|
+
assert.doesNotMatch(
|
|
376
|
+
promptResult.systemPrompt,
|
|
377
|
+
/Work Routing Ladder/,
|
|
378
|
+
"SDD executor startup must not receive parent routing instructions",
|
|
379
|
+
);
|
|
340
380
|
assert.match(ctx.ui.notifications.at(-1).message, /SDD preflight complete/);
|
|
341
381
|
|
|
342
|
-
await promptHook(
|
|
382
|
+
const reusedPromptResult = await promptHook(
|
|
343
383
|
{
|
|
344
384
|
agentName: "sdd-tasks",
|
|
345
385
|
systemPrompt: "You are the SDD tasks executor for Gentle AI.",
|
|
@@ -347,6 +387,11 @@ async function run() {
|
|
|
347
387
|
ctx,
|
|
348
388
|
);
|
|
349
389
|
assert.equal(ctx.ui.selections.length, 3, "SDD agent guard should reuse session choices");
|
|
390
|
+
assert.doesNotMatch(
|
|
391
|
+
reusedPromptResult.systemPrompt,
|
|
392
|
+
/el Gentleman Identity and Harness/,
|
|
393
|
+
"named SDD executor startup must not receive the parent orchestrator prompt",
|
|
394
|
+
);
|
|
350
395
|
} finally {
|
|
351
396
|
await rm(sddAgentGuardCwd, { recursive: true, force: true });
|
|
352
397
|
}
|
|
@@ -379,7 +424,9 @@ async function run() {
|
|
|
379
424
|
try {
|
|
380
425
|
const ctx = createCtx(installCwd, true);
|
|
381
426
|
await commands.get("gentle-ai:install-sdd").handler("", ctx);
|
|
382
|
-
assert.match(ctx.ui.notifications.at(-1).message, /SDD assets installed/);
|
|
427
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Global Gentle AI SDD assets installed/);
|
|
428
|
+
assert.equal(existsSync(join(installCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
429
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
383
430
|
} finally {
|
|
384
431
|
await rm(installCwd, { recursive: true, force: true });
|
|
385
432
|
}
|
|
@@ -393,7 +440,7 @@ async function run() {
|
|
|
393
440
|
await writeFile(join(staleAssetsCwd, ".pi", "chains", "sdd-full.chain.md"), "stale chain\n");
|
|
394
441
|
const ctx = createCtx(staleAssetsCwd, true);
|
|
395
442
|
await commands.get("gentle-ai:status").handler("", ctx);
|
|
396
|
-
assert.match(ctx.ui.notifications.at(-1).message, /SDD
|
|
443
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Project-local SDD override drift: \d+ file\(s\)/);
|
|
397
444
|
assert.match(ctx.ui.notifications.at(-1).message, /gentle-ai:install-sdd --force/);
|
|
398
445
|
} finally {
|
|
399
446
|
await rm(staleAssetsCwd, { recursive: true, force: true });
|
|
@@ -403,9 +450,11 @@ async function run() {
|
|
|
403
450
|
try {
|
|
404
451
|
const ctx = createCtx(sddCwd, true);
|
|
405
452
|
await commands.get("sdd-init").handler("", ctx);
|
|
406
|
-
assert.equal(existsSync(join(sddCwd, ".pi", "agents", "sdd-apply.md")),
|
|
407
|
-
assert.equal(existsSync(join(sddCwd, ".pi", "
|
|
408
|
-
assert.equal(existsSync(join(
|
|
453
|
+
assert.equal(existsSync(join(sddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
454
|
+
assert.equal(existsSync(join(sddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
455
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
456
|
+
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), true);
|
|
457
|
+
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
409
458
|
assert.equal(ctx.ui.selections.length, 3);
|
|
410
459
|
assert.match(ctx.ui.notifications[0].message, /SDD preflight complete/);
|
|
411
460
|
assert.match(ctx.ui.notifications.at(-1).message, /Wrote openspec\/config\.yaml/);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import { __testing } from "../extensions/skill-registry.ts";
|
|
7
7
|
|
|
@@ -28,68 +28,53 @@ test("project skill dirs include supported workspace roots", () => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
test("registry renders indexed skill paths instead of compact rules", () => {
|
|
32
|
+
const cwd = join(tmpdir(), `gentle-pi-render-${Date.now()}`);
|
|
33
|
+
const skillPath = join(cwd, "skills", "go-testing", "SKILL.md");
|
|
34
|
+
const registry = __testing.renderRegistry(cwd, ["skills"], [
|
|
35
|
+
{
|
|
36
|
+
name: "go-testing",
|
|
37
|
+
path: skillPath,
|
|
38
|
+
description: "Trigger: Go tests. Apply focused testing patterns.",
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
40
41
|
|
|
41
|
-
assert.
|
|
42
|
+
assert.match(registry, /## Skills/);
|
|
43
|
+
assert.match(registry, /\| Skill \| Trigger \/ description \| Scope \| Path \|/);
|
|
44
|
+
assert.match(registry, /## Loading protocol/);
|
|
45
|
+
assert.match(registry, /\| `go-testing` \| Trigger: Go tests\. Apply focused testing patterns\. \| project \|/);
|
|
46
|
+
assert.match(registry, new RegExp(skillPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
47
|
+
assert.doesNotMatch(registry, /Selected skills and compact rules/);
|
|
48
|
+
assert.doesNotMatch(registry, /Project Standards \(auto-resolved\)/);
|
|
49
|
+
assert.doesNotMatch(registry, /Rules:/);
|
|
42
50
|
});
|
|
43
51
|
|
|
44
|
-
test("
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
2. Keep PRs within review budget.
|
|
52
|
+
test("frontmatter parser keeps full multiline descriptions", () => {
|
|
53
|
+
const parsed = __testing.parseFrontmatter(`---
|
|
54
|
+
name: ai-sdk-5
|
|
55
|
+
description: >
|
|
56
|
+
Trigger: AI chat features, Vercel AI SDK 5, streaming UI.
|
|
57
|
+
Use AI SDK 5 patterns and avoid v4 APIs.
|
|
58
|
+
license: Apache-2.0
|
|
59
|
+
---
|
|
53
60
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
| Rule | Requirement |
|
|
57
|
-
|------|-------------|
|
|
58
|
-
| Be warm | Sound like a teammate. |
|
|
59
|
-
|
|
60
|
-
## Decision Gates
|
|
61
|
+
## Hard Rules
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|---|---|
|
|
64
|
-
| File operations | Use t.TempDir(). |
|
|
63
|
+
- Do not copy this rule.
|
|
65
64
|
`);
|
|
66
65
|
|
|
67
|
-
assert.
|
|
68
|
-
"Prefer focused tests.",
|
|
69
|
-
"Link an approved issue.",
|
|
70
|
-
"Keep PRs within review budget.",
|
|
71
|
-
"Be warm: Sound like a teammate.",
|
|
72
|
-
"File operations: Use t.TempDir().",
|
|
73
|
-
]);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("description trigger text is extracted when present", () => {
|
|
66
|
+
assert.equal(parsed.name, "ai-sdk-5");
|
|
77
67
|
assert.equal(
|
|
78
|
-
|
|
79
|
-
"
|
|
68
|
+
parsed.description,
|
|
69
|
+
"Trigger: AI chat features, Vercel AI SDK 5, streaming UI. Use AI SDK 5 patterns and avoid v4 APIs.",
|
|
80
70
|
);
|
|
81
|
-
assert.equal(__testing.extractTriggerDescription("No explicit trigger."), "No explicit trigger.");
|
|
82
71
|
});
|
|
83
72
|
|
|
84
|
-
test("
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const rules = __testing.extractCompactRulesSection(body);
|
|
90
|
-
|
|
91
|
-
assert.equal(rules.length, 15);
|
|
92
|
-
assert.equal(rules.at(-1), "Rule 15.");
|
|
73
|
+
test("description normalization preserves trigger and collapses whitespace", () => {
|
|
74
|
+
assert.equal(
|
|
75
|
+
__testing.normalizeSkillDescription("Trigger: PR feedback, issue replies.\nUse maintainer voice."),
|
|
76
|
+
"Trigger: PR feedback, issue replies. Use maintainer voice.",
|
|
77
|
+
);
|
|
93
78
|
});
|
|
94
79
|
|
|
95
80
|
test("project-scoped duplicate wins over user duplicate", () => {
|
|
@@ -97,8 +82,8 @@ test("project-scoped duplicate wins over user duplicate", () => {
|
|
|
97
82
|
const projectPath = join(cwd, ".opencode/skills/dup/SKILL.md");
|
|
98
83
|
const userPath = join(cwd + "-home", ".config/opencode/skills/dup/SKILL.md");
|
|
99
84
|
const entries = [
|
|
100
|
-
{ name: "dup", path: userPath, description: "user"
|
|
101
|
-
{ name: "dup", path: projectPath, description: "project"
|
|
85
|
+
{ name: "dup", path: userPath, description: "user" },
|
|
86
|
+
{ name: "dup", path: projectPath, description: "project" },
|
|
102
87
|
];
|
|
103
88
|
|
|
104
89
|
const [chosen] = __testing.dedupeBySkillName(entries, cwd);
|
|
@@ -129,3 +114,56 @@ test("startup skip honors no skill registry controls", () => {
|
|
|
129
114
|
);
|
|
130
115
|
assert.equal(__testing.shouldSkipSkillRegistryStartup(disabled, [], {}), false);
|
|
131
116
|
});
|
|
117
|
+
|
|
118
|
+
test("scope and markdown cells are represented in registry", () => {
|
|
119
|
+
const cwd = join(tmpdir(), `gentle-pi-scope-${Date.now()}`);
|
|
120
|
+
const projectPath = join(cwd, "skills", "docs", "SKILL.md");
|
|
121
|
+
const userPath = join(tmpdir(), `gentle-pi-home-${Date.now()}`, ".claude", "skills", "docs", "SKILL.md");
|
|
122
|
+
const registry = __testing.renderRegistry(cwd, ["skills"], [
|
|
123
|
+
{ name: "project-docs", path: projectPath, description: "Docs | guides" },
|
|
124
|
+
{ name: "user-docs", path: userPath, description: "" },
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
assert.match(registry, /\| `project-docs` \| Docs \\\| guides \| project \|/);
|
|
128
|
+
assert.match(registry, /\| `user-docs` \| — \| user \|/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("generated registry file indexes skill path and omits body rules", async () => {
|
|
132
|
+
const cwd = join(tmpdir(), `gentle-pi-regenerate-${Date.now()}`);
|
|
133
|
+
const skillPath = join(cwd, "skills", "go-testing", "SKILL.md");
|
|
134
|
+
mkdirSync(dirname(skillPath), { recursive: true });
|
|
135
|
+
writeFileSync(
|
|
136
|
+
skillPath,
|
|
137
|
+
`---
|
|
138
|
+
name: go-testing
|
|
139
|
+
description: "Trigger: Go tests. Apply focused Go testing patterns."
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Hard Rules
|
|
143
|
+
|
|
144
|
+
- Run focused tests before broad tests.
|
|
145
|
+
`,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const dirs = await __testing.uniqueExistingDirs(__testing.projectSkillDirs(cwd));
|
|
149
|
+
assert.ok(dirs.includes(join(cwd, "skills")));
|
|
150
|
+
|
|
151
|
+
const registry = __testing.renderRegistry(cwd, ["skills"], [
|
|
152
|
+
{
|
|
153
|
+
name: "go-testing",
|
|
154
|
+
path: skillPath,
|
|
155
|
+
description: "Trigger: Go tests. Apply focused Go testing patterns.",
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
assert.match(registry, /go-testing/);
|
|
159
|
+
assert.match(registry, /Trigger: Go tests\. Apply focused Go testing patterns\./);
|
|
160
|
+
assert.match(registry, new RegExp(skillPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
161
|
+
assert.doesNotMatch(registry, /Run focused tests before broad tests/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("orchestrator documents path injection protocol", () => {
|
|
165
|
+
const source = readFileSync(join(import.meta.dirname, "..", "assets", "orchestrator.md"), "utf8");
|
|
166
|
+
assert.match(source, /## Skills to load before work/);
|
|
167
|
+
assert.match(source, /paths-injected/);
|
|
168
|
+
assert.doesNotMatch(source, /Use matching compact rules based on code context and task intent/);
|
|
169
|
+
});
|