vigiles 2.5.0 → 3.0.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/.claude-plugin/plugin.json +9 -0
- package/README.md +73 -434
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress-proxy.d.ts +2 -0
- package/dist/adapters/claude-code/egress-proxy.js +60 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/adapters/claude-code/eval-baseline.d.ts +68 -0
- package/dist/adapters/claude-code/eval-baseline.js +173 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/adapters/claude-code/run-hook.d.ts +204 -0
- package/dist/adapters/claude-code/run-hook.js +418 -0
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +45 -4
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +76 -8
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claude-code.js +32 -0
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +60 -11
- package/dist/harness-assert.js +111 -9
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +9 -0
- package/dist/linting.js +25 -0
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.js +28 -0
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +35 -16
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +103 -0
- package/skills/generate-rule/SKILL.md +64 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-hook.d.ts +0 -77
- package/dist/run-hook.js +0 -80
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
package/dist/cli.js
CHANGED
|
@@ -13,27 +13,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
const node_fs_1 = require("node:fs");
|
|
14
14
|
const node_path_1 = require("node:path");
|
|
15
15
|
const glob_1 = require("glob");
|
|
16
|
-
const generate_types_js_1 = require("./generate-types.js");
|
|
17
|
-
const validate_js_1 = require("./validate.js");
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
16
|
+
const generate_types_js_1 = require("./core/generate-types.js");
|
|
17
|
+
const validate_js_1 = require("./core/validate.js");
|
|
18
|
+
const cli_flags_js_1 = require("./cli-flags.js");
|
|
19
|
+
const setup_plan_js_1 = require("./setup-plan.js");
|
|
20
|
+
const types_js_1 = require("./core/types.js");
|
|
21
|
+
const test_coverage_js_1 = require("./test-coverage.js");
|
|
22
|
+
const scan_js_1 = require("./scan.js");
|
|
23
|
+
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
24
|
+
const leaderboard_js_1 = require("./leaderboard.js");
|
|
25
|
+
const compile_js_1 = require("./core/compile.js");
|
|
26
|
+
const dialect_js_1 = require("./adapters/claude-code/dialect.js");
|
|
27
|
+
const proofs_js_1 = require("./core/proofs.js");
|
|
28
|
+
const inline_js_1 = require("./core/inline.js");
|
|
29
|
+
const frontmatter_js_1 = require("./core/frontmatter.js");
|
|
30
|
+
const generate_schema_js_1 = require("./core/generate-schema.js");
|
|
31
|
+
const compile_generator_js_1 = require("./core/compile-generator.js");
|
|
25
32
|
const action_gate_js_1 = require("./action-gate.js");
|
|
26
|
-
const agent_runtime_js_1 = require("./agent-runtime.js");
|
|
27
|
-
const refs_js_1 = require("./refs.js");
|
|
28
|
-
const mcp_js_1 = require("./mcp.js");
|
|
29
|
-
const skill_runtime_js_1 = require("./skill-runtime.js");
|
|
30
|
-
const linters_js_1 = require("./linters.js");
|
|
31
|
-
const harness_test_js_1 = require("./harness-test.js");
|
|
32
|
-
const run_scripts_js_1 = require("./run-scripts.js");
|
|
33
|
-
const integrity_js_1 = require("./integrity.js");
|
|
34
|
-
const coverage_js_1 = require("./coverage.js");
|
|
35
|
-
const orphans_js_1 = require("./orphans.js");
|
|
36
|
-
const doc_refs_js_1 = require("./doc-refs.js");
|
|
33
|
+
const agent_runtime_js_1 = require("./adapters/claude-code/agent-runtime.js");
|
|
34
|
+
const refs_js_1 = require("./core/refs.js");
|
|
35
|
+
const mcp_js_1 = require("./core/mcp.js");
|
|
36
|
+
const skill_runtime_js_1 = require("./adapters/claude-code/skill-runtime.js");
|
|
37
|
+
const linters_js_1 = require("./core/linters.js");
|
|
38
|
+
const harness_test_js_1 = require("./adapters/claude-code/harness-test.js");
|
|
39
|
+
const run_scripts_js_1 = require("./adapters/claude-code/run-scripts.js");
|
|
40
|
+
const integrity_js_1 = require("./core/integrity.js");
|
|
41
|
+
const coverage_js_1 = require("./core/coverage.js");
|
|
42
|
+
const orphans_js_1 = require("./core/orphans.js");
|
|
43
|
+
const doc_refs_js_1 = require("./core/doc-refs.js");
|
|
37
44
|
// ---------------------------------------------------------------------------
|
|
38
45
|
// Constants
|
|
39
46
|
// ---------------------------------------------------------------------------
|
|
@@ -133,6 +140,7 @@ function compileClaudeToFile(spec, specPath, config) {
|
|
|
133
140
|
const { markdown, errors, linterResults, targets } = (0, compile_js_1.compileClaude)(spec, {
|
|
134
141
|
basePath,
|
|
135
142
|
specFile: specPath,
|
|
143
|
+
dialect: dialect_js_1.claudeCodeDialect,
|
|
136
144
|
maxRules: config.maxRules,
|
|
137
145
|
maxTokens: config.maxTokens,
|
|
138
146
|
maxSectionLines: config.maxSectionLines,
|
|
@@ -168,6 +176,9 @@ function compileSkillToFile(spec, specPath) {
|
|
|
168
176
|
const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
|
|
169
177
|
basePath: process.cwd(),
|
|
170
178
|
specFile: specPath,
|
|
179
|
+
// Pick the SKILL.md frontmatter profile from the detected harness — a Codex
|
|
180
|
+
// repo gets a minimal (name + description) SKILL.md; CC gets the full set.
|
|
181
|
+
dialect: (0, adapter_registry_js_1.detectAdapter)(process.cwd()).dialect,
|
|
171
182
|
});
|
|
172
183
|
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
173
184
|
if (errors.length === 0) {
|
|
@@ -184,6 +195,7 @@ function compileAgentToFile(spec, specPath) {
|
|
|
184
195
|
const { markdown, errors } = (0, compile_js_1.compileAgent)(spec, {
|
|
185
196
|
basePath: process.cwd(),
|
|
186
197
|
specFile: specPath,
|
|
198
|
+
dialect: (0, adapter_registry_js_1.detectAdapter)(process.cwd()).dialect,
|
|
187
199
|
});
|
|
188
200
|
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
189
201
|
if (errors.length === 0) {
|
|
@@ -541,6 +553,7 @@ function auditExitCode(report) {
|
|
|
541
553
|
report.frontmatterErrors > 0 ||
|
|
542
554
|
report.integrityErrors > 0 ||
|
|
543
555
|
report.coverageErrors > 0 ||
|
|
556
|
+
report.untestedErrors > 0 ||
|
|
544
557
|
report.symbolRefErrors > 0 ||
|
|
545
558
|
report.mcpRefErrors > 0)
|
|
546
559
|
return 2;
|
|
@@ -817,6 +830,10 @@ async function audit(restArgs, flags, config) {
|
|
|
817
830
|
console.log(` ${line}`);
|
|
818
831
|
}
|
|
819
832
|
}
|
|
833
|
+
// 7b. Untested-surface check — skills/agents/hooks shipping without a test or
|
|
834
|
+
// eval. Warning by default (a nudge, exit 0); set rules.untested-surface to
|
|
835
|
+
// "error" to gate CI. See src/test-coverage.ts and docs/rules/untested-surface.md.
|
|
836
|
+
const untested = checkUntestedSurfaces(config, silent);
|
|
820
837
|
// 8. Validate vigiles builder calls inside markdown code blocks. Default
|
|
821
838
|
// is to validate every ref; illustrative blocks opt out via
|
|
822
839
|
// `<!-- vigiles:ignore -->` (single block) or
|
|
@@ -848,6 +865,8 @@ async function audit(restArgs, flags, config) {
|
|
|
848
865
|
integrityErrors,
|
|
849
866
|
coverageErrors,
|
|
850
867
|
orphanCount: orphanReport.orphans.length,
|
|
868
|
+
untestedSurfaces: untested.untested,
|
|
869
|
+
untestedErrors: untested.errors,
|
|
851
870
|
docRefErrors: docRefReport.errors.length,
|
|
852
871
|
symbolRefErrors,
|
|
853
872
|
mcpRefErrors,
|
|
@@ -876,6 +895,8 @@ function printAuditSummary(report) {
|
|
|
876
895
|
parts.push(`${String(report.duplicatePairs)} duplicates`);
|
|
877
896
|
if (report.orphanCount > 0)
|
|
878
897
|
parts.push(`${String(report.orphanCount)} orphan docs`);
|
|
898
|
+
if (report.untestedSurfaces > 0)
|
|
899
|
+
parts.push(`${String(report.untestedSurfaces)} untested surfaces`);
|
|
879
900
|
if (report.docRefErrors > 0)
|
|
880
901
|
parts.push(`${String(report.docRefErrors)} broken doc refs`);
|
|
881
902
|
if (report.symbolRefErrors > 0)
|
|
@@ -1016,34 +1037,133 @@ export default claude({${targetLine}
|
|
|
1016
1037
|
// ---------------------------------------------------------------------------
|
|
1017
1038
|
// Setup wizard
|
|
1018
1039
|
// ---------------------------------------------------------------------------
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
function
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1040
|
+
/** Full GitHub Actions workflow that wires the production `zernie/vigiles@v1`
|
|
1041
|
+
* Action (Pillar 1) and, when Pillar 2 is set up, a deterministic harness job. */
|
|
1042
|
+
function vigilesWorkflow(plan) {
|
|
1043
|
+
const harness = plan.test
|
|
1044
|
+
? `
|
|
1045
|
+
harness:
|
|
1046
|
+
# Pillar 2 — run your *.harness.{mjs,ts} tests against the real agent CLI and
|
|
1047
|
+
# a scripted mock model (deterministic, no API key). Drop this job if you only
|
|
1048
|
+
# author runHook unit tests, or keep it for the deterministic tier.
|
|
1049
|
+
runs-on: ubuntu-latest
|
|
1050
|
+
steps:
|
|
1051
|
+
- uses: actions/checkout@v4
|
|
1052
|
+
- uses: actions/setup-node@v4
|
|
1053
|
+
with:
|
|
1054
|
+
node-version: "20"
|
|
1055
|
+
- run: npm install
|
|
1056
|
+
- run: npm i -g @anthropic-ai/claude-code # mock tier needs the binary, no API key
|
|
1057
|
+
- run: npx vigiles test
|
|
1058
|
+
`
|
|
1059
|
+
: "";
|
|
1060
|
+
return `name: vigiles
|
|
1061
|
+
on:
|
|
1062
|
+
pull_request:
|
|
1063
|
+
push:
|
|
1064
|
+
branches: [main]
|
|
1065
|
+
|
|
1066
|
+
permissions:
|
|
1067
|
+
contents: read
|
|
1068
|
+
pull-requests: write # for the sticky PR comment
|
|
1069
|
+
|
|
1070
|
+
jobs:
|
|
1071
|
+
verify:
|
|
1072
|
+
# Pillar 1 — verify the references in your instruction files (composite Action
|
|
1073
|
+
# over the published CLI). Posts a sticky PR comment + a \`valid\` output.
|
|
1074
|
+
runs-on: ubuntu-latest
|
|
1075
|
+
steps:
|
|
1076
|
+
- uses: actions/checkout@v4
|
|
1077
|
+
- uses: actions/setup-node@v4
|
|
1078
|
+
with:
|
|
1079
|
+
node-version: "20"
|
|
1080
|
+
- uses: zernie/vigiles@v1
|
|
1081
|
+
${harness}`;
|
|
1082
|
+
}
|
|
1083
|
+
/** Create `.github/workflows/vigiles.yml` (or note it already exists). */
|
|
1084
|
+
function wireGha(plan) {
|
|
1085
|
+
const dir = (0, node_path_1.resolve)(process.cwd(), ".github", "workflows");
|
|
1086
|
+
const path = (0, node_path_1.resolve)(dir, "vigiles.yml");
|
|
1087
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
1088
|
+
console.log("✓ .github/workflows/vigiles.yml already exists");
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
1092
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
1093
|
+
(0, node_fs_1.writeFileSync)(path, vigilesWorkflow(plan));
|
|
1094
|
+
console.log("✓ Created .github/workflows/vigiles.yml (uses zernie/vigiles@v1)");
|
|
1095
|
+
}
|
|
1096
|
+
const STARTER_HARNESS = `/**
|
|
1097
|
+
* Starter harness test (Pillar 2) — scaffolded by \`vigiles init\`.
|
|
1098
|
+
* Proves a hook actually FIRES, deterministically and with no API key.
|
|
1099
|
+
*
|
|
1100
|
+
* npm i -D vigiles # the testing API this imports
|
|
1101
|
+
* npx vigiles test # or: node vigiles.harness.mjs
|
|
1102
|
+
*
|
|
1103
|
+
* Guide: https://github.com/zernie/vigiles/blob/main/docs/harness-testing.md
|
|
1104
|
+
*/
|
|
1105
|
+
import { runHook } from "vigiles/testing";
|
|
1106
|
+
import assert from "node:assert/strict";
|
|
1107
|
+
|
|
1108
|
+
// EXAMPLE — replace with one of YOUR hooks. This PreToolUse Bash guard blocks a
|
|
1109
|
+
// destructive command; runHook pipes it a fake event and checks the decision.
|
|
1110
|
+
const guard =
|
|
1111
|
+
\`CMD=$(cat | jq -r '.tool_input.command // empty'); \` +
|
|
1112
|
+
\`case "$CMD" in *"rm -rf /"*) echo blocked >&2; exit 2 ;; esac; exit 0\`;
|
|
1113
|
+
|
|
1114
|
+
const blocked = runHook(guard, {
|
|
1115
|
+
hook_event_name: "PreToolUse",
|
|
1116
|
+
tool_name: "Bash",
|
|
1117
|
+
tool_input: { command: "rm -rf / --no-preserve-root" },
|
|
1118
|
+
});
|
|
1119
|
+
assert.ok(blocked.blocked, "guard should block \\\`rm -rf /\\\`");
|
|
1120
|
+
|
|
1121
|
+
const allowed = runHook(guard, {
|
|
1122
|
+
hook_event_name: "PreToolUse",
|
|
1123
|
+
tool_name: "Bash",
|
|
1124
|
+
tool_input: { command: "ls -la" },
|
|
1125
|
+
});
|
|
1126
|
+
assert.ok(!allowed.blocked, "guard should allow a safe command");
|
|
1127
|
+
|
|
1128
|
+
console.log("\\u2713 hook blocks rm -rf / and allows safe commands");
|
|
1129
|
+
`;
|
|
1130
|
+
/** Pillar 2 — scaffold a starter harness test the user adapts to their hooks. */
|
|
1131
|
+
function scaffoldPillar2() {
|
|
1132
|
+
const path = (0, node_path_1.resolve)(process.cwd(), "vigiles.harness.mjs");
|
|
1133
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
1134
|
+
console.log("✓ vigiles.harness.mjs already exists");
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
(0, node_fs_1.writeFileSync)(path, STARTER_HARNESS);
|
|
1138
|
+
console.log("✓ Scaffolded vigiles.harness.mjs — Pillar 2 starter (npm i -D vigiles && npx vigiles test)");
|
|
1139
|
+
}
|
|
1140
|
+
/** Interactive prompts (TTY only): which pillars, CI, plugin. */
|
|
1141
|
+
async function promptSetup() {
|
|
1142
|
+
const readline = await import("node:readline");
|
|
1143
|
+
const rl = readline.createInterface({
|
|
1144
|
+
input: process.stdin,
|
|
1145
|
+
output: process.stdout,
|
|
1146
|
+
});
|
|
1147
|
+
const ask = (q, def) => new Promise((res) => {
|
|
1148
|
+
rl.question(q, (a) => {
|
|
1149
|
+
res(a.trim() || def);
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
const isYes = (s) => /^y(es)?$/i.test(s);
|
|
1153
|
+
try {
|
|
1154
|
+
const pillars = (await ask("Set up which pillars? [both/verify/test] (both): ", "both")).toLowerCase();
|
|
1155
|
+
const gha = isYes(await ask("Wire CI (GitHub Action)? [Y/n]: ", "y"));
|
|
1156
|
+
const plugin = isYes(await ask("Install the Claude Code plugin (hooks + skills)? [Y/n]: ", "y"));
|
|
1157
|
+
return {
|
|
1158
|
+
verify: pillars !== "test",
|
|
1159
|
+
test: pillars !== "verify",
|
|
1160
|
+
gha,
|
|
1161
|
+
plugin,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
finally {
|
|
1165
|
+
rl.close();
|
|
1045
1166
|
}
|
|
1046
|
-
return false;
|
|
1047
1167
|
}
|
|
1048
1168
|
const KNOWN_INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
1049
1169
|
const KNOWN_OTHER_CONFIGS = {
|
|
@@ -1120,42 +1240,17 @@ function detectProject() {
|
|
|
1120
1240
|
hasClaude: (0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, ".claude")),
|
|
1121
1241
|
};
|
|
1122
1242
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
console.log(`vigiles setup${strict ? " (strict mode)" : ""}\n`);
|
|
1128
|
-
// Step 1: Detect project
|
|
1129
|
-
const detected = detectProject();
|
|
1130
|
-
if (detected.agents.length > 0) {
|
|
1131
|
-
console.log(`Detected: ${detected.agents.join(", ")}`);
|
|
1132
|
-
}
|
|
1133
|
-
if (detected.otherConfigs.length > 0) {
|
|
1134
|
-
console.log(`Other agent configs: ${detected.otherConfigs.join(", ")}`);
|
|
1135
|
-
}
|
|
1136
|
-
if (detected.syncTools.length > 0) {
|
|
1137
|
-
console.log(`Sync tools: ${detected.syncTools.join(", ")}`);
|
|
1138
|
-
}
|
|
1139
|
-
for (const f of detected.instructionFiles) {
|
|
1140
|
-
if (f.isSymlink) {
|
|
1141
|
-
console.log(`Note: ${f.path} is a symlink`);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
if (detected.agents.length > 0 ||
|
|
1145
|
-
detected.otherConfigs.length > 0 ||
|
|
1146
|
-
detected.syncTools.length > 0) {
|
|
1147
|
-
console.log("");
|
|
1148
|
-
}
|
|
1149
|
-
// Step 2: Determine targets
|
|
1243
|
+
/** Pillar 1 — specs + types + schema + compile. Returns the spec targets
|
|
1244
|
+
* created/found (empty when every existing file needs migration instead). */
|
|
1245
|
+
async function setupPillar1(detected, targetValue) {
|
|
1246
|
+
// Determine targets
|
|
1150
1247
|
let targets;
|
|
1151
|
-
if (
|
|
1152
|
-
targets = [
|
|
1248
|
+
if (targetValue) {
|
|
1249
|
+
targets = [targetValue];
|
|
1153
1250
|
}
|
|
1154
1251
|
else {
|
|
1155
|
-
// Auto-detect: create specs for instruction files that need them
|
|
1156
1252
|
const needsSpec = detected.instructionFiles.filter((f) => !f.hasSpec);
|
|
1157
1253
|
if (needsSpec.length > 0) {
|
|
1158
|
-
// Existing files without specs — suggest migration
|
|
1159
1254
|
for (const f of needsSpec) {
|
|
1160
1255
|
console.log(`Found ${f.path} without a spec. Migrate with the migrate-to-spec skill`);
|
|
1161
1256
|
console.log(` or create a blank spec: npx vigiles init --target=${f.path}\n`);
|
|
@@ -1163,21 +1258,18 @@ async function setup(args) {
|
|
|
1163
1258
|
const hasAnySpec = detected.instructionFiles.some((f) => f.hasSpec);
|
|
1164
1259
|
if (!hasAnySpec &&
|
|
1165
1260
|
needsSpec.length === detected.instructionFiles.length) {
|
|
1166
|
-
// ALL existing files need migration — don't create new
|
|
1167
|
-
console.log("
|
|
1168
|
-
|
|
1169
|
-
return;
|
|
1261
|
+
// ALL existing files need migration — don't create new specs.
|
|
1262
|
+
console.log("Use the migration skill: npx skills add zernie/vigiles\n");
|
|
1263
|
+
return [];
|
|
1170
1264
|
}
|
|
1171
1265
|
}
|
|
1172
|
-
// Default: CLAUDE.md, plus AGENTS.md if Codex detected
|
|
1173
1266
|
targets = ["CLAUDE.md"];
|
|
1174
1267
|
const hasAgentsMd = detected.instructionFiles.some((f) => f.path === "AGENTS.md");
|
|
1175
1268
|
const hasCodex = detected.agents.includes("Codex / GitHub Copilot") || hasAgentsMd;
|
|
1176
|
-
if (hasCodex && !hasAgentsMd)
|
|
1269
|
+
if (hasCodex && !hasAgentsMd)
|
|
1177
1270
|
targets.push("AGENTS.md");
|
|
1178
|
-
}
|
|
1179
1271
|
}
|
|
1180
|
-
//
|
|
1272
|
+
// Create specs
|
|
1181
1273
|
for (const target of targets) {
|
|
1182
1274
|
const specPath = `${target}.spec.ts`;
|
|
1183
1275
|
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), specPath))) {
|
|
@@ -1190,105 +1282,129 @@ async function setup(args) {
|
|
|
1190
1282
|
init(["--target=" + target]);
|
|
1191
1283
|
}
|
|
1192
1284
|
}
|
|
1193
|
-
//
|
|
1285
|
+
// Generate types + schema
|
|
1194
1286
|
console.log("\nScanning linters and project files...");
|
|
1195
1287
|
const typesResult = (0, generate_types_js_1.generateTypes)({ basePath: process.cwd() });
|
|
1196
|
-
const outPath = ".vigiles/generated.d.ts";
|
|
1197
1288
|
const outDir = (0, node_path_1.resolve)(process.cwd(), ".vigiles");
|
|
1198
|
-
if (!(0, node_fs_1.existsSync)(outDir))
|
|
1289
|
+
if (!(0, node_fs_1.existsSync)(outDir))
|
|
1199
1290
|
(0, node_fs_1.mkdirSync)(outDir, { recursive: true });
|
|
1200
|
-
|
|
1201
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outPath), typesResult.dts);
|
|
1291
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), ".vigiles/generated.d.ts"), typesResult.dts);
|
|
1202
1292
|
for (const l of typesResult.linters) {
|
|
1203
1293
|
console.log(` ${l.linter}: ${String(l.rules.length)} rules`);
|
|
1204
1294
|
}
|
|
1205
1295
|
if (typesResult.scripts.length > 0) {
|
|
1206
1296
|
console.log(` npm scripts: ${String(typesResult.scripts.length)}`);
|
|
1207
1297
|
}
|
|
1208
|
-
console.log(
|
|
1209
|
-
// Also emit a JSON Schema so `vigiles:` markdown frontmatter (Level 1)
|
|
1210
|
-
// gets rule-name autocomplete + typo squiggles from the editor's YAML LSP.
|
|
1298
|
+
console.log("✓ Generated .vigiles/generated.d.ts");
|
|
1211
1299
|
const schemaResult = (0, generate_schema_js_1.generateSchema)({
|
|
1212
1300
|
basePath: process.cwd(),
|
|
1213
1301
|
linters: (0, validate_js_1.loadConfig)().linters,
|
|
1214
1302
|
});
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
// Step 5: Compile specs
|
|
1303
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), ".vigiles/schema.json"), schemaResult.json);
|
|
1304
|
+
console.log("✓ Generated .vigiles/schema.json (YAML-LSP frontmatter schema)");
|
|
1305
|
+
// Compile specs
|
|
1219
1306
|
console.log("\nCompiling specs...");
|
|
1220
1307
|
const specs = findSpecs();
|
|
1221
|
-
if (specs.length > 0)
|
|
1308
|
+
if (specs.length > 0)
|
|
1222
1309
|
await compile(specs, (0, validate_js_1.loadConfig)());
|
|
1310
|
+
return targets;
|
|
1311
|
+
}
|
|
1312
|
+
/** Install the Claude Code plugin (hooks + skills). Returns whether the
|
|
1313
|
+
* fallback wrote `.claude/settings.json` (for the commit hint). */
|
|
1314
|
+
function installPlugin() {
|
|
1315
|
+
try {
|
|
1316
|
+
const { execSync: exec } = require("node:child_process");
|
|
1317
|
+
exec("npx skills add zernie/vigiles", {
|
|
1318
|
+
cwd: process.cwd(),
|
|
1319
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1320
|
+
timeout: 30000,
|
|
1321
|
+
});
|
|
1322
|
+
console.log("✓ Installed vigiles plugin (hooks + skills) via skills CLI");
|
|
1323
|
+
return false;
|
|
1223
1324
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (shouldInstallPlugin) {
|
|
1234
|
-
let pluginInstalled = false;
|
|
1235
|
-
// Try installing the full plugin via skills CLI (gives hooks + skills)
|
|
1325
|
+
catch {
|
|
1326
|
+
// skills CLI not available — fall back to direct hook installation.
|
|
1327
|
+
}
|
|
1328
|
+
const settingsDir = (0, node_path_1.resolve)(process.cwd(), ".claude");
|
|
1329
|
+
const settingsPath = (0, node_path_1.resolve)(settingsDir, "settings.json");
|
|
1330
|
+
if (!(0, node_fs_1.existsSync)(settingsDir))
|
|
1331
|
+
(0, node_fs_1.mkdirSync)(settingsDir, { recursive: true });
|
|
1332
|
+
let settings = {};
|
|
1333
|
+
if ((0, node_fs_1.existsSync)(settingsPath)) {
|
|
1236
1334
|
try {
|
|
1237
|
-
|
|
1238
|
-
exec("npx skills add zernie/vigiles", {
|
|
1239
|
-
cwd: process.cwd(),
|
|
1240
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1241
|
-
timeout: 30000,
|
|
1242
|
-
});
|
|
1243
|
-
pluginInstalled = true;
|
|
1244
|
-
console.log("✓ Installed vigiles plugin (hooks + skills) via skills CLI");
|
|
1335
|
+
settings = JSON.parse((0, node_fs_1.readFileSync)(settingsPath, "utf-8"));
|
|
1245
1336
|
}
|
|
1246
1337
|
catch {
|
|
1247
|
-
//
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
}
|
|
1338
|
+
// Ignore malformed settings.
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (!settings["hooks"])
|
|
1342
|
+
settings["hooks"] = {};
|
|
1343
|
+
const hooks = settings["hooks"];
|
|
1344
|
+
const preCmd = `FILE=$(cat | jq -r '.tool_input.file_path // empty') && case "$FILE" in *.md) [ -f "$FILE" ] && head -1 "$FILE" | grep -q 'vigiles:sha256:' && { SPEC=$(head -1 "$FILE" | sed -n 's/.*compiled from \\(.*\\) -->/\\1/p'); echo "BLOCKED: Edit $SPEC instead." >&2; exit 2; } ;; esac; exit 0`;
|
|
1345
|
+
const postCmd = `FILE=$(cat | jq -r '.tool_input.file_path // empty') && case "$(basename "$FILE")" in eslint.config.*|.eslintrc*|package.json|pyproject.toml|Cargo.toml) npx vigiles generate-types 2>&1 || true ;; esac && case "$FILE" in *.spec.ts) npx vigiles compile 2>&1 || true ;; esac`;
|
|
1346
|
+
const existingStr = JSON.stringify(settings);
|
|
1347
|
+
if (!existingStr.includes("vigiles:sha256")) {
|
|
1348
|
+
const pre = (hooks["PreToolUse"] ?? []);
|
|
1349
|
+
pre.push({ matcher: "Edit|Write", command: preCmd });
|
|
1350
|
+
hooks["PreToolUse"] = pre;
|
|
1351
|
+
}
|
|
1352
|
+
if (!existingStr.includes("vigiles compile")) {
|
|
1353
|
+
const post = (hooks["PostToolUse"] ?? []);
|
|
1354
|
+
post.push({ matcher: "Edit|Write", command: postCmd });
|
|
1355
|
+
hooks["PostToolUse"] = post;
|
|
1356
|
+
}
|
|
1357
|
+
(0, node_fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1358
|
+
console.log("✓ Installed hooks in .claude/settings.json");
|
|
1359
|
+
console.log(" (For skills like edit-spec and migrate-to-spec, also run: npx skills add zernie/vigiles)");
|
|
1360
|
+
return true;
|
|
1361
|
+
}
|
|
1362
|
+
async function setup(args) {
|
|
1363
|
+
const parsed = (0, setup_plan_js_1.parseSetupArgs)(args);
|
|
1364
|
+
const strict = parsed.strict;
|
|
1365
|
+
// Plan: defaults → flags → interactive prompts (only a human at a TTY).
|
|
1366
|
+
let plan = (0, setup_plan_js_1.resolvePlan)(parsed);
|
|
1367
|
+
if ((0, setup_plan_js_1.shouldPrompt)(parsed, process.stdin.isTTY ?? false)) {
|
|
1368
|
+
plan = (0, setup_plan_js_1.resolvePlan)(parsed, await promptSetup());
|
|
1369
|
+
}
|
|
1370
|
+
const pillars = [plan.verify && "verify", plan.test && "test"]
|
|
1371
|
+
.filter(Boolean)
|
|
1372
|
+
.join(" + ");
|
|
1373
|
+
console.log(`vigiles setup${strict ? " (strict)" : ""} — pillars: ${pillars}\n`);
|
|
1374
|
+
// Detect project.
|
|
1375
|
+
const detected = detectProject();
|
|
1376
|
+
if (detected.agents.length > 0) {
|
|
1377
|
+
console.log(`Detected: ${detected.agents.join(", ")}`);
|
|
1288
1378
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1379
|
+
if (detected.otherConfigs.length > 0) {
|
|
1380
|
+
console.log(`Other agent configs: ${detected.otherConfigs.join(", ")}`);
|
|
1381
|
+
}
|
|
1382
|
+
if (detected.syncTools.length > 0) {
|
|
1383
|
+
console.log(`Sync tools: ${detected.syncTools.join(", ")}`);
|
|
1384
|
+
}
|
|
1385
|
+
for (const f of detected.instructionFiles) {
|
|
1386
|
+
if (f.isSymlink)
|
|
1387
|
+
console.log(`Note: ${f.path} is a symlink`);
|
|
1388
|
+
}
|
|
1389
|
+
// Pillar 1 — verify instruction files.
|
|
1390
|
+
let targets = [];
|
|
1391
|
+
if (plan.verify) {
|
|
1392
|
+
console.log("");
|
|
1393
|
+
targets = await setupPillar1(detected, parsed.target);
|
|
1394
|
+
}
|
|
1395
|
+
// Pillar 2 — test the harness.
|
|
1396
|
+
if (plan.test) {
|
|
1397
|
+
console.log("");
|
|
1398
|
+
scaffoldPillar2();
|
|
1399
|
+
}
|
|
1400
|
+
// CI — the production Action (+ a harness job when Pillar 2 is on).
|
|
1401
|
+
if (plan.gha) {
|
|
1402
|
+
console.log("");
|
|
1403
|
+
wireGha(plan);
|
|
1404
|
+
}
|
|
1405
|
+
// Claude Code plugin (hooks + skills).
|
|
1406
|
+
const wroteSettings = plan.plugin ? installPlugin() : false;
|
|
1407
|
+
// Agent-specific guidance.
|
|
1292
1408
|
if (targets.includes("AGENTS.md")) {
|
|
1293
1409
|
console.log("\n Codex / Copilot reads AGENTS.md directly — no hooks needed.");
|
|
1294
1410
|
console.log(" Run `npx vigiles compile` after spec edits. CI enforces freshness.");
|
|
@@ -1297,37 +1413,42 @@ async function setup(args) {
|
|
|
1297
1413
|
console.log("\n Non-markdown agent configs detected. Use a sync tool to convert:");
|
|
1298
1414
|
console.log(" npm install -D rule-porter");
|
|
1299
1415
|
}
|
|
1300
|
-
//
|
|
1416
|
+
// Strict config.
|
|
1301
1417
|
if (strict) {
|
|
1302
1418
|
const configPath = (0, node_path_1.resolve)(process.cwd(), ".vigilesrc.json");
|
|
1303
1419
|
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
1304
|
-
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify({
|
|
1305
|
-
rules: {
|
|
1306
|
-
"require-spec": "error",
|
|
1307
|
-
"require-skill-spec": "error",
|
|
1308
|
-
},
|
|
1309
|
-
}, null, 2) + "\n");
|
|
1420
|
+
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify({ rules: { "require-spec": "error", "require-skill-spec": "error" } }, null, 2) + "\n");
|
|
1310
1421
|
console.log("✓ Created .vigilesrc.json with strict rules");
|
|
1311
1422
|
}
|
|
1312
1423
|
}
|
|
1313
|
-
//
|
|
1314
|
-
|
|
1315
|
-
console.log("
|
|
1316
|
-
|
|
1317
|
-
|
|
1424
|
+
// Summary.
|
|
1425
|
+
const specPathsList = targets.map((t) => `${t}.spec.ts`);
|
|
1426
|
+
console.log("\n---\nSetup complete.\n");
|
|
1427
|
+
const nextSteps = [];
|
|
1428
|
+
if (specPathsList.length > 0) {
|
|
1429
|
+
nextSteps.push(`Edit ${specPathsList.join(", ")} — add your conventions, then \`/strengthen\``);
|
|
1430
|
+
}
|
|
1431
|
+
if (plan.test) {
|
|
1432
|
+
nextSteps.push("Edit vigiles.harness.mjs to test a real hook, then `npx vigiles test`");
|
|
1433
|
+
}
|
|
1318
1434
|
if (!strict) {
|
|
1319
|
-
|
|
1435
|
+
nextSteps.push("When ready, enforce in CI: npx vigiles init --strict");
|
|
1320
1436
|
}
|
|
1321
|
-
|
|
1437
|
+
nextSteps.forEach((s, i) => {
|
|
1438
|
+
console.log(` ${String(i + 1)}. ${s}`);
|
|
1439
|
+
});
|
|
1322
1440
|
const files = [
|
|
1323
1441
|
...targets,
|
|
1324
1442
|
...specPathsList,
|
|
1325
|
-
".vigiles/generated.d.ts",
|
|
1326
|
-
"
|
|
1327
|
-
...(
|
|
1443
|
+
...(plan.verify ? [".vigiles/generated.d.ts", ".vigiles/schema.json"] : []),
|
|
1444
|
+
...(plan.test ? ["vigiles.harness.mjs"] : []),
|
|
1445
|
+
...(plan.gha ? [".github/workflows/vigiles.yml"] : []),
|
|
1446
|
+
...(wroteSettings ? [".claude/settings.json"] : []),
|
|
1328
1447
|
...(strict ? [".vigilesrc.json"] : []),
|
|
1329
1448
|
];
|
|
1330
|
-
|
|
1449
|
+
if (files.length > 0) {
|
|
1450
|
+
console.log(`\n Commit:\n git add ${files.join(" ")} && git commit -m "Add vigiles"`);
|
|
1451
|
+
}
|
|
1331
1452
|
}
|
|
1332
1453
|
// ---------------------------------------------------------------------------
|
|
1333
1454
|
// Strengthen: guidance() → enforce() suggestions
|
|
@@ -1358,6 +1479,32 @@ function checkIntegrityForFiles(files, severity, silent) {
|
|
|
1358
1479
|
}
|
|
1359
1480
|
return severity === "error" ? errorCount : 0;
|
|
1360
1481
|
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Apply the `untested-surface` rule: find skills/agents/hooks with no test or
|
|
1484
|
+
* eval (see src/test-coverage.ts). Returns the raw untested count plus the
|
|
1485
|
+
* severity-gated error count — "warn" prints but never fails CI (errors=0),
|
|
1486
|
+
* "error" fails (exit 2), mirroring the integrity check.
|
|
1487
|
+
*/
|
|
1488
|
+
function checkUntestedSurfaces(config, silent) {
|
|
1489
|
+
const severity = (0, types_js_1.ruleSeverity)(config?.rules["untested-surface"]);
|
|
1490
|
+
if (!severity)
|
|
1491
|
+
return { untested: 0, errors: 0 };
|
|
1492
|
+
const opts = (0, types_js_1.ruleOptions)(config?.rules["untested-surface"]);
|
|
1493
|
+
const report = (0, test_coverage_js_1.findUntestedSurfaces)({ basePath: process.cwd(), ...opts });
|
|
1494
|
+
if (!silent) {
|
|
1495
|
+
console.log("\nUntested surfaces:\n");
|
|
1496
|
+
for (const line of (0, test_coverage_js_1.formatUntestedReport)(report).split("\n")) {
|
|
1497
|
+
console.log(` ${line}`);
|
|
1498
|
+
}
|
|
1499
|
+
for (const s of report.untested) {
|
|
1500
|
+
ghAnnotate(severity === "error" ? "error" : "warning", `${s.kind} ${s.path} ships without a test or eval`, s.path);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
untested: report.untested.length,
|
|
1505
|
+
errors: severity === "error" ? report.untested.length : 0,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1361
1508
|
/**
|
|
1362
1509
|
* Apply the configured coverage thresholds. Returns the number of failing
|
|
1363
1510
|
* thresholds (so the audit can fail CI when severity is "error").
|
|
@@ -1534,16 +1681,19 @@ function handleGenerateSchema(args, restArgs) {
|
|
|
1534
1681
|
*/
|
|
1535
1682
|
function handleRunScripts(kind, args, restArgs) {
|
|
1536
1683
|
const cwd = process.cwd();
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
console.log("vigiles test: `claude` CLI not found — skipping harness tests.");
|
|
1540
|
-
return;
|
|
1541
|
-
}
|
|
1684
|
+
// Harness/eval scripts may be authored in JS or TS (see run-scripts.ts).
|
|
1685
|
+
const defaultGlob = (0, run_scripts_js_1.scriptGlob)(kind === "test" ? "harness" : "eval");
|
|
1542
1686
|
const files = (0, run_scripts_js_1.discoverScripts)(restArgs, defaultGlob, cwd);
|
|
1543
1687
|
if (files.length === 0) {
|
|
1544
1688
|
console.log(`No ${defaultGlob} files found.`);
|
|
1545
1689
|
return;
|
|
1546
1690
|
}
|
|
1691
|
+
// No blanket skip: unit-tier (runHook) tests need no `claude`, so always run.
|
|
1692
|
+
// A script whose tier DOES need `claude` self-reports `⊘ SKIPPED` (exit 77) —
|
|
1693
|
+
// loud, never a silent green. Just flag up front that some may skip.
|
|
1694
|
+
if (kind === "test" && !(0, harness_test_js_1.claudeAvailable)()) {
|
|
1695
|
+
console.log("ℹ `claude` CLI not found — unit-tier tests run; tests that need it report SKIPPED.\n");
|
|
1696
|
+
}
|
|
1547
1697
|
const trialsFlag = args.find((a) => a.startsWith("--trials="));
|
|
1548
1698
|
const env = {};
|
|
1549
1699
|
if (trialsFlag)
|
|
@@ -1551,8 +1701,16 @@ function handleRunScripts(kind, args, restArgs) {
|
|
|
1551
1701
|
console.log(`Running ${String(files.length)} ${kind} file(s):\n`);
|
|
1552
1702
|
const results = (0, run_scripts_js_1.runScripts)(files, cwd, env);
|
|
1553
1703
|
console.log("\n" + (0, run_scripts_js_1.formatScriptSummary)(results));
|
|
1554
|
-
if (
|
|
1704
|
+
if ((0, run_scripts_js_1.anyFailed)(results))
|
|
1705
|
+
process.exit(1);
|
|
1706
|
+
// `--no-skip`: in a context that ASSERTS the capability is present (a CI job),
|
|
1707
|
+
// a skipped tier is untested surface — fail loudly instead of passing green.
|
|
1708
|
+
if (args.includes("--no-skip") && results.some((r) => r.status === "skip")) {
|
|
1709
|
+
const n = results.filter((r) => r.status === "skip").length;
|
|
1710
|
+
console.log(`\n✗ --no-skip: ${String(n)} tier(s) SKIPPED — untested surface here. ` +
|
|
1711
|
+
"Install the missing capability (e.g. the `claude` CLI) or scope the run.");
|
|
1555
1712
|
process.exit(1);
|
|
1713
|
+
}
|
|
1556
1714
|
}
|
|
1557
1715
|
function printUsage(command) {
|
|
1558
1716
|
console.log("vigiles — compile typed specs to instruction files");
|
|
@@ -1785,17 +1943,10 @@ function isInstructionFile(file) {
|
|
|
1785
1943
|
* was found. `basePath` is the file's own directory (where paths resolve).
|
|
1786
1944
|
*/
|
|
1787
1945
|
function reportRefIssues(markdown, basePath, log) {
|
|
1788
|
-
const
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
}
|
|
1793
|
-
for (const u of unmarked) {
|
|
1794
|
-
const callee = u.text.replace(/\s*\([^)]*\)\s*$/, "");
|
|
1795
|
-
log(` ✗ line ${String(u.line)}: \`${u.text}\` is an unmarked code reference — ` +
|
|
1796
|
-
`mark it as \`vigiles:symbol path/to/file.ext#${callee}\` or add <!-- vigiles:ignore --> if it is prose`);
|
|
1797
|
-
}
|
|
1798
|
-
return broken.length > 0 || unmarked.length > 0;
|
|
1946
|
+
const issues = (0, refs_js_1.collectRefIssues)(markdown, basePath);
|
|
1947
|
+
for (const m of issues)
|
|
1948
|
+
log(` ✗ ${m}`);
|
|
1949
|
+
return issues.length > 0;
|
|
1799
1950
|
}
|
|
1800
1951
|
/** `vigiles refs <file>` — check a file's symbol references (exit 2 on issues). */
|
|
1801
1952
|
function refsCommand(target) {
|
|
@@ -1845,6 +1996,9 @@ function refsHookCommand() {
|
|
|
1845
1996
|
}
|
|
1846
1997
|
if (!file || !isInstructionFile(file))
|
|
1847
1998
|
return;
|
|
1999
|
+
const severity = (0, types_js_1.ruleSeverity)((0, validate_js_1.loadConfig)().rules["unmarked-refs"]);
|
|
2000
|
+
if (severity === false)
|
|
2001
|
+
return;
|
|
1848
2002
|
const cwd = process.cwd();
|
|
1849
2003
|
const target = (0, node_path_1.relative)(cwd, (0, node_path_1.resolve)(cwd, file)) || file;
|
|
1850
2004
|
let markdown;
|
|
@@ -1854,22 +2008,39 @@ function refsHookCommand() {
|
|
|
1854
2008
|
catch {
|
|
1855
2009
|
return;
|
|
1856
2010
|
}
|
|
1857
|
-
const
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
if (
|
|
2011
|
+
const issues = (0, refs_js_1.collectRefIssues)(markdown, (0, node_path_1.dirname)((0, node_path_1.resolve)(cwd, file)));
|
|
2012
|
+
const action = (0, refs_js_1.refsHookAction)(issues.length, severity);
|
|
2013
|
+
if (action === "ok")
|
|
2014
|
+
return;
|
|
2015
|
+
if (action === "block") {
|
|
2016
|
+
// Opt-in (`unmarked-refs: "error"`): exit 2 feeds stderr to the model.
|
|
1862
2017
|
console.error(`vigiles: fix the code references in ${target}:`);
|
|
1863
|
-
for (const
|
|
1864
|
-
console.error(
|
|
2018
|
+
for (const m of issues)
|
|
2019
|
+
console.error(` ✗ ${m}`);
|
|
1865
2020
|
process.exit(2);
|
|
1866
2021
|
}
|
|
2022
|
+
// Default ("warn"): a non-blocking nudge injected into the agent's context.
|
|
2023
|
+
const context = `vigiles: ${target} has reference(s) that won't be verified unless they ` +
|
|
2024
|
+
`are vigiles marks:\n` +
|
|
2025
|
+
issues.map((m) => ` - ${m}`).join("\n") +
|
|
2026
|
+
`\nExpress references as marks (\`enforce()\` / \`file()\` / \`cmd()\` / a ` +
|
|
2027
|
+
`\`vigiles:symbol\` span / an inline \`<!-- vigiles:enforce -->\` comment) so ` +
|
|
2028
|
+
`\`vigiles audit\` can check them — or add \`<!-- vigiles:ignore -->\` if it ` +
|
|
2029
|
+
`is prose, not a reference.`;
|
|
2030
|
+
process.stdout.write(JSON.stringify({
|
|
2031
|
+
hookSpecificOutput: {
|
|
2032
|
+
hookEventName: "PostToolUse",
|
|
2033
|
+
additionalContext: context,
|
|
2034
|
+
},
|
|
2035
|
+
}) + "\n");
|
|
1867
2036
|
}
|
|
1868
2037
|
async function main() {
|
|
1869
2038
|
const args = process.argv.slice(2);
|
|
1870
2039
|
const command = args[0];
|
|
1871
2040
|
const restArgs = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
1872
|
-
|
|
2041
|
+
// Shared flags (--max-rules, --catalog-only) override the loaded config so
|
|
2042
|
+
// every GitHub Action input maps to a real CLI flag. See src/cli-flags.ts.
|
|
2043
|
+
const config = (0, cli_flags_js_1.applyConfigFlags)((0, validate_js_1.loadConfig)(), args);
|
|
1873
2044
|
switch (command) {
|
|
1874
2045
|
// --- Primary commands ---
|
|
1875
2046
|
case "init": {
|
|
@@ -1921,6 +2092,35 @@ async function main() {
|
|
|
1921
2092
|
case "eval":
|
|
1922
2093
|
handleRunScripts("eval", args, restArgs);
|
|
1923
2094
|
break;
|
|
2095
|
+
case "scan": {
|
|
2096
|
+
const dirs = restArgs.length > 0 ? restArgs : ["."];
|
|
2097
|
+
const json = args.includes("--json");
|
|
2098
|
+
if (dirs.length > 1) {
|
|
2099
|
+
// Multiple targets → rank them (the leaderboard engine).
|
|
2100
|
+
const scores = (0, leaderboard_js_1.rankPlugins)(dirs);
|
|
2101
|
+
console.log(json ? JSON.stringify(scores, null, 2) : (0, leaderboard_js_1.formatLeaderboard)(scores));
|
|
2102
|
+
}
|
|
2103
|
+
else {
|
|
2104
|
+
const root = (0, node_path_1.resolve)(dirs[0]);
|
|
2105
|
+
const harnessFlag = args
|
|
2106
|
+
.find((a) => a.startsWith("--harness="))
|
|
2107
|
+
?.slice("--harness=".length);
|
|
2108
|
+
const det = (0, adapter_registry_js_1.detectAdapterResult)(root);
|
|
2109
|
+
const adapter = harnessFlag
|
|
2110
|
+
? (0, adapter_registry_js_1.resolveAdapter)(root, harnessFlag)
|
|
2111
|
+
: det.adapter;
|
|
2112
|
+
const report = (0, scan_js_1.scanPlugin)(dirs[0], adapter.layout);
|
|
2113
|
+
if (!json) {
|
|
2114
|
+
console.log(`Detected harness: ${adapter.name}`);
|
|
2115
|
+
if (!harnessFlag && det.ambiguousWith.length > 0) {
|
|
2116
|
+
console.log(`⚠ repo also matches: ${det.ambiguousWith.join(", ")} — override with --harness=<name>`);
|
|
2117
|
+
}
|
|
2118
|
+
console.log("");
|
|
2119
|
+
}
|
|
2120
|
+
console.log(json ? JSON.stringify(report, null, 2) : (0, scan_js_1.formatScanReport)(report));
|
|
2121
|
+
}
|
|
2122
|
+
break;
|
|
2123
|
+
}
|
|
1924
2124
|
// --- Plumbing ---
|
|
1925
2125
|
case "generate-types":
|
|
1926
2126
|
handleGenerateTypes(args, restArgs);
|