vigiles 2.1.1 → 2.3.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 (49) hide show
  1. package/README.md +127 -5
  2. package/dist/action-gate.d.ts +28 -0
  3. package/dist/action-gate.js +73 -0
  4. package/dist/cli.js +450 -75
  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 +3 -0
  10. package/dist/compile.js +217 -26
  11. package/dist/eval.d.ts +87 -0
  12. package/dist/eval.js +208 -0
  13. package/dist/frontmatter.d.ts +24 -6
  14. package/dist/frontmatter.js +103 -30
  15. package/dist/generate-schema.js +10 -0
  16. package/dist/harness-assert.d.ts +68 -0
  17. package/dist/harness-assert.js +127 -0
  18. package/dist/harness-test.d.ts +45 -0
  19. package/dist/harness-test.js +138 -0
  20. package/dist/inline.d.ts +22 -4
  21. package/dist/inline.js +60 -13
  22. package/dist/jest.d.ts +9 -0
  23. package/dist/jest.js +23 -0
  24. package/dist/judge.d.ts +29 -0
  25. package/dist/judge.js +88 -0
  26. package/dist/linters.js +28 -0
  27. package/dist/mock-model.d.ts +31 -0
  28. package/dist/mock-model.js +189 -0
  29. package/dist/plugin-loader.d.ts +37 -0
  30. package/dist/plugin-loader.js +195 -0
  31. package/dist/refs.d.ts +44 -0
  32. package/dist/refs.js +144 -0
  33. package/dist/run-hook.d.ts +77 -0
  34. package/dist/run-hook.js +80 -0
  35. package/dist/run-scripts.d.ts +20 -0
  36. package/dist/run-scripts.js +70 -0
  37. package/dist/skill-driver.d.ts +77 -0
  38. package/dist/skill-driver.js +76 -0
  39. package/dist/skill-runtime.d.ts +101 -0
  40. package/dist/skill-runtime.js +289 -0
  41. package/dist/skill-test.d.ts +47 -0
  42. package/dist/skill-test.js +77 -0
  43. package/dist/spec.d.ts +90 -4
  44. package/dist/spec.js +29 -0
  45. package/dist/symbols.d.ts +30 -0
  46. package/dist/symbols.js +142 -0
  47. package/dist/vitest.d.mts +9 -0
  48. package/dist/vitest.mjs +22 -0
  49. package/package.json +45 -6
package/dist/cli.js CHANGED
@@ -21,7 +21,13 @@ 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");
29
+ const harness_test_js_1 = require("./harness-test.js");
30
+ const run_scripts_js_1 = require("./run-scripts.js");
25
31
  const integrity_js_1 = require("./integrity.js");
26
32
  const coverage_js_1 = require("./coverage.js");
27
33
  const orphans_js_1 = require("./orphans.js");
