vigiles 2.0.0 → 2.2.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 (184) hide show
  1. package/README.md +182 -134
  2. package/dist/action-gate.d.ts +28 -0
  3. package/dist/action-gate.js +73 -0
  4. package/dist/cli.js +705 -230
  5. package/dist/community-skills.d.ts +22 -0
  6. package/dist/community-skills.js +86 -0
  7. package/dist/compile-generator.d.ts +48 -0
  8. package/dist/compile-generator.js +322 -0
  9. package/dist/compile.d.ts +4 -0
  10. package/dist/compile.js +239 -45
  11. package/dist/coverage.d.ts +56 -0
  12. package/dist/coverage.js +178 -0
  13. package/dist/doc-refs.d.ts +60 -0
  14. package/dist/doc-refs.js +246 -0
  15. package/dist/eval.d.ts +62 -0
  16. package/dist/eval.js +174 -0
  17. package/dist/evolve.js +60 -125
  18. package/dist/frontmatter.d.ts +75 -0
  19. package/dist/frontmatter.js +263 -0
  20. package/dist/generate-schema.d.ts +51 -0
  21. package/dist/generate-schema.js +125 -0
  22. package/dist/generate-types.js +39 -1
  23. package/dist/harness-test.d.ts +38 -0
  24. package/dist/harness-test.js +129 -0
  25. package/dist/hash.d.ts +8 -0
  26. package/dist/hash.js +16 -0
  27. package/dist/inline.d.ts +22 -4
  28. package/dist/inline.js +60 -13
  29. package/dist/integrity.d.ts +29 -0
  30. package/dist/integrity.js +44 -0
  31. package/dist/linters.d.ts +5 -3
  32. package/dist/linters.js +144 -4
  33. package/dist/mock-model.d.ts +31 -0
  34. package/dist/mock-model.js +189 -0
  35. package/dist/orphans.d.ts +52 -0
  36. package/dist/orphans.js +124 -0
  37. package/dist/proofs.js +22 -16
  38. package/dist/refs.d.ts +44 -0
  39. package/dist/refs.js +144 -0
  40. package/dist/session.d.ts +97 -0
  41. package/dist/session.js +306 -0
  42. package/dist/sidecar.d.ts +35 -0
  43. package/dist/sidecar.js +102 -0
  44. package/dist/skill-driver.d.ts +77 -0
  45. package/dist/skill-driver.js +76 -0
  46. package/dist/skill-runtime.d.ts +101 -0
  47. package/dist/skill-runtime.js +289 -0
  48. package/dist/skill-test.d.ts +47 -0
  49. package/dist/skill-test.js +77 -0
  50. package/dist/spec.d.ts +119 -13
  51. package/dist/spec.js +51 -4
  52. package/dist/symbols.d.ts +30 -0
  53. package/dist/symbols.js +142 -0
  54. package/dist/test-utils.d.ts +8 -0
  55. package/dist/test-utils.js +41 -0
  56. package/dist/types.d.ts +34 -8
  57. package/dist/types.js +19 -0
  58. package/dist/validate.js +5 -3
  59. package/package.json +26 -5
  60. package/.claude/settings.json +0 -46
  61. package/.claude/settings.local.json +0 -8
  62. package/.github/workflows/ci.yml +0 -81
  63. package/.prettierignore +0 -1
  64. package/.vigiles/generated.d.ts +0 -205
  65. package/CLAUDE.md +0 -95
  66. package/CLAUDE.md.spec.ts +0 -142
  67. package/CONTRIBUTING.md +0 -121
  68. package/dist/action.d.ts.map +0 -1
  69. package/dist/action.js.map +0 -1
  70. package/dist/cli.d.ts.map +0 -1
  71. package/dist/cli.js.map +0 -1
  72. package/dist/cli.test.d.ts +0 -2
  73. package/dist/cli.test.d.ts.map +0 -1
  74. package/dist/cli.test.js +0 -650
  75. package/dist/cli.test.js.map +0 -1
  76. package/dist/compile.d.ts.map +0 -1
  77. package/dist/compile.js.map +0 -1
  78. package/dist/evolve.d.ts.map +0 -1
  79. package/dist/evolve.js.map +0 -1
  80. package/dist/freshness.d.ts +0 -67
  81. package/dist/freshness.d.ts.map +0 -1
  82. package/dist/freshness.js +0 -244
  83. package/dist/freshness.js.map +0 -1
  84. package/dist/freshness.test.d.ts +0 -2
  85. package/dist/freshness.test.d.ts.map +0 -1
  86. package/dist/freshness.test.js +0 -356
  87. package/dist/freshness.test.js.map +0 -1
  88. package/dist/generate-types.d.ts.map +0 -1
  89. package/dist/generate-types.js.map +0 -1
  90. package/dist/inline.d.ts.map +0 -1
  91. package/dist/inline.js.map +0 -1
  92. package/dist/inline.test.d.ts +0 -5
  93. package/dist/inline.test.d.ts.map +0 -1
  94. package/dist/inline.test.js +0 -152
  95. package/dist/inline.test.js.map +0 -1
  96. package/dist/linters.d.ts.map +0 -1
  97. package/dist/linters.js.map +0 -1
  98. package/dist/proofs.d.ts.map +0 -1
  99. package/dist/proofs.js.map +0 -1
  100. package/dist/proofs.test.d.ts +0 -9
  101. package/dist/proofs.test.d.ts.map +0 -1
  102. package/dist/proofs.test.js +0 -952
  103. package/dist/proofs.test.js.map +0 -1
  104. package/dist/spec.d.ts.map +0 -1
  105. package/dist/spec.js.map +0 -1
  106. package/dist/spec.test.d.ts +0 -2
  107. package/dist/spec.test.d.ts.map +0 -1
  108. package/dist/spec.test.js +0 -1222
  109. package/dist/spec.test.js.map +0 -1
  110. package/dist/types.d.ts.map +0 -1
  111. package/dist/types.js.map +0 -1
  112. package/dist/validate.d.ts.map +0 -1
  113. package/dist/validate.js.map +0 -1
  114. package/dist/validate.test.d.ts +0 -2
  115. package/dist/validate.test.d.ts.map +0 -1
  116. package/dist/validate.test.js +0 -531
  117. package/dist/validate.test.js.map +0 -1
  118. package/docs/agent-setup.md +0 -85
  119. package/docs/agent-workflows.md +0 -103
  120. package/docs/comparison.md +0 -71
  121. package/docs/freshness.md +0 -124
  122. package/docs/inline-mode.md +0 -119
  123. package/docs/linter-support.md +0 -166
  124. package/docs/spec-format.md +0 -194
  125. package/eslint.config.mjs +0 -79
  126. package/examples/CLAUDE.md +0 -54
  127. package/examples/CLAUDE.md.spec.ts +0 -65
  128. package/examples/SKILL.md +0 -50
  129. package/examples/SKILL.md.spec.ts +0 -57
  130. package/fixtures/example-project/CLAUDE.md +0 -11
  131. package/fixtures/example-project/package.json +0 -9
  132. package/fixtures/example-project/src/index.ts +0 -3
  133. package/fixtures/example-project/src/utils.test.ts +0 -2
  134. package/fixtures/example-project/src/utils.ts +0 -3
  135. package/logo.png +0 -0
  136. package/research/adoption-strategy.md +0 -111
  137. package/research/agent-integration.md +0 -145
  138. package/research/ai-code-quality.md +0 -197
  139. package/research/code-search-for-agents.md +0 -313
  140. package/research/competitive-landscape.md +0 -163
  141. package/research/doc-freshness.md +0 -516
  142. package/research/executable-specs.md +0 -368
  143. package/research/feature-ideas.md +0 -464
  144. package/research/formal-proofs-for-agents.md +0 -338
  145. package/research/fp-for-agent-harness.md +0 -150
  146. package/research/fp-for-deterministic-ai.md +0 -131
  147. package/research/self-evolving-specs.md +0 -298
  148. package/schemas/claude-md-strict.yml +0 -18
  149. package/schemas/claude-md.yml +0 -6
  150. package/schemas/skill-strict.yml +0 -12
  151. package/schemas/skill.yml +0 -5
  152. package/skills/audit-feedback-loop/SKILL.md +0 -76
  153. package/skills/edit-spec/SKILL.md +0 -131
  154. package/skills/enforce-rules-format/SKILL.md +0 -71
  155. package/skills/generate-logo/SKILL.md +0 -102
  156. package/skills/generate-rule/SKILL.md +0 -90
  157. package/skills/linter-docs/clippy.md +0 -241
  158. package/skills/linter-docs/eslint.md +0 -384
  159. package/skills/linter-docs/pylint.md +0 -288
  160. package/skills/linter-docs/rubocop.md +0 -277
  161. package/skills/linter-docs/ruff.md +0 -187
  162. package/skills/linter-docs/stylelint.md +0 -247
  163. package/skills/migrate-to-spec/SKILL.md +0 -124
  164. package/skills/pr-to-lint-rule/SKILL.md +0 -97
  165. package/skills/strengthen/SKILL.md +0 -168
  166. package/src/action.ts +0 -214
  167. package/src/cli.test.ts +0 -914
  168. package/src/cli.ts +0 -1631
  169. package/src/compile.ts +0 -691
  170. package/src/evolve.ts +0 -781
  171. package/src/freshness.test.ts +0 -449
  172. package/src/freshness.ts +0 -299
  173. package/src/generate-types.ts +0 -448
  174. package/src/inline.test.ts +0 -206
  175. package/src/inline.ts +0 -164
  176. package/src/linters.ts +0 -739
  177. package/src/proofs.test.ts +0 -1314
  178. package/src/proofs.ts +0 -849
  179. package/src/spec.test.ts +0 -1471
  180. package/src/spec.ts +0 -427
  181. package/src/types.ts +0 -117
  182. package/src/validate.test.ts +0 -701
  183. package/src/validate.ts +0 -381
  184. package/tsconfig.json +0 -23
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Ported community skills — proof that the generator form expresses the real,
3
+ * praised "agentic" skills (the deep tail the flat model can't hold), and that
4
+ * they are deterministically testable. Structural ports (not verbatim prose) of:
5
+ *
6
+ * - devonjones/pr-review-loop — the corpus stress test: a bounded round loop
7
+ * (7-round ceiling) with a quality-weighted exit, a per-finding for-each, and
8
+ * a nested bounded CI retry sub-loop.
9
+ * - test-driven-development (superpowers) — the red→green→refactor cycle.
10
+ * - subagent-driven-development (superpowers) — per-task for-each with two
11
+ * nested bounded review loops.
12
+ *
13
+ * These are exercised in community-skills.test.ts with a scripted model.
14
+ */
15
+ import { type SkillProgram } from "./skill-driver.js";
16
+ /** COLLECT → BATCH → FIX rounds; ceiling 7; exit when no actionable feedback. */
17
+ export declare const prReviewLoop: SkillProgram;
18
+ /** Red → Green → Refactor, once per behavior until done. */
19
+ export declare const tdd: SkillProgram;
20
+ /** Per task: bounded spec review, implement, bounded quality review, gate. */
21
+ export declare const subagentDriven: SkillProgram;
22
+ //# sourceMappingURL=community-skills.d.ts.map
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.subagentDriven = exports.tdd = exports.prReviewLoop = void 0;
4
+ /**
5
+ * Ported community skills — proof that the generator form expresses the real,
6
+ * praised "agentic" skills (the deep tail the flat model can't hold), and that
7
+ * they are deterministically testable. Structural ports (not verbatim prose) of:
8
+ *
9
+ * - devonjones/pr-review-loop — the corpus stress test: a bounded round loop
10
+ * (7-round ceiling) with a quality-weighted exit, a per-finding for-each, and
11
+ * a nested bounded CI retry sub-loop.
12
+ * - test-driven-development (superpowers) — the red→green→refactor cycle.
13
+ * - subagent-driven-development (superpowers) — per-task for-each with two
14
+ * nested bounded review loops.
15
+ *
16
+ * These are exercised in community-skills.test.ts with a scripted model.
17
+ */
18
+ const skill_driver_js_1 = require("./skill-driver.js");
19
+ const spec_js_1 = require("./spec.js");
20
+ /** COLLECT → BATCH → FIX rounds; ceiling 7; exit when no actionable feedback. */
21
+ const prReviewLoop = function* () {
22
+ let round = 0;
23
+ for (;;) {
24
+ round++;
25
+ yield (0, skill_driver_js_1.act)("Collect all review comments on the PR");
26
+ yield (0, skill_driver_js_1.act)("Batch findings by file and severity");
27
+ for (;;) {
28
+ const next = yield (0, skill_driver_js_1.act)("Take the next P1/P2 finding, or say 'done'");
29
+ if (next === "done")
30
+ break;
31
+ yield (0, skill_driver_js_1.act)("Fix this finding");
32
+ }
33
+ let attempt = 0;
34
+ let ci = "fail";
35
+ while (attempt < 3 && ci !== "pass") {
36
+ attempt++;
37
+ ci = yield (0, skill_driver_js_1.act)("Run CI and report 'pass' or 'fail'");
38
+ }
39
+ yield (0, skill_driver_js_1.checkpoint)((0, spec_js_1.cmd)("true")); // CI must be green to continue the round
40
+ const more = yield (0, skill_driver_js_1.act)("Is there actionable (P1/P2) feedback left? 'yes'/'no'");
41
+ if (more === "no" || round >= 7)
42
+ break;
43
+ }
44
+ yield (0, skill_driver_js_1.finish)((0, spec_js_1.cmd)("true"));
45
+ };
46
+ exports.prReviewLoop = prReviewLoop;
47
+ /** Red → Green → Refactor, once per behavior until done. */
48
+ const tdd = function* () {
49
+ for (;;) {
50
+ const feature = yield (0, skill_driver_js_1.act)("Pick the next behavior to implement, or 'done'");
51
+ if (feature === "done")
52
+ break;
53
+ yield (0, skill_driver_js_1.act)("Write a failing test and watch it fail (RED)");
54
+ yield (0, skill_driver_js_1.act)("Write the minimum code to pass (GREEN)");
55
+ yield (0, skill_driver_js_1.checkpoint)((0, spec_js_1.cmd)("true")); // tests pass
56
+ yield (0, skill_driver_js_1.act)("Refactor while keeping tests green");
57
+ yield (0, skill_driver_js_1.checkpoint)((0, spec_js_1.cmd)("true")); // tests still pass
58
+ }
59
+ yield (0, skill_driver_js_1.finish)((0, spec_js_1.cmd)("true"));
60
+ };
61
+ exports.tdd = tdd;
62
+ /** Per task: bounded spec review, implement, bounded quality review, gate. */
63
+ const subagentDriven = function* () {
64
+ for (;;) {
65
+ const task = yield (0, skill_driver_js_1.act)("Take the next task from the plan, or 'done'");
66
+ if (task === "done")
67
+ break;
68
+ let a1 = 0;
69
+ let specOk = "no";
70
+ while (a1 < 3 && specOk !== "yes") {
71
+ a1++;
72
+ specOk = yield (0, skill_driver_js_1.act)("Dispatch a subagent to review the spec; approved? yes/no");
73
+ }
74
+ yield (0, skill_driver_js_1.act)("Implement the task");
75
+ let a2 = 0;
76
+ let qualityOk = "no";
77
+ while (a2 < 3 && qualityOk !== "yes") {
78
+ a2++;
79
+ qualityOk = yield (0, skill_driver_js_1.act)("Dispatch a subagent for quality review; approved? yes/no");
80
+ }
81
+ yield (0, skill_driver_js_1.checkpoint)((0, spec_js_1.cmd)("true"));
82
+ }
83
+ yield (0, skill_driver_js_1.finish)((0, spec_js_1.cmd)("true"));
84
+ };
85
+ exports.subagentDriven = subagentDriven;
86
+ //# sourceMappingURL=community-skills.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * vigiles — Generator → SKILL.md compiler.
3
+ *
4
+ * A generator skill (`function* () { … yield act/gate/result … }`) has dynamic
5
+ * control flow, so it can't be rendered by *executing* it. Instead we parse its
6
+ * SOURCE with the TypeScript compiler API and render the structure to markdown:
7
+ *
8
+ * yield act("prose") → a prose step
9
+ * yield gate(cmd("npm test")) → a `vigiles:gate` marker (+ verified ref)
10
+ * yield result(cmd("npm test")) → a `vigiles:result` marker
11
+ * if (cond) { … } else { … } → "### If <cond>" / "### Otherwise"
12
+ * for / while (…) { … } → "### Repeat (…)"
13
+ *
14
+ * The emitted SKILL.md is what the agent reads (branches flattened to prose);
15
+ * the harness drives the real generator at run time (see `skill-driver.ts`).
16
+ * Gate references (`cmd`/`file`/`project`) are collected and verified, so the
17
+ * cross-referencing moat works on generators too (literal args only).
18
+ */
19
+ export interface GeneratorError {
20
+ type: "stale-file" | "stale-command";
21
+ message: string;
22
+ }
23
+ export interface CompileGeneratorResult {
24
+ markdown: string;
25
+ errors: GeneratorError[];
26
+ }
27
+ export interface CompileGeneratorSkillResult {
28
+ markdown: string;
29
+ errors: GeneratorError[];
30
+ }
31
+ /**
32
+ * Compile a generator-skill spec source — `genSkill({ name, description },
33
+ * function* () { … })` — to a full SKILL.md (frontmatter + body + integrity
34
+ * hash), verifying gate references. This is the CLI entry for generator skills.
35
+ */
36
+ export declare function compileGeneratorSkill(source: string, options?: {
37
+ basePath?: string;
38
+ specFile?: string;
39
+ }): CompileGeneratorSkillResult;
40
+ /**
41
+ * Compile a generator's SOURCE text to SKILL.md markdown + verified-ref
42
+ * errors. `frontmatter` (name/description/…) is prepended verbatim if given.
43
+ */
44
+ export declare function compileGenerator(source: string, options?: {
45
+ basePath?: string;
46
+ frontmatter?: string;
47
+ }): CompileGeneratorResult;
48
+ //# sourceMappingURL=compile-generator.d.ts.map
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ /**
3
+ * vigiles — Generator → SKILL.md compiler.
4
+ *
5
+ * A generator skill (`function* () { … yield act/gate/result … }`) has dynamic
6
+ * control flow, so it can't be rendered by *executing* it. Instead we parse its
7
+ * SOURCE with the TypeScript compiler API and render the structure to markdown:
8
+ *
9
+ * yield act("prose") → a prose step
10
+ * yield gate(cmd("npm test")) → a `vigiles:gate` marker (+ verified ref)
11
+ * yield result(cmd("npm test")) → a `vigiles:result` marker
12
+ * if (cond) { … } else { … } → "### If <cond>" / "### Otherwise"
13
+ * for / while (…) { … } → "### Repeat (…)"
14
+ *
15
+ * The emitted SKILL.md is what the agent reads (branches flattened to prose);
16
+ * the harness drives the real generator at run time (see `skill-driver.ts`).
17
+ * Gate references (`cmd`/`file`/`project`) are collected and verified, so the
18
+ * cross-referencing moat works on generators too (literal args only).
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.compileGeneratorSkill = compileGeneratorSkill;
25
+ exports.compileGenerator = compileGenerator;
26
+ const typescript_1 = __importDefault(require("typescript"));
27
+ const node_fs_1 = require("node:fs");
28
+ const node_path_1 = require("node:path");
29
+ const compile_js_1 = require("./compile.js");
30
+ /** Find the first generator function (`function*`) in a source file. */
31
+ function findGenerator(sf) {
32
+ let found = null;
33
+ const visit = (n) => {
34
+ if (found)
35
+ return;
36
+ if ((typescript_1.default.isFunctionExpression(n) || typescript_1.default.isFunctionDeclaration(n)) &&
37
+ n.asteriskToken) {
38
+ found = n;
39
+ return;
40
+ }
41
+ typescript_1.default.forEachChild(n, visit);
42
+ };
43
+ visit(sf);
44
+ return found;
45
+ }
46
+ /** The called function's name, handling both `act(...)` and `ns.act(...)`. */
47
+ function calleeName(call) {
48
+ const e = call.expression;
49
+ if (typescript_1.default.isIdentifier(e))
50
+ return e.text;
51
+ if (typescript_1.default.isPropertyAccessExpression(e))
52
+ return e.name.text;
53
+ return "";
54
+ }
55
+ /** A `cmd("…")` / `file("…")` / `project("…")` call → a gate reference. */
56
+ function extractGateRef(node) {
57
+ if (!node || !typescript_1.default.isCallExpression(node))
58
+ return null;
59
+ const name = calleeName(node);
60
+ const a = node.arguments[0];
61
+ if (!a || !typescript_1.default.isStringLiteralLike(a))
62
+ return null;
63
+ if (name === "cmd")
64
+ return { kind: "cmd", value: a.text };
65
+ if (name === "file")
66
+ return { kind: "file", value: a.text };
67
+ if (name === "project")
68
+ return { kind: "role", value: a.text };
69
+ return null;
70
+ }
71
+ /** The first YieldExpression inside a statement (e.g. `const x = yield act(...)`). */
72
+ function findYield(node) {
73
+ let y = null;
74
+ const visit = (n) => {
75
+ if (y)
76
+ return;
77
+ if (typescript_1.default.isYieldExpression(n)) {
78
+ y = n;
79
+ return;
80
+ }
81
+ typescript_1.default.forEachChild(n, visit);
82
+ };
83
+ visit(node);
84
+ return y;
85
+ }
86
+ function gateMarker(ref, terminal) {
87
+ const kind = terminal ? "result" : "gate";
88
+ if (ref.kind === "cmd")
89
+ return `<!-- vigiles:${kind} "${ref.value}" -->`;
90
+ if (ref.kind === "role")
91
+ return `<!-- vigiles:${kind} role:${ref.value} -->`;
92
+ return `<!-- vigiles:${kind} file:${ref.value} -->`;
93
+ }
94
+ function gateLabel(ref) {
95
+ if (ref.kind === "cmd")
96
+ return `\`${ref.value}\``;
97
+ if (ref.kind === "role")
98
+ return `the project's ${ref.value} command`;
99
+ return `${ref.value} exists`;
100
+ }
101
+ /** Render a single `yield act/gate/result(...)` into markdown lines. */
102
+ function renderYield(y, lines, refs) {
103
+ const call = y.expression;
104
+ if (!call || !typescript_1.default.isCallExpression(call))
105
+ return;
106
+ const name = calleeName(call);
107
+ if (name === "act") {
108
+ const a = call.arguments[0];
109
+ if (a && typescript_1.default.isStringLiteralLike(a))
110
+ lines.push(a.text.trim(), "");
111
+ return;
112
+ }
113
+ const terminal = name === "finish" || name === "result";
114
+ if (!terminal && name !== "gate" && name !== "checkpoint")
115
+ return;
116
+ const ref = extractGateRef(call.arguments[0]);
117
+ if (!ref)
118
+ return;
119
+ refs.push(ref);
120
+ if (terminal) {
121
+ lines.push("## Result", "", `Done when ${gateLabel(ref)} passes.`, "", gateMarker(ref, true), "");
122
+ }
123
+ else {
124
+ lines.push(`**Gate** — ${gateLabel(ref)}; do not proceed until it passes.`, "", gateMarker(ref, false), "");
125
+ }
126
+ }
127
+ /** A renderer bound to one source file (for `getText` of conditions). */
128
+ function makeRenderer(sf, refs) {
129
+ const lines = [];
130
+ const renderBlock = (stmt) => {
131
+ if (typescript_1.default.isBlock(stmt))
132
+ renderStatements(stmt.statements);
133
+ else
134
+ renderStatements([stmt]);
135
+ };
136
+ const renderIf = (s) => {
137
+ lines.push(`### If ${s.expression.getText(sf)}`, "");
138
+ renderBlock(s.thenStatement);
139
+ if (s.elseStatement) {
140
+ if (typescript_1.default.isIfStatement(s.elseStatement)) {
141
+ renderIf(s.elseStatement);
142
+ }
143
+ else {
144
+ lines.push(`### Otherwise`, "");
145
+ renderBlock(s.elseStatement);
146
+ }
147
+ }
148
+ };
149
+ const renderLoop = (s) => {
150
+ let header = "each iteration";
151
+ if (typescript_1.default.isWhileStatement(s))
152
+ header = `while ${s.expression.getText(sf)}`;
153
+ else if (typescript_1.default.isForOfStatement(s))
154
+ header = `for each item in ${s.expression.getText(sf)}`;
155
+ lines.push(`### Repeat (${header})`, "");
156
+ renderBlock(s.statement);
157
+ };
158
+ function renderStatements(stmts) {
159
+ for (const s of stmts) {
160
+ if (typescript_1.default.isVariableStatement(s) || typescript_1.default.isExpressionStatement(s)) {
161
+ const y = findYield(s);
162
+ if (y) {
163
+ renderYield(y, lines, refs);
164
+ continue;
165
+ }
166
+ }
167
+ if (typescript_1.default.isIfStatement(s)) {
168
+ renderIf(s);
169
+ continue;
170
+ }
171
+ if (typescript_1.default.isForStatement(s) ||
172
+ typescript_1.default.isForOfStatement(s) ||
173
+ typescript_1.default.isWhileStatement(s) ||
174
+ typescript_1.default.isDoStatement(s)) {
175
+ renderLoop(s);
176
+ continue;
177
+ }
178
+ // Other statements (plain logic, returns) carry no prose to render.
179
+ }
180
+ }
181
+ return {
182
+ run(body) {
183
+ renderStatements(body.statements);
184
+ return lines.join("\n").trimEnd();
185
+ },
186
+ };
187
+ }
188
+ /** Verify a collected gate reference against the project at `basePath`. */
189
+ function verifyRef(ref, basePath) {
190
+ if (ref.kind === "file") {
191
+ return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(basePath, ref.value))
192
+ ? null
193
+ : { type: "stale-file", message: `File not found: "${ref.value}"` };
194
+ }
195
+ if (ref.kind === "role")
196
+ return null; // resolved per host project at run time
197
+ // cmd: npm script or script-runner file
198
+ const npm = ref.value.match(/^npm\s+run\s+(\S+)/)?.[1];
199
+ if (npm) {
200
+ const scripts = (0, compile_js_1.readPackageScripts)(basePath);
201
+ return scripts && !scripts[npm]
202
+ ? {
203
+ type: "stale-command",
204
+ message: `Script "${npm}" not found in package.json`,
205
+ }
206
+ : null;
207
+ }
208
+ const scriptFile = ref.value.match(/^(?:python3?|node|bash|sh|ruby|deno run)\s+([^\s-]\S*\.[A-Za-z0-9]+)/)?.[1];
209
+ if (scriptFile && !(0, node_fs_1.existsSync)((0, node_path_1.resolve)(basePath, scriptFile))) {
210
+ return {
211
+ type: "stale-command",
212
+ message: `Script "${scriptFile}" not found`,
213
+ };
214
+ }
215
+ return null;
216
+ }
217
+ /** Find the `genSkill({…}, function* () {…})` call in a source file. */
218
+ function findGenSkillCall(sf) {
219
+ let found = null;
220
+ const visit = (n) => {
221
+ if (found)
222
+ return;
223
+ if (typescript_1.default.isCallExpression(n) && calleeName(n) === "genSkill") {
224
+ found = n;
225
+ return;
226
+ }
227
+ typescript_1.default.forEachChild(n, visit);
228
+ };
229
+ visit(sf);
230
+ return found;
231
+ }
232
+ /** Extract name/description/disable-model-invocation from a meta object literal. */
233
+ function extractMeta(obj) {
234
+ const meta = {};
235
+ for (const p of obj.properties) {
236
+ if (!typescript_1.default.isPropertyAssignment(p) || !typescript_1.default.isIdentifier(p.name))
237
+ continue;
238
+ const key = p.name.text;
239
+ const v = p.initializer;
240
+ if (typescript_1.default.isStringLiteralLike(v)) {
241
+ if (key === "name")
242
+ meta.name = v.text;
243
+ else if (key === "description")
244
+ meta.description = v.text;
245
+ }
246
+ else if (key === "disableModelInvocation") {
247
+ meta.disableModelInvocation = v.kind === typescript_1.default.SyntaxKind.TrueKeyword;
248
+ }
249
+ }
250
+ return meta;
251
+ }
252
+ /**
253
+ * Compile a generator-skill spec source — `genSkill({ name, description },
254
+ * function* () { … })` — to a full SKILL.md (frontmatter + body + integrity
255
+ * hash), verifying gate references. This is the CLI entry for generator skills.
256
+ */
257
+ function compileGeneratorSkill(source, options = {}) {
258
+ const basePath = options.basePath ?? process.cwd();
259
+ const specFile = options.specFile ?? "SKILL.md.spec.ts";
260
+ const sf = typescript_1.default.createSourceFile("s.ts", source, typescript_1.default.ScriptTarget.Latest, true);
261
+ const call = findGenSkillCall(sf);
262
+ const metaArg = call?.arguments[0];
263
+ const genArg = call?.arguments[1];
264
+ if (!metaArg ||
265
+ !typescript_1.default.isObjectLiteralExpression(metaArg) ||
266
+ !genArg ||
267
+ !typescript_1.default.isFunctionExpression(genArg) ||
268
+ !genArg.asteriskToken ||
269
+ !genArg.body) {
270
+ return {
271
+ markdown: "",
272
+ errors: [
273
+ {
274
+ type: "stale-command",
275
+ message: "Expected `genSkill({ name, description }, function* () { … })`.",
276
+ },
277
+ ],
278
+ };
279
+ }
280
+ const meta = extractMeta(metaArg);
281
+ const refs = [];
282
+ const body = makeRenderer(sf, refs).run(genArg.body);
283
+ const errors = refs
284
+ .map((r) => verifyRef(r, basePath))
285
+ .filter((e) => e !== null);
286
+ const fm = ["---", "", `name: ${meta.name ?? ""}`];
287
+ if (meta.description)
288
+ fm.push(`description: ${meta.description}`);
289
+ if (meta.disableModelInvocation !== undefined) {
290
+ fm.push(`disable-model-invocation: ${String(meta.disableModelInvocation)}`);
291
+ }
292
+ fm.push("", "---");
293
+ const content = `${fm.join("\n")}\n\n${body.trim()}\n`;
294
+ return { markdown: (0, compile_js_1.addHash)(content, specFile), errors };
295
+ }
296
+ /**
297
+ * Compile a generator's SOURCE text to SKILL.md markdown + verified-ref
298
+ * errors. `frontmatter` (name/description/…) is prepended verbatim if given.
299
+ */
300
+ function compileGenerator(source, options = {}) {
301
+ const basePath = options.basePath ?? process.cwd();
302
+ const sf = typescript_1.default.createSourceFile("skill.ts", source, typescript_1.default.ScriptTarget.Latest, true);
303
+ const gen = findGenerator(sf);
304
+ if (!gen?.body) {
305
+ return {
306
+ markdown: "",
307
+ errors: [
308
+ { type: "stale-command", message: "No generator function found." },
309
+ ],
310
+ };
311
+ }
312
+ const refs = [];
313
+ const body = makeRenderer(sf, refs).run(gen.body);
314
+ const errors = refs
315
+ .map((r) => verifyRef(r, basePath))
316
+ .filter((e) => e !== null);
317
+ const markdown = options.frontmatter
318
+ ? `${options.frontmatter.trim()}\n\n${body}\n`
319
+ : `${body}\n`;
320
+ return { markdown, errors };
321
+ }
322
+ //# sourceMappingURL=compile-generator.js.map
package/dist/compile.d.ts CHANGED
@@ -28,6 +28,10 @@ export interface CompileError {
28
28
  message: string;
29
29
  path?: string;
30
30
  }
31
+ export declare function validateFileRef(filePath: string, basePath: string): CompileError | null;
32
+ export declare function readPackageScripts(basePath: string): Record<string, string> | null;
33
+ export declare function validateCommandRef(command: string, basePath: string): CompileError | null;
34
+ export declare function validateSymbolRef(file: string, name: string, basePath: string): CompileError | null;
31
35
  export interface CompileClaudeResult {
32
36
  markdown: string;
33
37
  errors: CompileError[];