opencode-agent-skills-md 1.0.1 → 1.1.0

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.
Files changed (129) hide show
  1. package/dist/cli.mjs +770 -0
  2. package/dist/plugin.mjs +1138 -0
  3. package/dist/src/cli/config.d.ts +144 -0
  4. package/dist/src/cli/install.d.ts +33 -0
  5. package/dist/src/cli/main.d.ts +11 -0
  6. package/dist/src/cli/real-fs.d.ts +6 -0
  7. package/dist/src/cli/status.d.ts +34 -0
  8. package/dist/src/cli/uninstall.d.ts +22 -0
  9. package/dist/src/host.d.ts +51 -0
  10. package/dist/src/index.d.ts +17 -0
  11. package/dist/src/plugin.d.ts +35 -0
  12. package/dist/src/sdk.d.ts +51 -0
  13. package/dist/src/tools.d.ts +86 -0
  14. package/package.json +48 -18
  15. package/.beads/.local_version +0 -1
  16. package/.beads/README.md +0 -81
  17. package/.beads/config.yaml +0 -61
  18. package/.beads/deletions.jsonl +0 -1
  19. package/.beads/issues.jsonl +0 -64
  20. package/.beads/metadata.json +0 -4
  21. package/.gitattributes +0 -3
  22. package/.github/CODEOWNERS +0 -1
  23. package/.github/copilot-instructions.md +0 -78
  24. package/.github/dependabot.yml +0 -13
  25. package/.github/workflows/release.yml +0 -51
  26. package/.opencode/command/test-compaction.md +0 -9
  27. package/.opencode/command/test-find-skills.md +0 -7
  28. package/.opencode/command/test-read-skill-file.md +0 -14
  29. package/.opencode/command/test-run-skill-script.md +0 -13
  30. package/.opencode/command/test-skills.md +0 -14
  31. package/.opencode/command/test-use-skill.md +0 -10
  32. package/.opencode/skills/git-helper/SKILL.md +0 -65
  33. package/.opencode/skills/test-skill/SKILL.md +0 -43
  34. package/.opencode/skills/test-skill/example-config.json +0 -16
  35. package/.opencode/skills/test-skill/helper-docs.md +0 -29
  36. package/.opencode/skills/test-skill/scripts/echo-args +0 -14
  37. package/.opencode/skills/test-skill/scripts/greet +0 -6
  38. package/AGENTS.md +0 -43
  39. package/CHANGELOG.md +0 -178
  40. package/Justfile +0 -39
  41. package/README.md +0 -220
  42. package/openspec/changes/archive/2026-06-14-skills-core-decouple/specs/core-decoupling/spec.md +0 -74
  43. package/openspec/changes/archive/2026-06-14-skills-core-decouple/tasks.md +0 -64
  44. package/openspec/changes/archive/2026-06-14-skills-core-decouple/verify-report.md +0 -75
  45. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/apply-progress.md +0 -136
  46. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/archive-report.md +0 -77
  47. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/design.md +0 -89
  48. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/proposal.md +0 -65
  49. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/specs/core-decoupling/spec.md +0 -77
  50. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/tasks.md +0 -65
  51. package/openspec/changes/archive/2026-06-17-fix-skill-loading-regression/verify-report.md +0 -165
  52. package/openspec/specs/core-decoupling/spec.md +0 -110
  53. package/packages/core/package.json +0 -30
  54. package/packages/core/src/content.d.ts +0 -16
  55. package/packages/core/src/content.ts +0 -30
  56. package/packages/core/src/debug.ts +0 -16
  57. package/packages/core/src/discovery.d.ts +0 -86
  58. package/packages/core/src/discovery.ts +0 -257
  59. package/packages/core/src/index.d.ts +0 -20
  60. package/packages/core/src/index.ts +0 -55
  61. package/packages/core/src/match.d.ts +0 -19
  62. package/packages/core/src/match.ts +0 -75
  63. package/packages/core/src/parse.d.ts +0 -26
  64. package/packages/core/src/parse.ts +0 -141
  65. package/packages/core/src/scripts.d.ts +0 -17
  66. package/packages/core/src/scripts.ts +0 -79
  67. package/packages/core/src/search.d.ts +0 -83
  68. package/packages/core/src/search.ts +0 -188
  69. package/packages/core/src/types.d.ts +0 -82
  70. package/packages/core/src/types.ts +0 -131
  71. package/packages/core/src/walk.ts +0 -109
  72. package/packages/core/tests/agnostic.test.ts +0 -346
  73. package/packages/core/tests/content.test.ts +0 -65
  74. package/packages/core/tests/discovery.test.ts +0 -370
  75. package/packages/core/tests/package-boundary.test.ts +0 -310
  76. package/packages/core/tests/parse-trigger.test.ts +0 -282
  77. package/packages/core/tests/search.test.ts +0 -374
  78. package/packages/core/tests/subpath.test.ts +0 -87
  79. package/packages/core/tsconfig.json +0 -10
  80. package/packages/opencode-agent-skills-md/package.json +0 -66
  81. package/packages/opencode-agent-skills-md/rolldown.config.js +0 -47
  82. package/packages/opencode-agent-skills-md/tests/cli-commands.test.ts +0 -1423
  83. package/packages/opencode-agent-skills-md/tests/e2e/startup-smoke.test.ts +0 -66
  84. package/packages/opencode-agent-skills-md/tests/fixtures/skills/home/.claude/skills/claude-user-only-skill/SKILL.md +0 -8
  85. package/packages/opencode-agent-skills-md/tests/fixtures/skills/home/.config/opencode/skills/shared-skill/SKILL.md +0 -8
  86. package/packages/opencode-agent-skills-md/tests/fixtures/skills/home/.config/opencode/skills/user-only-skill/SKILL.md +0 -8
  87. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.claude/skills/claude-project-only-skill/SKILL.md +0 -8
  88. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/go-tester/SKILL.md +0 -12
  89. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/nested/team/nested-skill/SKILL.md +0 -8
  90. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/rust-tester/SKILL.md +0 -11
  91. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/scripted-skill/SKILL.md +0 -8
  92. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/scripted-skill/bin/echo.sh +0 -2
  93. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/scripted-skill/docs/reference.md +0 -1
  94. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/shared-skill/SKILL.md +0 -8
  95. package/packages/opencode-agent-skills-md/tests/fixtures/skills/project/.opencode/skills/using-superpowers/SKILL.md +0 -8
  96. package/packages/opencode-agent-skills-md/tests/integration/helpers/mock-opencode.ts +0 -114
  97. package/packages/opencode-agent-skills-md/tests/integration/plugin.test.ts +0 -316
  98. package/packages/opencode-agent-skills-md/tests/integration/skill-discovery.test.ts +0 -315
  99. package/packages/opencode-agent-skills-md/tests/opencode/host.test.ts +0 -179
  100. package/packages/opencode-agent-skills-md/tests/opencode/plugin.test.ts +0 -551
  101. package/packages/opencode-agent-skills-md/tests/opencode/subpath.test.ts +0 -66
  102. package/packages/opencode-agent-skills-md/tests/opencode/tools.test.ts +0 -213
  103. package/packages/opencode-agent-skills-md/tests/package-boundary.test.ts +0 -345
  104. package/packages/opencode-agent-skills-md/tests/tools-security.test.ts +0 -72
  105. package/packages/opencode-agent-skills-md/tsconfig.build.json +0 -11
  106. package/packages/opencode-agent-skills-md/tsconfig.json +0 -10
  107. package/plans/001-ci-gate.md +0 -177
  108. package/plans/002-is-path-safe.md +0 -243
  109. package/plans/003-escape-prompts.md +0 -310
  110. package/plans/004-test-security-paths.md +0 -228
  111. package/plans/005-stop-swallowing-errors.md +0 -246
  112. package/plans/006-preserve-jsonc-commas.md +0 -144
  113. package/plans/007-write-before-purge.md +0 -144
  114. package/plans/008-reuse-walkdir-for-list-skill-files.md +0 -164
  115. package/plans/README.md +0 -43
  116. package/pnpm-workspace.yaml +0 -6
  117. package/tests/workspace.test.ts +0 -367
  118. package/tsconfig.json +0 -15
  119. /package/{packages/opencode-agent-skills-md/src → src}/cli/config.ts +0 -0
  120. /package/{packages/opencode-agent-skills-md/src → src}/cli/install.ts +0 -0
  121. /package/{packages/opencode-agent-skills-md/src → src}/cli/main.ts +0 -0
  122. /package/{packages/opencode-agent-skills-md/src → src}/cli/real-fs.ts +0 -0
  123. /package/{packages/opencode-agent-skills-md/src → src}/cli/status.ts +0 -0
  124. /package/{packages/opencode-agent-skills-md/src → src}/cli/uninstall.ts +0 -0
  125. /package/{packages/opencode-agent-skills-md/src → src}/host.ts +0 -0
  126. /package/{packages/opencode-agent-skills-md/src → src}/index.ts +0 -0
  127. /package/{packages/opencode-agent-skills-md/src → src}/plugin.ts +0 -0
  128. /package/{packages/opencode-agent-skills-md/src → src}/sdk.ts +0 -0
  129. /package/{packages/opencode-agent-skills-md/src → src}/tools.ts +0 -0
