vigiles 2.1.1 → 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.
- package/README.md +76 -5
- package/dist/action-gate.d.ts +28 -0
- package/dist/action-gate.js +73 -0
- package/dist/cli.js +408 -75
- package/dist/community-skills.d.ts +22 -0
- package/dist/community-skills.js +86 -0
- package/dist/compile-generator.d.ts +48 -0
- package/dist/compile-generator.js +322 -0
- package/dist/compile.d.ts +3 -0
- package/dist/compile.js +217 -26
- package/dist/eval.d.ts +62 -0
- package/dist/eval.js +174 -0
- package/dist/frontmatter.d.ts +24 -6
- package/dist/frontmatter.js +103 -30
- package/dist/generate-schema.js +10 -0
- package/dist/harness-test.d.ts +38 -0
- package/dist/harness-test.js +129 -0
- package/dist/inline.d.ts +22 -4
- package/dist/inline.js +60 -13
- package/dist/linters.js +28 -0
- package/dist/mock-model.d.ts +31 -0
- package/dist/mock-model.js +189 -0
- package/dist/refs.d.ts +44 -0
- package/dist/refs.js +144 -0
- package/dist/skill-driver.d.ts +77 -0
- package/dist/skill-driver.js +76 -0
- package/dist/skill-runtime.d.ts +101 -0
- package/dist/skill-runtime.js +289 -0
- package/dist/skill-test.d.ts +47 -0
- package/dist/skill-test.js +77 -0
- package/dist/spec.d.ts +90 -4
- package/dist/spec.js +29 -0
- package/dist/symbols.d.ts +30 -0
- package/dist/symbols.js +142 -0
- package/package.json +14 -5
|
@@ -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,7 +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;
|
|
31
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;
|
|
32
35
|
export interface CompileClaudeResult {
|
|
33
36
|
markdown: string;
|
|
34
37
|
errors: CompileError[];
|