@@ -102,9 +108,84 @@ function printErrors(specFile, errors) {
102
108
  // ---------------------------------------------------------------------------
103
109
  // Commands
104
110
  // ---------------------------------------------------------------------------
111
+ /** Compile a generator-skill spec from source → SKILL.md. Returns validity. */
112
+ function compileGeneratorSkillToFile(specPath, source) {
113
+ const outputPath = specPath.replace(/\.spec\.ts$/, "");
114
+ const { markdown, errors } = (0, compile_generator_js_1.compileGeneratorSkill)(source, {
115
+ basePath: process.cwd(),
116
+ specFile: specPath,
117
+ });
118
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
119
+ if (errors.length === 0) {
120
+ console.log(`\n✓ ${specPath} → ${outputPath} (generator skill)`);
121
+ return true;
122
+ }
123
+ console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
124
+ for (const e of errors)
125
+ console.log(` ${e.type}: ${e.message}`);
126
+ return false;
127
+ }
128
+ /** Compile a ClaudeSpec → its primary + any additional targets. */
129
+ function compileClaudeToFile(spec, specPath, config) {
130
+ const basePath = process.cwd();
131
+ const { markdown, errors, linterResults, targets } = (0, compile_js_1.compileClaude)(spec, {
132
+ basePath,
133
+ specFile: specPath,
134
+ maxRules: config.maxRules,
135
+ maxTokens: config.maxTokens,
136
+ maxSectionLines: config.maxSectionLines,
137
+ catalogOnly: config.catalogOnly,
138
+ linters: config.linters,
139
+ });
140
+ const primaryOutput = specPath.replace(/\.spec\.ts$/, "");
141
+ if (errors.length > 0) {
142
+ console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
143
+ printErrors(specPath, errors);
144
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
145
+ return false;
146
+ }
147
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, primaryOutput), markdown);
148
+ const outputNames = [primaryOutput];
149
+ for (const t of targets.slice(1)) {
150
+ const body = markdown
151
+ .replace(/^<!-- vigiles:[^\n]+\n\n?/, "")
152
+ .replace(/^# [^\n]+/, `# ${t}`);
153
+ const dir = primaryOutput.substring(0, primaryOutput.lastIndexOf("/") + 1);
154
+ const targetPath = dir + t;
155
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, targetPath), (0, compile_js_1.addHash)(body, specPath));
156
+ outputNames.push(targetPath);
157
+ }
158
+ const linterCount = linterResults.filter((r) => r.exists).length;
159
+ console.log(`\n✓ ${specPath} → ${outputNames.join(", ")}`);
160
+ console.log(` ${String(Object.keys(spec.rules).length)} rules (${String(linterCount)} linter-verified)`);
161
+ return true;
162
+ }
163
+ /** Compile a declarative SkillSpec → SKILL.md. */
164
+ function compileSkillToFile(spec, specPath) {
165
+ const outputPath = specPath.replace(/\.spec\.ts$/, "");
166
+ const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
167
+ basePath: process.cwd(),
168
+ specFile: specPath,
169
+ });
170
+ (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
171
+ if (errors.length === 0) {
172
+ console.log(`\n✓ ${specPath} → ${outputPath}`);
173
+ return true;
174
+ }
175
+ console.log(`\n✗ ${specPath} — ${String(errors.length)} error(s)`);
176
+ printErrors(specPath, errors);
177
+ return false;
178
+ }
105
179
  async function compile(specPaths, config) {
106
180
  let allValid = true;
107
181
  for (const specPath of specPaths) {
182
+ // Generator skills can't be executed to markdown — compile from source.
183
+ const source = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(process.cwd(), specPath), "utf-8");
184
+ if (/\bgenSkill\s*\(/.test(source)) {
185
+ if (!compileGeneratorSkillToFile(specPath, source))
186
+ allValid = false;
187
+ continue;
188
+ }
108
189
  const spec = await loadSpec(specPath);
109
190
  if (!spec) {
110
191
  console.log(`\n✗ ${specPath} — failed to load`);
@@ -112,62 +193,13 @@ async function compile(specPaths, config) {
112
193
  allValid = false;
113
194
  continue;
114
195
  }
115
- const basePath = process.cwd();
116
196
  if (spec._specType === "claude") {
117
- const { markdown, errors, linterResults, targets } = (0, compile_js_1.compileClaude)(spec, {
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);
197
+ if (!compileClaudeToFile(spec, specPath, config))
150
198
  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
199
  }
155
200
  else if (spec._specType === "skill") {
156
- const outputPath = specPath.replace(/\.spec\.ts$/, "");
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);
201
+ if (!compileSkillToFile(spec, specPath))
168
202
  allValid = false;
169
- (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(basePath, outputPath), markdown);
170
- }
171
203
  }
172
204
  }
173
205
  return allValid;
@@ -363,6 +395,44 @@ async function findDuplicateRules(threshold = 0.3, silent = false, scopeFiles) {
363
395
  log(`\n ${String(totalPairs)} duplicate pair(s) in ${String(specsWithDuplicates)} spec(s). Consider merging or rewording.`);
364
396
  return { valid: false, pairCount: totalPairs };
365
397
  }
398
+ /**
399
+ * Verify the file-qualified symbol references (`path.ext#symbol`) in instruction
400
+ * files: the named file must exist and define the named symbol. The author
401
+ * names the file, so this is a *declared* reference — a broken one is an error.
402
+ * Each named file is parsed on demand; there is no project-wide index. Returns
403
+ * the count of broken references.
404
+ */
405
+ function verifyMarkdownSymbols(files, silent) {
406
+ if (files.length === 0)
407
+ return 0;
408
+ const cwd = process.cwd();
409
+ let printedHeader = false;
410
+ let errors = 0;
411
+ for (const f of files) {
412
+ let markdown;
413
+ try {
414
+ markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, f), "utf-8");
415
+ }
416
+ catch {
417
+ continue;
418
+ }
419
+ const broken = (0, refs_js_1.verifySymbolRefs)(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, f)));
420
+ if (broken.length === 0)
421
+ continue;
422
+ if (!silent) {
423
+ if (!printedHeader) {
424
+ console.log("\nSymbol reference check:\n");
425
+ printedHeader = true;
426
+ }
427
+ for (const b of broken) {
428
+ console.log(` ✗ ${f}:${String(b.line)} ${b.reason}`);
429
+ ghAnnotate("error", b.reason, f, b.line);
430
+ }
431
+ }
432
+ errors += broken.length;
433
+ }
434
+ return errors;
435
+ }
366
436
  /** Exit codes: 0 clean, 1 warnings only, 2 hard errors. */
