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
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,10 @@ const proofs_js_1 = require("./proofs.js");
|
|
|
21
21
|
const inline_js_1 = require("./inline.js");
|
|
22
22
|
const frontmatter_js_1 = require("./frontmatter.js");
|
|
23
23
|
const generate_schema_js_1 = require("./generate-schema.js");
|
|
24
|
+
const compile_generator_js_1 = require("./compile-generator.js");
|
|
25
|
+
const action_gate_js_1 = require("./action-gate.js");
|
|
26
|
+
const refs_js_1 = require("./refs.js");
|
|
27
|
+
const skill_runtime_js_1 = require("./skill-runtime.js");
|
|
24
28
|
const linters_js_1 = require("./linters.js");
|
|
25
29
|
const integrity_js_1 = require("./integrity.js");
|
|
26
30
|
const coverage_js_1 = require("./coverage.js");
|
|
@@ -102,9 +106,84 @@ function printErrors(specFile, errors) {
|
|
|
102
106
|
// ---------------------------------------------------------------------------
|
|
103
107
|
// Commands
|
|
104
108
|
// ---------------------------------------------------------------------------
|
|
109
|
+
/** Compile a generator-skill spec from source → SKILL.md. Returns validity. */
|
|
110
|
+
function compileGeneratorSkillToFile(specPath, source) {
|
|
111
|
+
const outputPath = specPath.replace(/\.spec\.ts$/, "");
|
|
112
|
+
const { markdown, errors } = (0, compile_generator_js_1.compileGeneratorSkill)(source, {
|
|
113
|
+
basePath: process.cwd(),
|
|
114
|
+
specFile: specPath,
|
|
115
|
+
});
|
|
116
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
117
|
+
if (errors.length === 0) {
|
|
118
|
+
console.log(`\n✓ ${specPath} → ${outputPath} (generator skill)`);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
|
|
122
|
+
for (const e of errors)
|
|
123
|
+
console.log(` ${e.type}: ${e.message}`);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/** Compile a ClaudeSpec → its primary + any additional targets. */
|
|
127
|
+
function compileClaudeToFile(spec, specPath, config) {
|
|
128
|
+
const basePath = process.cwd();
|
|
129
|
+
const { markdown, errors, linterResults, targets } = (0, compile_js_1.compileClaude)(spec, {
|
|
130
|
+
basePath,
|
|
131
|
+
specFile: specPath,
|
|
132
|
+
maxRules: config.maxRules,
|
|
133
|
+
maxTokens: config.maxTokens,
|
|
134
|
+
maxSectionLines: config.maxSectionLines,
|
|
135
|
+
catalogOnly: config.catalogOnly,
|
|
136
|
+
linters: config.linters,
|
|
137
|
+
});
|
|
138
|
+
const primaryOutput = specPath.replace(/\.spec\.ts$/, "");
|
|
139
|
+
if (errors.length > 0) {
|
|
140
|
+
console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
|
|
141
|
+
printErrors(specPath, errors);
|
|
142
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
|
|
146
|
+
const outputNames = [primaryOutput];
|
|
147
|
+
for (const t of targets.slice(1)) {
|
|
148
|
+
const body = markdown
|
|
149
|
+
.replace(/^<!-- vigiles:[^\n]+\n\n?/, "")
|
|
150
|
+
.replace(/^# [^\n]+/, `# ${t}`);
|
|
151
|
+
const dir = primaryOutput.substring(0, primaryOutput.lastIndexOf("/") + 1);
|
|
152
|
+
const targetPath = dir + t;
|
|
153
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, targetPath), (0, compile_js_1.addHash)(body, specPath));
|
|
154
|
+
outputNames.push(targetPath);
|
|
155
|
+
}
|
|
156
|
+
const linterCount = linterResults.filter((r) => r.exists).length;
|
|
157
|
+
console.log(`\n✓ ${specPath} → ${outputNames.join(", ")}`);
|
|
158
|
+
console.log(` ${String(Object.keys(spec.rules).length)} rules (${String(linterCount)} linter-verified)`);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
/** Compile a declarative SkillSpec → SKILL.md. */
|
|
162
|
+
function compileSkillToFile(spec, specPath) {
|
|
163
|
+
const outputPath = specPath.replace(/\.spec\.ts$/, "");
|
|
164
|
+
const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
|
|
165
|
+
basePath: process.cwd(),
|
|
166
|
+
specFile: specPath,
|
|
167
|
+
});
|
|
168
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
169
|
+
if (errors.length === 0) {
|
|
170
|
+
console.log(`\n✓ ${specPath} → ${outputPath}`);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
|
|
174
|
+
printErrors(specPath, errors);
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
105
177
|
async function compile(specPaths, config) {
|
|
106
178
|
let allValid = true;
|
|
107
179
|
for (const specPath of specPaths) {
|
|
180
|
+
// Generator skills can't be executed to markdown — compile from source.
|
|
181
|
+
const source = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(process.cwd(), specPath), "utf-8");
|
|
182
|
+
if (/\bgenSkill\s*\(/.test(source)) {
|
|
183
|
+
if (!compileGeneratorSkillToFile(specPath, source))
|
|
184
|
+
allValid = false;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
108
187
|
const spec = await loadSpec(specPath);
|
|
109
188
|
if (!spec) {
|
|
110
189
|
console.log(`\n✗ ${specPath} — failed to load`);
|
|
@@ -112,62 +191,13 @@ async function compile(specPaths, config) {
|
|
|
112
191
|
allValid = false;
|
|
113
192
|
continue;
|
|
114
193
|
}
|
|
115
|
-
const basePath = process.cwd();
|
|
116
194
|
if (spec._specType === "claude") {
|
|
117
|
-
|
|
118
|
-
basePath,
|
|
119
|
-
specFile: specPath,
|
|
120
|
-
maxRules: config.maxRules,
|
|
121
|
-
maxTokens: config.maxTokens,
|
|
122
|
-
maxSectionLines: config.maxSectionLines,
|
|
123
|
-
catalogOnly: config.catalogOnly,
|
|
124
|
-
linters: config.linters,
|
|
125
|
-
});
|
|
126
|
-
const linterCount = linterResults.filter((r) => r.exists).length;
|
|
127
|
-
const primaryOutput = specPath.replace(/\.spec\.ts$/, "");
|
|
128
|
-
if (errors.length === 0) {
|
|
129
|
-
// Write primary target
|
|
130
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
|
|
131
|
-
const outputNames = [primaryOutput];
|
|
132
|
-
// Write additional targets with swapped heading + recomputed hash
|
|
133
|
-
for (const t of targets.slice(1)) {
|
|
134
|
-
// Strip hash, replace heading, recompute hash
|
|
135
|
-
const body = markdown
|
|
136
|
-
.replace(/^<!-- vigiles:[^\n]+\n\n?/, "")
|
|
137
|
-
.replace(/^# [^\n]+/, `# ${t}`);
|
|
138
|
-
const additional = (0, compile_js_1.addHash)(body, specPath);
|
|
139
|
-
const dir = primaryOutput.substring(0, primaryOutput.lastIndexOf("/") + 1);
|
|
140
|
-
const targetPath = dir + t;
|
|
141
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, targetPath), additional);
|
|
142
|
-
outputNames.push(targetPath);
|
|
143
|
-
}
|
|
144
|
-
console.log(`\n✓ ${specPath} → ${outputNames.join(", ")}`);
|
|
145
|
-
console.log(` ${String(Object.keys(spec.rules).length)} rules (${String(linterCount)} linter-verified)`);
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
|
|
149
|
-
printErrors(specPath, errors);
|
|
195
|
+
if (!compileClaudeToFile(spec, specPath, config))
|
|
150
196
|
allValid = false;
|
|
151
|
-
// Still write the file so the user can see partial output
|
|
152
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
|
|
153
|
-
}
|
|
154
197
|
}
|
|
155
198
|
else if (spec._specType === "skill") {
|
|
156
|
-
|
|
157
|
-
const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
|
|
158
|
-
basePath,
|
|
159
|
-
specFile: specPath,
|
|
160
|
-
});
|
|
161
|
-
if (errors.length === 0) {
|
|
162
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, outputPath), markdown);
|
|
163
|
-
console.log(`\n✓ ${specPath} → ${outputPath}`);
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
|
|
167
|
-
printErrors(specPath, errors);
|
|
199
|
+
if (!compileSkillToFile(spec, specPath))
|
|
168
200
|
allValid = false;
|
|
169
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, outputPath), markdown);
|
|
170
|
-
}
|
|
171
201
|
}
|
|
172
202
|
}
|
|
173
203
|
return allValid;
|
|
@@ -363,6 +393,44 @@ async function findDuplicateRules(threshold = 0.3, silent = false, scopeFiles) {
|
|
|
363
393
|
log(`\n ${String(totalPairs)} duplicate pair(s) in ${String(specsWithDuplicates)} spec(s). Consider merging or rewording.`);
|
|
364
394
|
return { valid: false, pairCount: totalPairs };
|
|
365
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Verify the file-qualified symbol references (`path.ext#symbol`) in instruction
|
|
398
|
+
* files: the named file must exist and define the named symbol. The author
|
|
399
|
+
* names the file, so this is a *declared* reference — a broken one is an error.
|
|
400
|
+
* Each named file is parsed on demand; there is no project-wide index. Returns
|
|
401
|
+
* the count of broken references.
|
|
402
|
+
*/
|
|
403
|
+
function verifyMarkdownSymbols(files, silent) {
|
|
404
|
+
if (files.length === 0)
|
|
405
|
+
return 0;
|
|
406
|
+
const cwd = process.cwd();
|
|
407
|
+
let printedHeader = false;
|
|
408
|
+
let errors = 0;
|
|
409
|
+
for (const f of files) {
|
|
410
|
+
let markdown;
|
|
411
|
+
try {
|
|
412
|
+
markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, f), "utf-8");
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const broken = (0, refs_js_1.verifySymbolRefs)(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, f)));
|
|
418
|
+
if (broken.length === 0)
|
|
419
|
+
continue;
|
|
420
|
+
if (!silent) {
|
|
421
|
+
if (!printedHeader) {
|
|
422
|
+
console.log("\nSymbol reference check:\n");
|
|
423
|
+
printedHeader = true;
|
|
424
|
+
}
|
|
425
|
+
for (const b of broken) {
|
|
426
|
+
console.log(` ✗ ${f}:${String(b.line)} ${b.reason}`);
|
|
427
|
+
ghAnnotate("error", b.reason, f, b.line);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
errors += broken.length;
|
|
431
|
+
}
|
|
432
|
+
return errors;
|
|
433
|
+
}
|
|
366
434
|
/** Exit codes: 0 clean, 1 warnings only, 2 hard errors. */
|
|
367
435
|
function auditExitCode(report) {
|
|
368
436
|
if (report.hashErrors > 0 ||
|
|
@@ -370,7 +438,8 @@ function auditExitCode(report) {
|
|
|
370
438
|
report.inlineErrors > 0 ||
|
|
371
439
|
report.frontmatterErrors > 0 ||
|
|
372
440
|
report.integrityErrors > 0 ||
|
|
373
|
-
report.coverageErrors > 0
|
|
441
|
+
report.coverageErrors > 0 ||
|
|
442
|
+
report.symbolRefErrors > 0)
|
|
374
443
|
return 2;
|
|
375
444
|
if (report.duplicatePairs > 0 ||
|
|
376
445
|
report.orphanCount > 0 ||
|
|
@@ -406,6 +475,35 @@ function verifyOneRule(rule, filePath, silent, linterOptions) {
|
|
|
406
475
|
log(` ✓ line ${String(rule.line)}: ${rule.linterRule}`);
|
|
407
476
|
return true;
|
|
408
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Verify the `vigiles:file` / `vigiles:cmd` references a markdown file declares
|
|
480
|
+
* (inline comments or frontmatter lists), using the same engine spec mode uses:
|
|
481
|
+
* file paths via existsSync, npm scripts and script-runner commands via
|
|
482
|
+
* package.json / the filesystem. References resolve relative to the markdown
|
|
483
|
+
* file's own directory. Returns the number of stale references found.
|
|
484
|
+
*/
|
|
485
|
+
function verifyMarkdownRefs(files, commands, filePath, silent) {
|
|
486
|
+
const basePath = (0, node_path_1.dirname)((0, node_path_1.resolve)(process.cwd(), filePath));
|
|
487
|
+
let errorCount = 0;
|
|
488
|
+
const report = (err, line) => {
|
|
489
|
+
if (!silent) {
|
|
490
|
+
console.log(` ✗ line ${String(line)}: ${err.message}`);
|
|
491
|
+
ghAnnotate("error", err.message, filePath, line);
|
|
492
|
+
}
|
|
493
|
+
errorCount++;
|
|
494
|
+
};
|
|
495
|
+
for (const f of files) {
|
|
496
|
+
const err = (0, compile_js_1.validateFileRef)(f.path, basePath);
|
|
497
|
+
if (err)
|
|
498
|
+
report(err, f.line);
|
|
499
|
+
}
|
|
500
|
+
for (const c of commands) {
|
|
501
|
+
const err = (0, compile_js_1.validateCommandRef)(c.command, basePath);
|
|
502
|
+
if (err)
|
|
503
|
+
report(err, c.line);
|
|
504
|
+
}
|
|
505
|
+
return errorCount;
|
|
506
|
+
}
|
|
409
507
|
function verifyInlineRules(filePath, silent, linterOptions) {
|
|
410
508
|
const log = (msg) => {
|
|
411
509
|
if (!silent)
|
|
@@ -418,8 +516,11 @@ function verifyInlineRules(filePath, silent, linterOptions) {
|
|
|
418
516
|
catch {
|
|
419
517
|
return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
|
|
420
518
|
}
|
|
421
|
-
const { rules, errors: parseErrors } = (0, inline_js_1.parseInlineRules)(content);
|
|
422
|
-
if (rules.length === 0 &&
|
|
519
|
+
const { rules, files, commands, errors: parseErrors, } = (0, inline_js_1.parseInlineRules)(content);
|
|
520
|
+
if (rules.length === 0 &&
|
|
521
|
+
files.length === 0 &&
|
|
522
|
+
commands.length === 0 &&
|
|
523
|
+
parseErrors.length === 0) {
|
|
423
524
|
return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
|
|
424
525
|
}
|
|
425
526
|
let errorCount = 0;
|
|
@@ -435,10 +536,11 @@ function verifyInlineRules(filePath, silent, linterOptions) {
|
|
|
435
536
|
if (!verifyOneRule(rule, filePath, silent, linterOptions))
|
|
436
537
|
errorCount++;
|
|
437
538
|
}
|
|
539
|
+
errorCount += verifyMarkdownRefs(files, commands, filePath, silent);
|
|
438
540
|
return {
|
|
439
541
|
ok: errorCount === 0,
|
|
440
542
|
errorCount,
|
|
441
|
-
ruleCount: rules.length,
|
|
543
|
+
ruleCount: rules.length + files.length + commands.length,
|
|
442
544
|
ruleNames: rules.map((r) => r.linterRule),
|
|
443
545
|
};
|
|
444
546
|
}
|
|
@@ -460,9 +562,12 @@ function verifyFrontmatterRules(filePath, silent, exclude, linterOptions) {
|
|
|
460
562
|
catch {
|
|
461
563
|
return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
|
|
462
564
|
}
|
|
463
|
-
const { rules: allRules, errors: parseErrors } = (0, frontmatter_js_1.parseFrontmatterRules)(content);
|
|
565
|
+
const { rules: allRules, files, commands, errors: parseErrors, } = (0, frontmatter_js_1.parseFrontmatterRules)(content);
|
|
464
566
|
const rules = allRules.filter((r) => !exclude.has(r.linterRule));
|
|
465
|
-
if (rules.length === 0 &&
|
|
567
|
+
if (rules.length === 0 &&
|
|
568
|
+
files.length === 0 &&
|
|
569
|
+
commands.length === 0 &&
|
|
570
|
+
parseErrors.length === 0) {
|
|
466
571
|
return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
|
|
467
572
|
}
|
|
468
573
|
let errorCount = 0;
|
|
@@ -478,10 +583,11 @@ function verifyFrontmatterRules(filePath, silent, exclude, linterOptions) {
|
|
|
478
583
|
if (!verifyOneRule(rule, filePath, silent, linterOptions))
|
|
479
584
|
errorCount++;
|
|
480
585
|
}
|
|
586
|
+
errorCount += verifyMarkdownRefs(files, commands, filePath, silent);
|
|
481
587
|
return {
|
|
482
588
|
ok: errorCount === 0,
|
|
483
589
|
errorCount,
|
|
484
|
-
ruleCount: rules.length,
|
|
590
|
+
ruleCount: rules.length + files.length + commands.length,
|
|
485
591
|
ruleNames: rules.map((r) => r.linterRule),
|
|
486
592
|
};
|
|
487
593
|
}
|
|
@@ -620,6 +726,8 @@ async function audit(restArgs, flags, config) {
|
|
|
620
726
|
console.log(` ${line}`);
|
|
621
727
|
}
|
|
622
728
|
}
|
|
729
|
+
// 9. Verify code-shaped symbol references live (see src/refs.ts).
|
|
730
|
+
const symbolRefErrors = verifyMarkdownSymbols(files, silent);
|
|
623
731
|
const report = {
|
|
624
732
|
hashErrors: hashResult.hashErrors,
|
|
625
733
|
validationErrors: hashResult.validationErrors,
|
|
@@ -635,6 +743,7 @@ async function audit(restArgs, flags, config) {
|
|
|
635
743
|
coverageErrors,
|
|
636
744
|
orphanCount: orphanReport.orphans.length,
|
|
637
745
|
docRefErrors: docRefReport.errors.length,
|
|
746
|
+
symbolRefErrors,
|
|
638
747
|
files,
|
|
639
748
|
};
|
|
640
749
|
if (summary) {
|
|
@@ -662,6 +771,8 @@ function printAuditSummary(report) {
|
|
|
662
771
|
parts.push(`${String(report.orphanCount)} orphan docs`);
|
|
663
772
|
if (report.docRefErrors > 0)
|
|
664
773
|
parts.push(`${String(report.docRefErrors)} broken doc refs`);
|
|
774
|
+
if (report.symbolRefErrors > 0)
|
|
775
|
+
parts.push(`${String(report.symbolRefErrors)} broken symbol refs`);
|
|
665
776
|
const undocumented = report.coverageEnabled - report.coverageDocumented;
|
|
666
777
|
if (undocumented > 0)
|
|
667
778
|
parts.push(`${String(undocumented)} undocumented rules`);
|
|
@@ -1328,6 +1439,242 @@ function printUsage(command) {
|
|
|
1328
1439
|
// ---------------------------------------------------------------------------
|
|
1329
1440
|
// Main
|
|
1330
1441
|
// ---------------------------------------------------------------------------
|
|
1442
|
+
/**
|
|
1443
|
+
* Emit GitHub Actions annotations for an audit report. Skipped when --json or
|
|
1444
|
+
* --summary is active — those modes promise clean machine-readable stdout, and
|
|
1445
|
+
* ::error/::warning lines would contaminate output parsed as JSON.
|
|
1446
|
+
*/
|
|
1447
|
+
function annotateAuditForGitHub(report, flags) {
|
|
1448
|
+
const structuredOutput = flags.includes("--json") || flags.includes("--summary");
|
|
1449
|
+
if (!isGitHubActions() || structuredOutput)
|
|
1450
|
+
return;
|
|
1451
|
+
if (report.hashErrors > 0) {
|
|
1452
|
+
ghAnnotate("error", `${String(report.hashErrors)} compiled file(s) with stale hash — run vigiles compile`);
|
|
1453
|
+
}
|
|
1454
|
+
if (report.validationErrors > 0) {
|
|
1455
|
+
ghAnnotate("error", `${String(report.validationErrors)} spec validation failure(s) — see audit output`);
|
|
1456
|
+
}
|
|
1457
|
+
if (report.duplicatePairs > 0) {
|
|
1458
|
+
ghAnnotate("warning", `${String(report.duplicatePairs)} near-duplicate rule pair(s) detected — consider merging`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Run a compiled skill's deterministic gate ladder: execute each step gate in
|
|
1463
|
+
* order (short-circuiting on the first failure), then the result gate. This is
|
|
1464
|
+
* the v0 runtime — it enforces the `vigiles:gate`/`vigiles:result` markers a
|
|
1465
|
+
* compiled SKILL.md carries. It does not yet drive the model through the prose
|
|
1466
|
+
* steps (that needs a live harness).
|
|
1467
|
+
*/
|
|
1468
|
+
function runSkillCommand(target) {
|
|
1469
|
+
if (!target) {
|
|
1470
|
+
console.error("Usage: vigiles run-skill <SKILL.md>");
|
|
1471
|
+
process.exit(2);
|
|
1472
|
+
}
|
|
1473
|
+
const path = (0, node_path_1.resolve)(process.cwd(), target);
|
|
1474
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
1475
|
+
console.error(`Not found: ${target}`);
|
|
1476
|
+
process.exit(2);
|
|
1477
|
+
}
|
|
1478
|
+
const gates = (0, skill_runtime_js_1.parseSkillGates)((0, node_fs_1.readFileSync)(path, "utf-8"));
|
|
1479
|
+
if (gates.steps.length === 0 && !gates.result) {
|
|
1480
|
+
console.log(`No vigiles:gate / vigiles:result markers in ${target}.`);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
console.log(`Running gate ladder for ${target}:\n`);
|
|
1484
|
+
const report = (0, skill_runtime_js_1.runSkillGates)(gates, process.cwd());
|
|
1485
|
+
for (const r of report.results) {
|
|
1486
|
+
const label = r.at === "result" ? "result" : `step ${String(r.at)}`;
|
|
1487
|
+
console.log(` ${r.ok ? "✓" : "✗"} ${label} — ${(0, skill_runtime_js_1.gateLabel)(r.gate)}`);
|
|
1488
|
+
if (!r.ok && r.output) {
|
|
1489
|
+
console.log(r.output
|
|
1490
|
+
.split("\n")
|
|
1491
|
+
.map((l) => ` ${l}`)
|
|
1492
|
+
.join("\n"));
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (report.ok) {
|
|
1496
|
+
console.log("\n✓ All gates passed.");
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
const where = report.blockedAt === "result"
|
|
1500
|
+
? "the result gate"
|
|
1501
|
+
: `step ${String(report.blockedAt)}`;
|
|
1502
|
+
console.log(`\n✗ Blocked at ${where} — fix it before the skill is done.`);
|
|
1503
|
+
process.exit(2);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Stop-hook entrypoint: run the active skill's result gate and decide whether
|
|
1508
|
+
* the agent may stop. Exit 2 (with the reason on stderr) blocks the stop and
|
|
1509
|
+
* feeds the message back to the model; exit 0 allows it and clears the marker.
|
|
1510
|
+
*/
|
|
1511
|
+
function skillHookCommand() {
|
|
1512
|
+
const decision = (0, skill_runtime_js_1.evaluateStopHook)(process.cwd());
|
|
1513
|
+
if (decision.allow) {
|
|
1514
|
+
if (decision.message)
|
|
1515
|
+
console.log(decision.message);
|
|
1516
|
+
(0, skill_runtime_js_1.clearActiveSkill)(process.cwd());
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
console.error(decision.message);
|
|
1520
|
+
process.exit(2);
|
|
1521
|
+
}
|
|
1522
|
+
/** Mark a skill active so the Stop hook enforces its result gate. */
|
|
1523
|
+
function skillStartCommand(target) {
|
|
1524
|
+
if (!target) {
|
|
1525
|
+
console.error("Usage: vigiles skill-start <SKILL.md>");
|
|
1526
|
+
process.exit(2);
|
|
1527
|
+
}
|
|
1528
|
+
(0, skill_runtime_js_1.setActiveSkill)(process.cwd(), target);
|
|
1529
|
+
console.log(`Active skill: ${target}`);
|
|
1530
|
+
}
|
|
1531
|
+
/** Dispatch the skill-runtime subcommands. Returns false if unrecognized. */
|
|
1532
|
+
function handleSkillCommand(command, restArgs) {
|
|
1533
|
+
switch (command) {
|
|
1534
|
+
case "run-skill":
|
|
1535
|
+
runSkillCommand(restArgs[0]);
|
|
1536
|
+
return true;
|
|
1537
|
+
case "skill-start":
|
|
1538
|
+
skillStartCommand(restArgs[0]);
|
|
1539
|
+
return true;
|
|
1540
|
+
case "skill-done":
|
|
1541
|
+
(0, skill_runtime_js_1.clearActiveSkill)(process.cwd());
|
|
1542
|
+
return true;
|
|
1543
|
+
case "skill-hook":
|
|
1544
|
+
skillHookCommand();
|
|
1545
|
+
return true;
|
|
1546
|
+
case "action-hook":
|
|
1547
|
+
actionHookCommand();
|
|
1548
|
+
return true;
|
|
1549
|
+
case "refs":
|
|
1550
|
+
refsCommand(restArgs[0]);
|
|
1551
|
+
return true;
|
|
1552
|
+
case "refs-hook":
|
|
1553
|
+
refsHookCommand();
|
|
1554
|
+
return true;
|
|
1555
|
+
default:
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* PostToolUse-hook entrypoint for action gates. Reads the tool event on stdin,
|
|
1561
|
+
* runs the matching action gates from `.vigiles/action-gates.json`, and blocks
|
|
1562
|
+
* (exit 2 + reason on stderr) if any fails — plan-agnostic, so it works inside
|
|
1563
|
+
* dynamic workflows where there is no static step to attach a gate to.
|
|
1564
|
+
*/
|
|
1565
|
+
function actionHookCommand() {
|
|
1566
|
+
let raw = "";
|
|
1567
|
+
try {
|
|
1568
|
+
raw = (0, node_fs_1.readFileSync)(0, "utf-8");
|
|
1569
|
+
}
|
|
1570
|
+
catch {
|
|
1571
|
+
/* no stdin */
|
|
1572
|
+
}
|
|
1573
|
+
let event = { tool: "" };
|
|
1574
|
+
try {
|
|
1575
|
+
const j = JSON.parse(raw);
|
|
1576
|
+
event = { tool: j.tool_name ?? "", input: j.tool_input };
|
|
1577
|
+
}
|
|
1578
|
+
catch {
|
|
1579
|
+
/* malformed input → no event, allow */
|
|
1580
|
+
}
|
|
1581
|
+
const decision = (0, action_gate_js_1.evaluateAction)(event, (0, action_gate_js_1.loadActionGates)(process.cwd()), process.cwd());
|
|
1582
|
+
if (!decision.allow) {
|
|
1583
|
+
console.error(decision.message);
|
|
1584
|
+
process.exit(2);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const INSTRUCTION_FILE = /^(SKILL|CLAUDE|AGENTS)\.md$/;
|
|
1588
|
+
function isInstructionFile(file) {
|
|
1589
|
+
return INSTRUCTION_FILE.test((0, node_path_1.basename)(file));
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Inspect an instruction file's symbol references: broken file-qualified refs
|
|
1593
|
+
* (`path.ext#symbol` whose file/symbol is wrong) and code-shaped references not
|
|
1594
|
+
* yet marked. Emits one line per finding via `log`; returns whether any issue
|
|
1595
|
+
* was found. `basePath` is the file's own directory (where paths resolve).
|
|
1596
|
+
*/
|
|
1597
|
+
function reportRefIssues(markdown, basePath, log) {
|
|
1598
|
+
const broken = (0, refs_js_1.verifySymbolRefs)(markdown, basePath);
|
|
1599
|
+
const unmarked = (0, refs_js_1.unmarkedCodeRefs)(markdown);
|
|
1600
|
+
for (const b of broken) {
|
|
1601
|
+
log(` ✗ line ${String(b.line)}: ${b.reason}`);
|
|
1602
|
+
}
|
|
1603
|
+
for (const u of unmarked) {
|
|
1604
|
+
const callee = u.text.replace(/\s*\([^)]*\)\s*$/, "");
|
|
1605
|
+
log(` ✗ line ${String(u.line)}: \`${u.text}\` is an unmarked code reference — ` +
|
|
1606
|
+
`mark it as \`vigiles:symbol path/to/file.ext#${callee}\` or add <!-- vigiles:ignore --> if it is prose`);
|
|
1607
|
+
}
|
|
1608
|
+
return broken.length > 0 || unmarked.length > 0;
|
|
1609
|
+
}
|
|
1610
|
+
/** `vigiles refs <file>` — check a file's symbol references (exit 2 on issues). */
|
|
1611
|
+
function refsCommand(target) {
|
|
1612
|
+
if (!target) {
|
|
1613
|
+
console.error("Usage: vigiles refs <instruction-file.md>");
|
|
1614
|
+
process.exit(2);
|
|
1615
|
+
}
|
|
1616
|
+
const cwd = process.cwd();
|
|
1617
|
+
let markdown;
|
|
1618
|
+
try {
|
|
1619
|
+
markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, target), "utf-8");
|
|
1620
|
+
}
|
|
1621
|
+
catch {
|
|
1622
|
+
console.error(`Cannot read ${target}`);
|
|
1623
|
+
process.exit(2);
|
|
1624
|
+
}
|
|
1625
|
+
const bad = reportRefIssues(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, target)), (m) => {
|
|
1626
|
+
console.log(m);
|
|
1627
|
+
});
|
|
1628
|
+
if (bad)
|
|
1629
|
+
process.exit(2);
|
|
1630
|
+
console.log(`✓ ${target}: all code references are marked and resolve.`);
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* PostToolUse-hook entrypoint: when the agent edits an instruction file, force
|
|
1634
|
+
* every code reference to carry a file-qualified mark (`path.ext#symbol`) and
|
|
1635
|
+
* verify the marked ones against the named file. Exit 2 (reason on stderr)
|
|
1636
|
+
* blocks the edit and feeds the fix back to the agent — the harness makes the
|
|
1637
|
+
* agent mark its references, at write time, with full context. `vigiles:ignore`
|
|
1638
|
+
* opts a prose span out.
|
|
1639
|
+
*/
|
|
1640
|
+
function refsHookCommand() {
|
|
1641
|
+
let raw = "";
|
|
1642
|
+
try {
|
|
1643
|
+
raw = (0, node_fs_1.readFileSync)(0, "utf-8");
|
|
1644
|
+
}
|
|
1645
|
+
catch {
|
|
1646
|
+
/* no stdin */
|
|
1647
|
+
}
|
|
1648
|
+
let file = "";
|
|
1649
|
+
try {
|
|
1650
|
+
const j = JSON.parse(raw);
|
|
1651
|
+
file = j.tool_input?.file_path ?? "";
|
|
1652
|
+
}
|
|
1653
|
+
catch {
|
|
1654
|
+
/* malformed → nothing to do */
|
|
1655
|
+
}
|
|
1656
|
+
if (!file || !isInstructionFile(file))
|
|
1657
|
+
return;
|
|
1658
|
+
const cwd = process.cwd();
|
|
1659
|
+
const target = (0, node_path_1.relative)(cwd, (0, node_path_1.resolve)(cwd, file)) || file;
|
|
1660
|
+
let markdown;
|
|
1661
|
+
try {
|
|
1662
|
+
markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, file), "utf-8");
|
|
1663
|
+
}
|
|
1664
|
+
catch {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const lines = [];
|
|
1668
|
+
const bad = reportRefIssues(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, file)), (m) => {
|
|
1669
|
+
lines.push(m);
|
|
1670
|
+
});
|
|
1671
|
+
if (bad) {
|
|
1672
|
+
console.error(`vigiles: fix the code references in ${target}:`);
|
|
1673
|
+
for (const l of lines)
|
|
1674
|
+
console.error(l);
|
|
1675
|
+
process.exit(2);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1331
1678
|
async function main() {
|
|
1332
1679
|
const args = process.argv.slice(2);
|
|
1333
1680
|
const command = args[0];
|
|
@@ -1371,23 +1718,8 @@ async function main() {
|
|
|
1371
1718
|
// audit = verify + discover + guidance count
|
|
1372
1719
|
const flags = args.slice(1).filter((a) => a.startsWith("--"));
|
|
1373
1720
|
const report = await audit(restArgs, flags, config);
|
|
1721
|
+
annotateAuditForGitHub(report, flags);
|
|
1374
1722
|
const exitCode = auditExitCode(report);
|
|
1375
|
-
// Skip GH annotations when --json or --summary is active —
|
|
1376
|
-
// those modes promise clean machine-readable stdout, and
|
|
1377
|
-
// ::error/::warning lines would contaminate the output for
|
|
1378
|
-
// callers parsing it as JSON.
|
|
1379
|
-
const structuredOutput = flags.includes("--json") || flags.includes("--summary");
|
|
1380
|
-
if (isGitHubActions() && !structuredOutput) {
|
|
1381
|
-
if (report.hashErrors > 0) {
|
|
1382
|
-
ghAnnotate("error", `${String(report.hashErrors)} compiled file(s) with stale hash — run vigiles compile`);
|
|
1383
|
-
}
|
|
1384
|
-
if (report.validationErrors > 0) {
|
|
1385
|
-
ghAnnotate("error", `${String(report.validationErrors)} spec validation failure(s) — see audit output`);
|
|
1386
|
-
}
|
|
1387
|
-
if (report.duplicatePairs > 0) {
|
|
1388
|
-
ghAnnotate("warning", `${String(report.duplicatePairs)} near-duplicate rule pair(s) detected — consider merging`);
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
1723
|
if (exitCode !== 0) {
|
|
1392
1724
|
process.exit(exitCode);
|
|
1393
1725
|
}
|
|
@@ -1401,7 +1733,8 @@ async function main() {
|
|
|
1401
1733
|
handleGenerateSchema(args, restArgs);
|
|
1402
1734
|
break;
|
|
1403
1735
|
default:
|
|
1404
|
-
|
|
1736
|
+
if (!handleSkillCommand(command, restArgs))
|
|
1737
|
+
printUsage(command);
|
|
1405
1738
|
break;
|
|
1406
1739
|
}
|
|
1407
1740
|
}
|
|
@@ -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
|