@@ -1,346 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
3
- import { readdir, readFile, stat } from "node:fs/promises";
4
- import * as path from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { describe, test, before, after, mock } from "node:test";
7
-
8
- /**
9
- * Agnostic-core contract test.
10
- *
11
- * Proves two things about `src/core/`:
12
- * 1. The core can be exercised end-to-end (discover -> parse -> resolve)
13
- * against a throwaway workspace with no OpenCode coupling in the test.
14
- * 2. No file under `src/core/` references the OpenCode host SDK anywhere
15
- * in its source text (imports, comments, strings).
16
- *
17
- * The static walk uses literal text matching instead of `require.resolve` so
18
- * it is deterministic across machines and does not depend on the build.
19
- */
20
- describe("agnostic core", () => {
21
- let workspace: string;
22
- const coreDir = path.resolve(import.meta.dirname, "..", "src");
23
-
24
- before(async () => {
25
- workspace = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-core-agnostic-"));
26
- const skillDir = path.join(workspace, ".opencode", "skills", "foo");
27
- await mkdir(skillDir, { recursive: true });
28
- await writeFile(
29
- path.join(skillDir, "SKILL.md"),
30
- [
31
- "---",
32
- "name: foo",
33
- "description: a fixture skill for the agnostic core test",
34
- "metadata:",
35
- " namespace: fixtures",
36
- "---",
37
- "",
38
- "# Foo",
39
- "",
40
- "Body content for the foo skill.",
41
- ].join("\n"),
42
- "utf8"
43
- );
44
- });
45
-
46
- after(async () => {
47
- if (workspace) {
48
- await rm(workspace, { recursive: true, force: true });
49
- }
50
- });
51
-
52
- test("core exposes discoverAllSkills, parseSkillFile, and resolveSkill", async () => {
53
- const core = await import("../src/index.ts");
54
-
55
- assert.equal(typeof core.discoverAllSkills, "function");
56
- assert.equal(typeof core.parseSkillFile, "function");
57
- assert.equal(typeof core.resolveSkill, "function");
58
- });
59
-
60
- test("discoverAllSkills finds the foo skill in a temp workspace", async () => {
61
- const { discoverAllSkills, parseSkillFile, resolveSkill } = await import("../src/index.ts");
62
-
63
- const skills = await discoverAllSkills(workspace);
64
- const foo = skills.get("foo");
65
-
66
- assert.ok(foo, "expected foo skill to be discovered");
67
- assert.equal(foo?.description, "a fixture skill for the agnostic core test");
68
- assert.equal(foo?.label, "project");
69
- assert.equal(foo?.namespace, "fixtures");
70
-
71
- // Re-parse through the public parseSkillFile entrypoint and confirm the
72
- // returned Skill is structurally equivalent to the discovered one.
73
- const skillPath = path.join(workspace, ".opencode", "skills", "foo", "SKILL.md");
74
- const reparsed = await parseSkillFile(skillPath, "foo", "project");
75
- assert.ok(reparsed, "expected parseSkillFile to return a Skill");
76
- assert.equal(reparsed?.name, "foo");
77
- assert.equal(reparsed?.template.includes("Body content for the foo skill."), true);
78
-
79
- // resolveSkill handles the bare name and the namespaced form.
80
- assert.equal(resolveSkill("foo", skills)?.name, "foo");
81
- assert.equal(resolveSkill("project:foo", skills)?.name, "foo");
82
- assert.equal(resolveSkill("fixtures:foo", skills)?.name, "foo");
83
- assert.equal(resolveSkill("nope", skills), null);
84
- });
85
-
86
- test("src/core contains zero references to the OpenCode host SDK", async () => {
87
- const violations: Array<{ file: string; line: number; text: string }> = [];
88
-
89
- async function walk(dir: string): Promise<void> {
90
- const entries = await readdir(dir, { withFileTypes: true });
91
- for (const entry of entries) {
92
- const fullPath = path.join(dir, entry.name);
93
- const stats = await stat(fullPath);
94
- if (stats.isDirectory()) {
95
- await walk(fullPath);
96
- } else if (stats.isFile() && entry.name.endsWith(".ts")) {
97
- const text = await readFile(fullPath, "utf8");
98
- const lines = text.split("\n");
99
- for (let i = 0; i < lines.length; i++) {
100
- const line = lines[i] ?? "";
101
- if (line.includes("@opencode-ai/plugin")) {
102
- violations.push({ file: fullPath, line: i + 1, text: line.trim() });
103
- }
104
- }
105
- }
106
- }
107
- }
108
-
109
- await walk(coreDir);
110
-
111
- assert.deepEqual(
112
- violations,
113
- [],
114
- `expected zero references to the host SDK under packages/core/src, found: ${JSON.stringify(violations)}`
115
- );
116
- });
117
-
118
- test("discovers a SKILL.md placed at the root of a discovery baseDir", async () => {
119
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-root-"));
120
- const opencodeSkills = path.join(rootDir, ".opencode", "skills");
121
- await mkdir(opencodeSkills, { recursive: true });
122
- await writeFile(
123
- path.join(opencodeSkills, "SKILL.md"),
124
- [
125
- "---",
126
- "name: root-skill",
127
- "description: a skill defined at the baseDir root",
128
- "---",
129
- "",
130
- "# Root",
131
- "",
132
- "Body for the root-level skill.",
133
- ].join("\n"),
134
- "utf8"
135
- );
136
-
137
- try {
138
- const { discoverAllSkills } = await import("../src/index.ts");
139
- const skills = await discoverAllSkills(rootDir);
140
-
141
- const found = skills.get("root-skill");
142
- assert.ok(found, "expected root-skill to be discovered from the baseDir root");
143
- assert.equal(found?.relativePath, "");
144
- assert.equal(found?.label, "project");
145
- } finally {
146
- await rm(rootDir, { recursive: true, force: true });
147
- }
148
- });
149
-
150
- test("root-level SKILL.md wins the shadowing tie-break over a same-name subdir", async () => {
151
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-root-tie-"));
152
- const opencodeSkills = path.join(rootDir, ".opencode", "skills");
153
- await mkdir(opencodeSkills, { recursive: true });
154
-
155
- await writeFile(
156
- path.join(opencodeSkills, "SKILL.md"),
157
- [
158
- "---",
159
- "name: shared",
160
- "description: root version wins",
161
- "---",
162
- "",
163
- "# Root",
164
- ].join("\n"),
165
- "utf8"
166
- );
167
-
168
- const subdir = path.join(opencodeSkills, "shared");
169
- await mkdir(subdir, { recursive: true });
170
- await writeFile(
171
- path.join(subdir, "SKILL.md"),
172
- [
173
- "---",
174
- "name: shared",
175
- "description: subdir version is shadowed",
176
- "---",
177
- "",
178
- "# Subdir",
179
- ].join("\n"),
180
- "utf8"
181
- );
182
-
183
- try {
184
- const { discoverAllSkills } = await import("../src/index.ts");
185
- const skills = await discoverAllSkills(rootDir);
186
-
187
- const found = skills.get("shared");
188
- assert.ok(found, "expected shared skill to be discovered");
189
- assert.equal(found?.description, "root version wins");
190
- assert.equal(found?.relativePath, "");
191
- } finally {
192
- await rm(rootDir, { recursive: true, force: true });
193
- }
194
- });
195
-
196
- test("discovers a nested skill at depth 3 under .claude/skills (maxDepth=3)", async () => {
197
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-depth-"));
198
- const deepDir = path.join(rootDir, ".claude", "skills", "foo", "bar", "baz");
199
- await mkdir(deepDir, { recursive: true });
200
- await writeFile(
201
- path.join(deepDir, "SKILL.md"),
202
- [
203
- "---",
204
- "name: deep-skill",
205
- "description: nested two subdir levels under .claude/skills",
206
- "---",
207
- "",
208
- "# Deep",
209
- ].join("\n"),
210
- "utf8"
211
- );
212
-
213
- try {
214
- const { discoverAllSkills } = await import("../src/index.ts");
215
- const skills = await discoverAllSkills(rootDir);
216
-
217
- const found = skills.get("deep-skill");
218
- assert.ok(found, "expected deep-skill to be discovered with maxDepth=3");
219
- assert.equal(found?.label, "claude-project");
220
- } finally {
221
- await rm(rootDir, { recursive: true, force: true });
222
- }
223
- });
224
-
225
- test("does not discover skills beyond depth 3 (maxDepth cap preserved)", async () => {
226
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-deep-cap-"));
227
- const tooDeep = path.join(rootDir, ".claude", "skills", "a", "b", "c", "d", "e");
228
- await mkdir(tooDeep, { recursive: true });
229
- await writeFile(
230
- path.join(tooDeep, "SKILL.md"),
231
- [
232
- "---",
233
- "name: too-deep",
234
- "description: lives 4 levels under .claude/skills",
235
- "---",
236
- "",
237
- "# Too Deep",
238
- ].join("\n"),
239
- "utf8"
240
- );
241
-
242
- try {
243
- const { discoverAllSkills } = await import("../src/index.ts");
244
- const skills = await discoverAllSkills(rootDir);
245
-
246
- assert.equal(skills.get("too-deep"), undefined, "depth-4 skill must be skipped");
247
- } finally {
248
- await rm(rootDir, { recursive: true, force: true });
249
- }
250
- });
251
-
252
- test("onDuplicate callback fires when two roots provide the same skill name", async () => {
253
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-dup-root-"));
254
- const homeDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-dup-home-"));
255
- const previousHome = process.env.HOME;
256
- process.env.HOME = homeDir;
257
-
258
- try {
259
- const projectSkill = path.join(rootDir, ".opencode", "skills", "shared-skill");
260
- await mkdir(projectSkill, { recursive: true });
261
- await writeFile(
262
- path.join(projectSkill, "SKILL.md"),
263
- [
264
- "---",
265
- "name: shared-skill",
266
- "description: project version wins",
267
- "---",
268
- "",
269
- "# Project",
270
- ].join("\n"),
271
- "utf8"
272
- );
273
-
274
- const userSkill = path.join(homeDir, ".claude", "skills", "shared-skill");
275
- await mkdir(userSkill, { recursive: true });
276
- await writeFile(
277
- path.join(userSkill, "SKILL.md"),
278
- [
279
- "---",
280
- "name: shared-skill",
281
- "description: user version should be shadowed",
282
- "---",
283
- "",
284
- "# User",
285
- ].join("\n"),
286
- "utf8"
287
- );
288
-
289
- const duplicateSpy = mock.fn((_existing: unknown, _dup: unknown) => {});
290
- const { discoverAllSkills } = await import("../src/index.ts");
291
- const skills = await discoverAllSkills(rootDir, undefined, duplicateSpy);
292
-
293
- // First match wins: project should be retained.
294
- assert.equal(skills.get("shared-skill")?.label, "project");
295
- assert.equal(skills.get("shared-skill")?.description, "project version wins");
296
- assert.equal(duplicateSpy.mock.calls.length, 1);
297
-
298
- const call = duplicateSpy.mock.calls[0];
299
- assert.ok(call, "expected the duplicate callback to have been called");
300
- const [existing, duplicate] = call.arguments as [
301
- { label: string; path: string },
302
- { label: string; path: string }
303
- ];
304
- assert.equal(existing.label, "project");
305
- assert.equal(duplicate.label, "claude-user");
306
- assert.match(duplicate.path, new RegExp(`${path.sep}shared-skill$`));
307
- } finally {
308
- process.env.HOME = previousHome;
309
- await rm(rootDir, { recursive: true, force: true });
310
- await rm(homeDir, { recursive: true, force: true });
311
- }
312
- });
313
-
314
- test("onDuplicate callback is NOT called when all discovered skills are unique", async () => {
315
- const rootDir = await mkdtemp(path.join(tmpdir(), "opencode-agent-skills-md-uniq-"));
316
- const opencodeSkills = path.join(rootDir, ".opencode", "skills", "alpha");
317
- await mkdir(opencodeSkills, { recursive: true });
318
- await writeFile(
319
- path.join(opencodeSkills, "SKILL.md"),
320
- [
321
- "---",
322
- "name: alpha",
323
- "description: only skill in this workspace",
324
- "---",
325
- "",
326
- "# Alpha",
327
- ].join("\n"),
328
- "utf8"
329
- );
330
-
331
- try {
332
- const duplicateSpy = mock.fn((_existing: unknown, _dup: unknown) => {});
333
- // Pin roots to a single project root so the user's real HOME does not
334
- // create an unintended duplicate that would taint the assertion.
335
- const roots = [
336
- { path: path.join(rootDir, ".opencode", "skills"), label: "project" as const, maxDepth: 3 },
337
- ];
338
- const { discoverAllSkills } = await import("../src/index.ts");
339
- await discoverAllSkills(rootDir, roots, duplicateSpy);
340
-
341
- assert.equal(duplicateSpy.mock.calls.length, 0);
342
- } finally {
343
- await rm(rootDir, { recursive: true, force: true });
344
- }
345
- });
346
- });
@@ -1,65 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, test } from "node:test";
3
- import type { Skill } from "../src/index";
4
-
5
- /**
6
- * `formatSkillListing` and `renderAvailableSkillsBlock` are the
7
- * always-on renderers used by `<available-skills>...</available-skills>`.
8
- *
9
- * The trigger-aware design (D3) is explicit: the always-on block stays
10
- * compact (`- name: description`). Trigger text leaks only into the
11
- * targeted outputs (matched-skill injection, `get_available_skills`).
12
- *
13
- * These tests guard that contract: even when skills carry a non-empty
14
- * `trigger`, the always-on block must NOT include it.
15
- */
16
- function makeSkill(overrides: Partial<Skill> = {}): Skill {
17
- return {
18
- name: "default-skill",
19
- description: "default description",
20
- path: "/default",
21
- relativePath: "default",
22
- label: "project",
23
- scripts: [],
24
- template: "",
25
- tags: [],
26
- ...overrides,
27
- } as Skill;
28
- }
29
-
30
- describe("formatSkillListing", () => {
31
- test("renders `- name: description` and omits the trigger (R5, D3)", async () => {
32
- const { formatSkillListing } = await import("../src/index");
33
- const skills: Skill[] = [
34
- makeSkill({ name: "alpha", description: "first skill", trigger: "auth, login" }),
35
- makeSkill({ name: "bravo", description: "second skill" }),
36
- ];
37
-
38
- const output = formatSkillListing(skills);
39
-
40
- assert.match(output, /^- alpha: first skill$/m);
41
- assert.match(output, /^- bravo: second skill$/m);
42
- assert.doesNotMatch(output, /trigger:/, "trigger text must NOT appear in the always-on block");
43
- });
44
-
45
- test("an empty skill list renders an empty string", async () => {
46
- const { formatSkillListing } = await import("../src/index");
47
- assert.equal(formatSkillListing([]), "");
48
- });
49
- });
50
-
51
- describe("renderAvailableSkillsBlock", () => {
52
- test("wraps the compact listing and never leaks trigger text (R5, D3)", async () => {
53
- const { renderAvailableSkillsBlock } = await import("../src/index");
54
- const skills: Skill[] = [
55
- makeSkill({ name: "alpha", description: "first skill", trigger: "auth, login" }),
56
- ];
57
-
58
- const output = renderAvailableSkillsBlock(skills);
59
-
60
- assert.match(output, /<available-skills>/);
61
- assert.match(output, /<\/available-skills>/);
62
- assert.match(output, /^- alpha: first skill$/m);
63
- assert.doesNotMatch(output, /trigger:/, "trigger text must NOT appear in the always-on block");
64
- });
65
- });