367
437
  function auditExitCode(report) {
368
438
  if (report.hashErrors > 0 ||
@@ -370,7 +440,8 @@ function auditExitCode(report) {
370
440
  report.inlineErrors > 0 ||
371
441
  report.frontmatterErrors > 0 ||
372
442
  report.integrityErrors > 0 ||
373
- report.coverageErrors > 0)
443
+ report.coverageErrors > 0 ||
444
+ report.symbolRefErrors > 0)
374
445
  return 2;
375
446
  if (report.duplicatePairs > 0 ||
376
447
  report.orphanCount > 0 ||
@@ -406,6 +477,35 @@ function verifyOneRule(rule, filePath, silent, linterOptions) {
406
477
  log(` ✓ line ${String(rule.line)}: ${rule.linterRule}`);
407
478
  return true;
408
479
  }
480
+ /**
481
+ * Verify the `vigiles:file` / `vigiles:cmd` references a markdown file declares
482
+ * (inline comments or frontmatter lists), using the same engine spec mode uses:
483
+ * file paths via existsSync, npm scripts and script-runner commands via
484
+ * package.json / the filesystem. References resolve relative to the markdown
485
+ * file's own directory. Returns the number of stale references found.
486
+ */
487
+ function verifyMarkdownRefs(files, commands, filePath, silent) {
488
+ const basePath = (0, node_path_1.dirname)((0, node_path_1.resolve)(process.cwd(), filePath));
489
+ let errorCount = 0;
490
+ const report = (err, line) => {
491
+ if (!silent) {
492
+ console.log(` ✗ line ${String(line)}: ${err.message}`);
493
+ ghAnnotate("error", err.message, filePath, line);
494
+ }
495
+ errorCount++;
496
+ };
497
+ for (const f of files) {
498
+ const err = (0, compile_js_1.validateFileRef)(f.path, basePath);
499
+ if (err)
500
+ report(err, f.line);
501
+ }
502
+ for (const c of commands) {
503
+ const err = (0, compile_js_1.validateCommandRef)(c.command, basePath);
504
+ if (err)
505
+ report(err, c.line);
506
+ }
507
+ return errorCount;
508
+ }
409
509
  function verifyInlineRules(filePath, silent, linterOptions) {
410
510
  const log = (msg) => {
411
511
  if (!silent)
@@ -418,8 +518,11 @@ function verifyInlineRules(filePath, silent, linterOptions) {
418
518
  catch {
419
519
  return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
420
520
  }
421
- const { rules, errors: parseErrors } = (0, inline_js_1.parseInlineRules)(content);
422
- if (rules.length === 0 && parseErrors.length === 0) {
521
+ const { rules, files, commands, errors: parseErrors, } = (0, inline_js_1.parseInlineRules)(content);
522
+ if (rules.length === 0 &&
523
+ files.length === 0 &&
524
+ commands.length === 0 &&
525
+ parseErrors.length === 0) {
423
526
  return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
424
527
  }
425
528
  let errorCount = 0;
@@ -435,10 +538,11 @@ function verifyInlineRules(filePath, silent, linterOptions) {
435
538
  if (!verifyOneRule(rule, filePath, silent, linterOptions))
436
539
  errorCount++;
437
540
  }
541
+ errorCount += verifyMarkdownRefs(files, commands, filePath, silent);
438
542
  return {
439
543
  ok: errorCount === 0,
440
544
  errorCount,
441
- ruleCount: rules.length,
545
+ ruleCount: rules.length + files.length + commands.length,
442
546
  ruleNames: rules.map((r) => r.linterRule),
443
547
  };
444
548
  }
@@ -460,9 +564,12 @@ function verifyFrontmatterRules(filePath, silent, exclude, linterOptions) {
460
564
  catch {
461
565
  return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
462
566
  }
463
- const { rules: allRules, errors: parseErrors } = (0, frontmatter_js_1.parseFrontmatterRules)(content);
567
+ const { rules: allRules, files, commands, errors: parseErrors, } = (0, frontmatter_js_1.parseFrontmatterRules)(content);
464
568
  const rules = allRules.filter((r) => !exclude.has(r.linterRule));
465
- if (rules.length === 0 && parseErrors.length === 0) {
569
+ if (rules.length === 0 &&
570
+ files.length === 0 &&
571
+ commands.length === 0 &&
572
+ parseErrors.length === 0) {
466
573
  return { ok: true, errorCount: 0, ruleCount: 0, ruleNames: [] };
467
574
  }
468
575
  let errorCount = 0;
@@ -478,10 +585,11 @@ function verifyFrontmatterRules(filePath, silent, exclude, linterOptions) {
478
585
  if (!verifyOneRule(rule, filePath, silent, linterOptions))
479
586
  errorCount++;
480
587
  }
588
+ errorCount += verifyMarkdownRefs(files, commands, filePath, silent);
481
589
  return {
482
590
  ok: errorCount === 0,
483
591
  errorCount,
484
- ruleCount: rules.length,
592
+ ruleCount: rules.length + files.length + commands.length,
485
593
  ruleNames: rules.map((r) => r.linterRule),
486
594
  };
487
595
  }
@@ -620,6 +728,8 @@ async function audit(restArgs, flags, config) {
620
728
  console.log(` ${line}`);
621
729
  }
622
730
  }
731
+ // 9. Verify code-shaped symbol references live (see src/refs.ts).
732
+ const symbolRefErrors = verifyMarkdownSymbols(files, silent);
623
733
  const report = {
624
734
  hashErrors: hashResult.hashErrors,
625
735
  validationErrors: hashResult.validationErrors,
@@ -635,6 +745,7 @@ async function audit(restArgs, flags, config) {
635
745
  coverageErrors,
636
746
  orphanCount: orphanReport.orphans.length,
637
747
  docRefErrors: docRefReport.errors.length,
748
+ symbolRefErrors,
638
749
  files,
639
750
  };
640
751
  if (summary) {
@@ -662,6 +773,8 @@ function printAuditSummary(report) {
662
773
  parts.push(`${String(report.orphanCount)} orphan docs`);
663
774
  if (report.docRefErrors > 0)
664
775
  parts.push(`${String(report.docRefErrors)} broken doc refs`);
776
+ if (report.symbolRefErrors > 0)
777
+ parts.push(`${String(report.symbolRefErrors)} broken symbol refs`);
665
778
  const undocumented = report.coverageEnabled - report.coverageDocumented;
666
779
  if (undocumented > 0)
667
780
  parts.push(`${String(undocumented)} undocumented rules`);
@@ -1302,6 +1415,38 @@ function handleGenerateSchema(args, restArgs) {
1302
1415
  console.log(" Add to your markdown frontmatter:\n" +
1303
1416
  ` # yaml-language-server: $schema=./${outPath}`);
1304
1417
  }
1418
+ /**
1419
+ * `vigiles test` / `vigiles eval` — discover and run the two-tier harness
1420
+ * scripts (deterministic `*.harness.mjs` / real-model `*.eval.mjs`) as child
1421
+ * `node` processes, aggregating exit codes so they work as a CI command. See
1422
+ * src/run-scripts.ts.
1423
+ *
1424
+ * `vigiles test` skips clean when the `claude` CLI is absent (the deterministic
1425
+ * tier needs it, just like the node:test suite). `--trials=N` is forwarded to
1426
+ * eval scripts via the `VIGILES_TRIALS` env var.
1427
+ */
1428
+ function handleRunScripts(kind, args, restArgs) {
1429
+ const cwd = process.cwd();
1430
+ const defaultGlob = kind === "test" ? "**/*.harness.mjs" : "**/*.eval.mjs";
1431
+ if (kind === "test" && !(0, harness_test_js_1.claudeAvailable)()) {
1432
+ console.log("vigiles test: `claude` CLI not found — skipping harness tests.");
1433
+ return;
1434
+ }
1435
+ const files = (0, run_scripts_js_1.discoverScripts)(restArgs, defaultGlob, cwd);
1436
+ if (files.length === 0) {
1437
+ console.log(`No ${defaultGlob} files found.`);
1438
+ return;
1439
+ }
1440
+ const trialsFlag = args.find((a) => a.startsWith("--trials="));
1441
+ const env = {};
1442
+ if (trialsFlag)
1443
+ env.VIGILES_TRIALS = trialsFlag.split("=")[1];
1444
+ console.log(`Running ${String(files.length)} ${kind} file(s):\n`);
1445
+ const results = (0, run_scripts_js_1.runScripts)(files, cwd, env);
1446
+ console.log("\n" + (0, run_scripts_js_1.formatScriptSummary)(results));
1447
+ if (results.some((r) => r.code !== 0))
1448
+ process.exit(1);
1449
+ }
1305
1450
  function printUsage(command) {
1306
1451
  console.log("vigiles — compile typed specs to instruction files");
1307
1452
  console.log("");
@@ -1309,6 +1454,8 @@ function printUsage(command) {
1309
1454
  console.log(" vigiles init [flags] Setup project (--target=X.md, --strict, --no-gha)");
1310
1455
  console.log(" vigiles compile [files...] Compile .spec.ts → .md");
1311
1456
  console.log(" vigiles audit [files...] Verify, find gaps, suggest improvements");
1457
+ console.log(" vigiles test [files...] Run *.harness.mjs deterministic harness tests");
1458
+ console.log(" vigiles eval [files...] Run *.eval.mjs real-model harness evals (--trials=N)");
1312
1459
  console.log("");
1313
1460
  console.log("Examples:");
1314
1461
  console.log(" vigiles init Auto-detect project, create specs, wire CI");
@@ -1328,6 +1475,242 @@ function printUsage(command) {
1328
1475
  // ---------------------------------------------------------------------------
1329
1476
  // Main
1330
1477
  // ---------------------------------------------------------------------------
1478
+ /**
1479
+ * Emit GitHub Actions annotations for an audit report. Skipped when --json or
1480
+ * --summary is active — those modes promise clean machine-readable stdout, and
1481
+ * ::error/::warning lines would contaminate output parsed as JSON.
1482
+ */
1483
+ function annotateAuditForGitHub(report, flags) {
1484
+ const structuredOutput = flags.includes("--json") || flags.includes("--summary");
1485
+ if (!isGitHubActions() || structuredOutput)
1486
+ return;
1487
+ if (report.hashErrors > 0) {
1488
+ ghAnnotate("error", `${String(report.hashErrors)} compiled file(s) with stale hash — run vigiles compile`);
1489
+ }
1490
+ if (report.validationErrors > 0) {
1491
+ ghAnnotate("error", `${String(report.validationErrors)} spec validation failure(s) — see audit output`);
1492
+ }
1493
+ if (report.duplicatePairs > 0) {
1494
+ ghAnnotate("warning", `${String(report.duplicatePairs)} near-duplicate rule pair(s) detected — consider merging`);
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Run a compiled skill's deterministic gate ladder: execute each step gate in
1499
+ * order (short-circuiting on the first failure), then the result gate. This is
1500
+ * the v0 runtime — it enforces the `vigiles:gate`/`vigiles:result` markers a
1501
+ * compiled SKILL.md carries. It does not yet drive the model through the prose
1502
+ * steps (that needs a live harness).
1503
+ */
1504
+ function runSkillCommand(target) {
1505
+ if (!target) {
1506
+ console.error("Usage: vigiles run-skill <SKILL.md>");
1507
+ process.exit(2);
1508
+ }
1509
+ const path = (0, node_path_1.resolve)(process.cwd(), target);
1510
+ if (!(0, node_fs_1.existsSync)(path)) {
1511
+ console.error(`Not found: ${target}`);
1512
+ process.exit(2);
1513
+ }
1514
+ const gates = (0, skill_runtime_js_1.parseSkillGates)((0, node_fs_1.readFileSync)(path, "utf-8"));
1515
+ if (gates.steps.length === 0 && !gates.result) {
1516
+ console.log(`No vigiles:gate / vigiles:result markers in ${target}.`);
1517
+ return;
1518
+ }
1519
+ console.log(`Running gate ladder for ${target}:\n`);
1520
+ const report = (0, skill_runtime_js_1.runSkillGates)(gates, process.cwd());
1521
+ for (const r of report.results) {
1522
+ const label = r.at === "result" ? "result" : `step ${String(r.at)}`;
1523
+ console.log(` ${r.ok ? "✓" : "✗"} ${label} — ${(0, skill_runtime_js_1.gateLabel)(r.gate)}`);
1524
+ if (!r.ok && r.output) {
1525
+ console.log(r.output
1526
+ .split("\n")
1527
+ .map((l) => ` ${l}`)
1528
+ .join("\n"));
1529
+ }
1530
+ }
1531
+ if (report.ok) {
1532
+ console.log("\n✓ All gates passed.");
1533
+ }
1534
+ else {
1535
+ const where = report.blockedAt === "result"
1536
+ ? "the result gate"
1537
+ : `step ${String(report.blockedAt)}`;
1538
+ console.log(`\n✗ Blocked at ${where} — fix it before the skill is done.`);
1539
+ process.exit(2);
1540
+ }
1541
+ }
1542
+ /**
1543
+ * Stop-hook entrypoint: run the active skill's result gate and decide whether
1544
+ * the agent may stop. Exit 2 (with the reason on stderr) blocks the stop and
1545
+ * feeds the message back to the model; exit 0 allows it and clears the marker.
1546
+ */
1547
+ function skillHookCommand() {
1548
+ const decision = (0, skill_runtime_js_1.evaluateStopHook)(process.cwd());
1549
+ if (decision.allow) {
1550
+ if (decision.message)
1551
+ console.log(decision.message);
1552
+ (0, skill_runtime_js_1.clearActiveSkill)(process.cwd());
1553
+ return;
1554
+ }
1555
+ console.error(decision.message);
1556
+ process.exit(2);
1557
+ }
1558
+ /** Mark a skill active so the Stop hook enforces its result gate. */
1559
+ function skillStartCommand(target) {
1560
+ if (!target) {
1561
+ console.error("Usage: vigiles skill-start <SKILL.md>");
1562
+ process.exit(2);
1563
+ }
1564
+ (0, skill_runtime_js_1.setActiveSkill)(process.cwd(), target);
1565
+ console.log(`Active skill: ${target}`);
1566
+ }
1567
+ /** Dispatch the skill-runtime subcommands. Returns false if unrecognized. */
1568
+ function handleSkillCommand(command, restArgs) {
1569
+ switch (command) {
1570
+ case "run-skill":
1571
+ runSkillCommand(restArgs[0]);
1572
+ return true;
1573
+ case "skill-start":
1574
+ skillStartCommand(restArgs[0]);
1575
+ return true;
1576
+ case "skill-done":
1577
+ (0, skill_runtime_js_1.clearActiveSkill)(process.cwd());
1578
+ return true;
1579
+ case "skill-hook":
1580
+ skillHookCommand();
1581
+ return true;
1582
+ case "action-hook":
1583
+ actionHookCommand();
1584
+ return true;
1585
+ case "refs":
1586
+ refsCommand(restArgs[0]);
1587
+ return true;
1588
+ case "refs-hook":
1589
+ refsHookCommand();
1590
+ return true;
1591
+ default:
1592
+ return false;
1593
+ }
1594
+ }
1595
+ /**
1596
+ * PostToolUse-hook entrypoint for action gates. Reads the tool event on stdin,
1597
+ * runs the matching action gates from `.vigiles/action-gates.json`, and blocks
1598
+ * (exit 2 + reason on stderr) if any fails — plan-agnostic, so it works inside
1599
+ * dynamic workflows where there is no static step to attach a gate to.
1600
+ */
1601
+ function actionHookCommand() {
1602
+ let raw = "";
1603
+ try {
1604
+ raw = (0, node_fs_1.readFileSync)(0, "utf-8");
1605
+ }
1606
+ catch {
1607
+ /* no stdin */
1608
+ }
1609
+ let event = { tool: "" };
1610
+ try {
1611
+ const j = JSON.parse(raw);
1612
+ event = { tool: j.tool_name ?? "", input: j.tool_input };
1613
+ }
1614
+ catch {
1615
+ /* malformed input → no event, allow */
1616
+ }
1617
+ const decision = (0, action_gate_js_1.evaluateAction)(event, (0, action_gate_js_1.loadActionGates)(process.cwd()), process.cwd());
1618
+ if (!decision.allow) {
1619
+ console.error(decision.message);
1620
+ process.exit(2);
1621
+ }
1622
+ }
1623
+ const INSTRUCTION_FILE = /^(SKILL|CLAUDE|AGENTS)\.md$/;
1624
+ function isInstructionFile(file) {
1625
+ return INSTRUCTION_FILE.test((0, node_path_1.basename)(file));
1626
+ }
1627
+ /**
1628
+ * Inspect an instruction file's symbol references: broken file-qualified refs
1629
+ * (`path.ext#symbol` whose file/symbol is wrong) and code-shaped references not
1630
+ * yet marked. Emits one line per finding via `log`; returns whether any issue
1631
+ * was found. `basePath` is the file's own directory (where paths resolve).
1632
+ */
1633
+ function reportRefIssues(markdown, basePath, log) {
1634
+ const broken = (0, refs_js_1.verifySymbolRefs)(markdown, basePath);
1635
+ const unmarked = (0, refs_js_1.unmarkedCodeRefs)(markdown);
1636
+ for (const b of broken) {
1637
+ log(` ✗ line ${String(b.line)}: ${b.reason}`);
1638
+ }
1639
+ for (const u of unmarked) {
1640
+ const callee = u.text.replace(/\s*\([^)]*\)\s*$/, "");
1641
+ log(` ✗ line ${String(u.line)}: \`${u.text}\` is an unmarked code reference — ` +
1642
+ `mark it as \`vigiles:symbol path/to/file.ext#${callee}\` or add <!-- vigiles:ignore --> if it is prose`);
1643
+ }
1644
+ return broken.length > 0 || unmarked.length > 0;
1645
+ }
1646
+ /** `vigiles refs <file>` — check a file's symbol references (exit 2 on issues). */
1647
+ function refsCommand(target) {
1648
+ if (!target) {
1649
+ console.error("Usage: vigiles refs <instruction-file.md>");
1650
+ process.exit(2);
1651
+ }
1652
+ const cwd = process.cwd();
1653
+ let markdown;
1654
+ try {
1655
+ markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, target), "utf-8");
1656
+ }
1657
+ catch {
1658
+ console.error(`Cannot read ${target}`);
1659
+ process.exit(2);
1660
+ }
1661
+ const bad = reportRefIssues(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, target)), (m) => {
1662
+ console.log(m);
1663
+ });
1664
+ if (bad)
1665
+ process.exit(2);
1666
+ console.log(`✓ ${target}: all code references are marked and resolve.`);
1667
+ }
1668
+ /**
1669
+ * PostToolUse-hook entrypoint: when the agent edits an instruction file, force
1670
+ * every code reference to carry a file-qualified mark (`path.ext#symbol`) and
1671
+ * verify the marked ones against the named file. Exit 2 (reason on stderr)
1672
+ * blocks the edit and feeds the fix back to the agent — the harness makes the
1673
+ * agent mark its references, at write time, with full context. `vigiles:ignore`
1674
+ * opts a prose span out.
1675
+ */
1676
+ function refsHookCommand() {
1677
+ let raw = "";
1678
+ try {
1679
+ raw = (0, node_fs_1.readFileSync)(0, "utf-8");
1680
+ }
1681
+ catch {
1682
+ /* no stdin */
1683
+ }
1684
+ let file = "";
1685
+ try {
1686
+ const j = JSON.parse(raw);
1687
+ file = j.tool_input?.file_path ?? "";
1688
+ }
1689
+ catch {
1690
+ /* malformed → nothing to do */
1691
+ }
1692
+ if (!file || !isInstructionFile(file))
1693
+ return;
1694
+ const cwd = process.cwd();
1695
+ const target = (0, node_path_1.relative)(cwd, (0, node_path_1.resolve)(cwd, file)) || file;
1696
+ let markdown;
1697
+ try {
1698
+ markdown = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(cwd, file), "utf-8");
1699
+ }
1700
+ catch {
1701
+ return;
1702
+ }
1703
+ const lines = [];
1704
+ const bad = reportRefIssues(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, file)), (m) => {
1705
+ lines.push(m);
1706
+ });
1707
+ if (bad) {
1708
+ console.error(`vigiles: fix the code references in ${target}:`);
1709
+ for (const l of lines)
1710
+ console.error(l);
1711
+ process.exit(2);
1712
+ }
1713
+ }
1331
1714
  async function main() {
1332
1715
  const args = process.argv.slice(2);
1333
1716
  const command = args[0];
@@ -1371,28 +1754,19 @@ async function main() {
1371
1754
  // audit = verify + discover + guidance count
1372
1755
  const flags = args.slice(1).filter((a) => a.startsWith("--"));
1373
1756
  const report = await audit(restArgs, flags, config);
1757
+ annotateAuditForGitHub(report, flags);
1374
1758
  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
1759
  if (exitCode !== 0) {
1392
1760
  process.exit(exitCode);
1393
1761
  }
1394
1762
  break;
1395
1763
  }
1764
+ case "test":
1765
+ handleRunScripts("test", args, restArgs);
1766
+ break;
1767
+ case "eval":
1768
+ handleRunScripts("eval", args, restArgs);
1769
+ break;
1396
1770
  // --- Plumbing ---
1397
1771
  case "generate-types":
1398
1772
  handleGenerateTypes(args, restArgs);
@@ -1401,7 +1775,8 @@ async function main() {
1401
1775
  handleGenerateSchema(args, restArgs);
1402
1776
  break;
1403
1777
  default:
1404
- printUsage(command);
1778
+ if (!handleSkillCommand(command, restArgs))
1779
+ printUsage(command);
1405
1780
  break;
1406
1781
  }
1407
1782
  }
@@ -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