vigiles 2.6.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 +74 -129
- 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.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -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/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
- package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
- 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} +10 -0
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
- 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 +8 -2
- package/dist/claude-code.js +8 -2
- 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 +29 -14
- package/dist/harness-assert.js +37 -10
- 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 +2 -2
- package/dist/linting.js +2 -2
- 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 +3 -3
- package/dist/testing.js +3 -3
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +30 -15
- 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-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/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
- /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
- /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
- /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sync-tool compatibility detector — keep vigiles composable with the rule-sync
|
|
4
|
+
* tools (Ruler, rulesync) instead of fighting them for the same files.
|
|
5
|
+
*
|
|
6
|
+
* Both vigiles and a sync tool want to *write* CLAUDE.md / AGENTS.md. That
|
|
7
|
+
* collision is the whole compatibility problem: vigiles stamps a SHA-256
|
|
8
|
+
* integrity header on line 1 (see `integrity.ts`), but Ruler concatenates its
|
|
9
|
+
* source files into CLAUDE.md (prepending `<!-- Source: … -->`) and rulesync
|
|
10
|
+
* regenerates it — either way the hash silently goes stale. The clean topology
|
|
11
|
+
* is "vigiles upstream": compile into the tool's *source slot* and let the tool
|
|
12
|
+
* distribute (see `research/sync-tool-compatibility.md`).
|
|
13
|
+
*
|
|
14
|
+
* This detector is pure filesystem inspection — the same deterministic-detector
|
|
15
|
+
* shape as `orphans.ts` / `test-coverage.ts`. It reports which tools are present
|
|
16
|
+
* and any target that collides with a file the tool regenerates, so `vigiles
|
|
17
|
+
* audit` can warn before the integrity guarantee is lost.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.detectSyncTools = detectSyncTools;
|
|
21
|
+
exports.detectInstructionMirror = detectInstructionMirror;
|
|
22
|
+
exports.composeCollisions = composeCollisions;
|
|
23
|
+
const node_fs_1 = require("node:fs");
|
|
24
|
+
const node_path_1 = require("node:path");
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Detection
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/** True when `rel` under `root` exists and is a directory. */
|
|
29
|
+
function hasDir(root, rel) {
|
|
30
|
+
const p = (0, node_path_1.join)(root, rel);
|
|
31
|
+
return (0, node_fs_1.existsSync)(p) && (0, node_fs_1.statSync)(p).isDirectory();
|
|
32
|
+
}
|
|
33
|
+
/** True when `rel` under `root` exists as a file. */
|
|
34
|
+
function hasFile(root, rel) {
|
|
35
|
+
const p = (0, node_path_1.join)(root, rel);
|
|
36
|
+
return (0, node_fs_1.existsSync)(p) && (0, node_fs_1.statSync)(p).isFile();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detect the rule-sync tools configured in the repo at `root`. Ruler is keyed
|
|
40
|
+
* on its `.ruler/` source dir or a `ruler.toml`; rulesync on its `.rulesync/`
|
|
41
|
+
* dir. Returns each tool's recommended source slot and the files it owns as
|
|
42
|
+
* distribution output (used to find collisions).
|
|
43
|
+
*/
|
|
44
|
+
function detectSyncTools(root) {
|
|
45
|
+
const base = (0, node_path_1.resolve)(root);
|
|
46
|
+
const tools = [];
|
|
47
|
+
if (hasDir(base, ".ruler") || hasFile(base, "ruler.toml")) {
|
|
48
|
+
tools.push({
|
|
49
|
+
name: "ruler",
|
|
50
|
+
sourceSlot: (0, node_path_1.join)(".ruler", "AGENTS.md"),
|
|
51
|
+
// Ruler's default agent outputs that overlap vigiles instruction targets.
|
|
52
|
+
distributes: ["CLAUDE.md", "AGENTS.md"],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (hasDir(base, ".rulesync")) {
|
|
56
|
+
tools.push({
|
|
57
|
+
name: "rulesync",
|
|
58
|
+
sourceSlot: (0, node_path_1.join)(".rulesync", "rules", "vigiles.md"),
|
|
59
|
+
distributes: ["CLAUDE.md", "AGENTS.md"],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return tools;
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Collision analysis
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/** Normalize a target to its bare filename for comparison (handles paths). */
|
|
68
|
+
function targetName(target) {
|
|
69
|
+
const parts = target.split(/[\\/]/);
|
|
70
|
+
return parts[parts.length - 1];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Detect whether `CLAUDE.md` and `AGENTS.md` at `root` are ONE artifact, not two
|
|
74
|
+
* — a symlink in either direction (`ln -s CLAUDE.md AGENTS.md`) or byte-identical
|
|
75
|
+
* content (a sync tool keeping them in lockstep). Claude Code reads CLAUDE.md
|
|
76
|
+
* only ([anthropics/claude-code#34235]); users bridge to the AGENTS.md tools this
|
|
77
|
+
* way (see `research/sync-tool-compatibility.md` requirement 7). When mirrored,
|
|
78
|
+
* vigiles must treat them as the same file — hash + `require-spec` run once on the
|
|
79
|
+
* real one, and the mirror is never flagged as a second, spec-less instruction
|
|
80
|
+
* file. Returns null when one is absent, or both exist but genuinely differ.
|
|
81
|
+
*/
|
|
82
|
+
function detectInstructionMirror(root) {
|
|
83
|
+
const claude = (0, node_path_1.join)(root, "CLAUDE.md");
|
|
84
|
+
const agents = (0, node_path_1.join)(root, "AGENTS.md");
|
|
85
|
+
if (!(0, node_fs_1.existsSync)(claude) || !(0, node_fs_1.existsSync)(agents))
|
|
86
|
+
return null;
|
|
87
|
+
// Symlink either direction (or both → a third file) resolves to one realpath.
|
|
88
|
+
try {
|
|
89
|
+
if ((0, node_fs_1.realpathSync)(claude) === (0, node_fs_1.realpathSync)(agents)) {
|
|
90
|
+
const claudeIsLink = (0, node_fs_1.lstatSync)(claude).isSymbolicLink();
|
|
91
|
+
const link = claudeIsLink ? "CLAUDE.md" : "AGENTS.md";
|
|
92
|
+
const realTarget = claudeIsLink ? "AGENTS.md" : "CLAUDE.md";
|
|
93
|
+
return {
|
|
94
|
+
files: ["CLAUDE.md", "AGENTS.md"],
|
|
95
|
+
kind: "symlink",
|
|
96
|
+
link,
|
|
97
|
+
realTarget,
|
|
98
|
+
reason: `${link} is a symlink resolving to the same file as ${realTarget} — one instruction artifact bridged to the AGENTS.md tools; verify and hash the real file once, never flag the mirror as spec-less.`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* realpath failed (race / broken link) → fall through to content compare */
|
|
104
|
+
}
|
|
105
|
+
// Byte-identical content: kept in sync by rulesync/Ruler rather than symlinked.
|
|
106
|
+
try {
|
|
107
|
+
if ((0, node_fs_1.readFileSync)(claude).equals((0, node_fs_1.readFileSync)(agents))) {
|
|
108
|
+
return {
|
|
109
|
+
files: ["CLAUDE.md", "AGENTS.md"],
|
|
110
|
+
kind: "identical-content",
|
|
111
|
+
reason: `CLAUDE.md and AGENTS.md are byte-identical — kept in sync (rulesync/Ruler); treat as one artifact and stamp the integrity hash only on the compile source slot.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* unreadable → not a detectable mirror */
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Given the repo `root` and the spec's compile `targets`, report every case
|
|
122
|
+
* where a target is also a file a detected sync tool regenerates. Each
|
|
123
|
+
* collision carries the source slot to compile into instead — the actionable
|
|
124
|
+
* fix that preserves the integrity hash (the tool distributes from there).
|
|
125
|
+
*
|
|
126
|
+
* No detected tool, or no overlapping target, yields an empty list.
|
|
127
|
+
*/
|
|
128
|
+
function composeCollisions(root, targets) {
|
|
129
|
+
const tools = detectSyncTools(root);
|
|
130
|
+
const collisions = [];
|
|
131
|
+
for (const tool of tools) {
|
|
132
|
+
for (const target of targets) {
|
|
133
|
+
if (!tool.distributes.includes(targetName(target)))
|
|
134
|
+
continue;
|
|
135
|
+
collisions.push({
|
|
136
|
+
tool: tool.name,
|
|
137
|
+
target,
|
|
138
|
+
redirectTo: tool.sourceSlot,
|
|
139
|
+
reason: `${tool.name} regenerates ${targetName(target)}, which would overwrite vigiles output and stale its integrity hash — compile into ${tool.sourceSlot} and let ${tool.name} distribute.`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return collisions;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessDialect — the format/dialect PORT (see
|
|
3
|
+
* `research/code-adapter-architecture.md`, the format axis).
|
|
4
|
+
*
|
|
5
|
+
* The compiler needs a handful of harness-specific facts to verify and render an
|
|
6
|
+
* instruction file: the built-in tool catalog a subagent may declare, the tools
|
|
7
|
+
* the platform never exposes, the shape of an MCP tool reference, the hook
|
|
8
|
+
* events the harness fires, the instruction-file targets it reads, and the env
|
|
9
|
+
* token expanded to the plugin root. Those used to be hard-coded literals inside
|
|
10
|
+
* `compile.ts`; here they are a single named value behind an interface.
|
|
11
|
+
*
|
|
12
|
+
* Claude Code is the reference dialect today (`claudeCodeDialect`, defined in its
|
|
13
|
+
* adapter at `src/adapters/claude-code/dialect.ts`). A second harness (Codex
|
|
14
|
+
* likely next) is added by defining a sibling `HarnessDialect` — e.g.
|
|
15
|
+
* `src/adapters/codex/dialect.ts` exporting `codexDialect` — and injecting it
|
|
16
|
+
* (`compileAgent(spec, { dialect })`). The compiler reads these from the injected
|
|
17
|
+
* dialect, so the core never hard-codes — nor even defines — one harness's
|
|
18
|
+
* vocabulary: the concrete dialects live in the adapters, only this interface
|
|
19
|
+
* lives in the core. That is the format axis of the hexagonal boundary.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Which SKILL.md frontmatter keys a harness understands — see
|
|
23
|
+
* `HarnessDialect.skillFrontmatter`.
|
|
24
|
+
*/
|
|
25
|
+
export type SkillFrontmatterProfile = "claude-code" | "minimal";
|
|
26
|
+
export interface HarnessDialect {
|
|
27
|
+
/** Stable identifier, e.g. "claude-code". */
|
|
28
|
+
readonly name: string;
|
|
29
|
+
/** Built-in tools a subagent may list in its `tools:` contract. */
|
|
30
|
+
readonly builtinAgentTools: readonly string[];
|
|
31
|
+
/** Tools the platform never exposes to a subagent (a listed one is dead). */
|
|
32
|
+
readonly neverAvailableTools: readonly string[];
|
|
33
|
+
/** Matches an MCP tool reference, e.g. `mcp__server__tool`. */
|
|
34
|
+
readonly mcpToolPattern: RegExp;
|
|
35
|
+
/** Hook event names the harness fires. */
|
|
36
|
+
readonly hookEvents: readonly string[];
|
|
37
|
+
/** Instruction-file targets the harness reads (also the h1 heading). */
|
|
38
|
+
readonly instructionTargets: readonly string[];
|
|
39
|
+
/** The env token expanded to the plugin root in hook commands. */
|
|
40
|
+
readonly pluginRootToken: string;
|
|
41
|
+
/**
|
|
42
|
+
* Which SKILL.md frontmatter keys this harness understands — the profile the
|
|
43
|
+
* compiler renders under:
|
|
44
|
+
* - `"claude-code"` — the full Claude Code set (name, description, plus the
|
|
45
|
+
* CC-only keys: disable-model-invocation, argument-hint, …).
|
|
46
|
+
* - `"minimal"` — name + description ONLY (the cross-tool SKILL.md shape Codex
|
|
47
|
+
* and OpenCode read; CC-only keys are omitted because they'd be inert noise).
|
|
48
|
+
*/
|
|
49
|
+
readonly skillFrontmatter: SkillFrontmatterProfile;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HarnessTestDriver — the seam that makes the pillar-2 deterministic runner
|
|
3
|
+
* (`runHarnessTest`) adapter-driven instead of hard-wired to Claude Code.
|
|
4
|
+
*
|
|
5
|
+
* Each harness (Claude Code, Codex, …) brings a driver that knows how to: build
|
|
6
|
+
* the non-interactive argv for its binary, start its scripted mock model, and
|
|
7
|
+
* parse its stdout into the unified `Trace` fields. The runner stays
|
|
8
|
+
* harness-agnostic — it spawns `driver.runtime.agentBinary`, points it at the
|
|
9
|
+
* mock via `driver.runtime.wireMock(url)`, and assembles the result through
|
|
10
|
+
* `driver.parseRun`. A `HarnessAdapter` carries its driver as
|
|
11
|
+
* `harnessTestDriver`, so the runner never imports a sibling adapter.
|
|
12
|
+
*
|
|
13
|
+
* The shared trace shapes (`ModelTurn`/`ModelRequest`/`ToolCall`/`HookFire`)
|
|
14
|
+
* live here in core so BOTH the adapters and the runner can reference them
|
|
15
|
+
* without a cross-adapter import. `vigiles/claude-code` re-exports
|
|
16
|
+
* `ModelTurn`/`ModelRequest` for back-compat.
|
|
17
|
+
*/
|
|
18
|
+
import type { HarnessRuntime } from "./runtime.js";
|
|
19
|
+
/**
|
|
20
|
+
* One scripted assistant turn: a final text answer, or a tool call. The common
|
|
21
|
+
* shape both harness mocks consume — the Anthropic Messages mock
|
|
22
|
+
* (`src/adapters/claude-code/mock-model.ts`) and the OpenAI Responses mock
|
|
23
|
+
* (`src/adapters/codex/mock-model.ts`, which uses only `text`).
|
|
24
|
+
*/
|
|
25
|
+
export interface ModelTurn {
|
|
26
|
+
/** Final text answer (stops the turn). */
|
|
27
|
+
readonly text?: string;
|
|
28
|
+
/** A tool to invoke, e.g. "Bash" | "Write" | "Edit". */
|
|
29
|
+
readonly tool?: string;
|
|
30
|
+
/** The tool input, e.g. `{ file_path, content }` or `{ command }`. */
|
|
31
|
+
readonly input?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One model request the scripted mock received, flattened to text for
|
|
35
|
+
* assertions — the seam that lets a harness test prove what reached the model
|
|
36
|
+
* (a SessionStart hook's injected context, a slash command's expansion), not
|
|
37
|
+
* just that a hook fired.
|
|
38
|
+
*/
|
|
39
|
+
export interface ModelRequest {
|
|
40
|
+
/** The system prompt, flattened to text (string or text-block array). */
|
|
41
|
+
readonly system: string;
|
|
42
|
+
/** The conversation messages, each flattened to `{ role, text }`. */
|
|
43
|
+
readonly messages: readonly {
|
|
44
|
+
readonly role: string;
|
|
45
|
+
readonly text: string;
|
|
46
|
+
}[];
|
|
47
|
+
}
|
|
48
|
+
/** A tool the agent invoked, paired with its result (transcript mode only). */
|
|
49
|
+
export interface ToolCall {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly input: unknown;
|
|
52
|
+
/** The tool_result text ("" if none / not captured). */
|
|
53
|
+
readonly resultText: string;
|
|
54
|
+
/** Whether the tool_result came back flagged as an error. */
|
|
55
|
+
readonly isError: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A hook invocation observed during the run, recorded (not inferred) from the
|
|
59
|
+
* harness's stream events — so a test can assert which hook fired and whether
|
|
60
|
+
* it blocked.
|
|
61
|
+
*/
|
|
62
|
+
export interface HookFire {
|
|
63
|
+
/** The hook label, e.g. `"PreToolUse:Edit"` (`Event:Matcher`). */
|
|
64
|
+
readonly name: string;
|
|
65
|
+
/** The hook event, e.g. `"PreToolUse"`, `"PostToolUse"`, `"Stop"`. */
|
|
66
|
+
readonly event: string;
|
|
67
|
+
/** The hook process exit code (2 = block), or undefined if not reported. */
|
|
68
|
+
readonly exitCode: number | undefined;
|
|
69
|
+
/** Whether the hook blocked / errored (exit ≠ 0 or outcome "error"). */
|
|
70
|
+
readonly blocked: boolean;
|
|
71
|
+
/** What the hook printed (its block reason / diagnostic), or "". */
|
|
72
|
+
readonly output: string;
|
|
73
|
+
}
|
|
74
|
+
/** A running scripted mock model: the URL to point the binary at + the record. */
|
|
75
|
+
export interface HarnessMockHandle {
|
|
76
|
+
/** Base URL the spawned binary is pointed at. */
|
|
77
|
+
readonly url: string;
|
|
78
|
+
/** Every request the mock received, flattened for assertions, in order. */
|
|
79
|
+
readonly requests: readonly ModelRequest[];
|
|
80
|
+
/** Number of model turns served so far. */
|
|
81
|
+
readonly count: number;
|
|
82
|
+
/** Stop the mock server. */
|
|
83
|
+
close(): void | Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/** The facts a driver needs to build the non-interactive argv for one run. */
|
|
86
|
+
export interface HarnessDriverContext {
|
|
87
|
+
/** The user prompt. */
|
|
88
|
+
readonly prompt: string;
|
|
89
|
+
/** The temp working dir the binary runs in. */
|
|
90
|
+
readonly cwd: string;
|
|
91
|
+
/** Whether a settings file was written (CC: pass `--settings`). */
|
|
92
|
+
readonly hasSettings: boolean;
|
|
93
|
+
/** Tools the agent may use. */
|
|
94
|
+
readonly tools: readonly string[];
|
|
95
|
+
/** Capture the full event transcript instead of just the final result. */
|
|
96
|
+
readonly transcript: boolean;
|
|
97
|
+
/** Path to a plugin dir to install natively, if any. */
|
|
98
|
+
readonly pluginDir?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The mock-wiring args from `runtime.wireMock(url).args` — the flags that
|
|
101
|
+
* point the binary at the mock model. The DRIVER inserts them at the correct
|
|
102
|
+
* argv position (for Codex they MUST follow `exec`; for env-only harnesses
|
|
103
|
+
* like Claude Code this is empty). Passed in (not prepended by the runner)
|
|
104
|
+
* because only the driver knows its argv structure.
|
|
105
|
+
*/
|
|
106
|
+
readonly mockArgs: readonly string[];
|
|
107
|
+
}
|
|
108
|
+
/** A harness's stdout parsed into the unified `Trace` fields. */
|
|
109
|
+
export interface ParsedRun {
|
|
110
|
+
/** Number of model turns, if the stdout reports it (else taken from the mock). */
|
|
111
|
+
readonly turns?: number;
|
|
112
|
+
readonly toolCalls: readonly ToolCall[];
|
|
113
|
+
readonly hooks: readonly HookFire[];
|
|
114
|
+
/** The agent's final answer text, or "". */
|
|
115
|
+
readonly output: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* What a harness brings so `runHarnessTest` can drive it. Implemented in each
|
|
119
|
+
* adapter (`claudeCodeDriver`, `codexDriver`) and carried on the
|
|
120
|
+
* `HarnessAdapter` as `harnessTestDriver`.
|
|
121
|
+
*/
|
|
122
|
+
export interface HarnessTestDriver {
|
|
123
|
+
/** The transport (binary to spawn + how to reach the mock via `wireMock`). */
|
|
124
|
+
readonly runtime: HarnessRuntime;
|
|
125
|
+
/** The non-interactive argv for one run. Pure, so it's unit-tested. */
|
|
126
|
+
buildArgs(ctx: HarnessDriverContext): readonly string[];
|
|
127
|
+
/** Start this harness's scripted mock for the given turn script. */
|
|
128
|
+
startMock(script: readonly ModelTurn[]): Promise<HarnessMockHandle>;
|
|
129
|
+
/** Parse the harness's stdout into the unified trace fields. */
|
|
130
|
+
parseRun(stdout: string): ParsedRun;
|
|
131
|
+
/** Whether the agent binary is available on PATH. */
|
|
132
|
+
available(): boolean;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=harness-driver.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HookProtocol — the hook-wire PORT (transport axis). How a harness signals that
|
|
3
|
+
* a hook blocked/denied a tool call: the block exit code, the decision values
|
|
4
|
+
* that mean "deny", and the env vars a synthesized event carries. The research
|
|
5
|
+
* (research/harness-landscape.md) found Claude Code and Codex hooks are nearly
|
|
6
|
+
* identical at the wire level (both: JSON on stdin, `permissionDecision: "deny"`
|
|
7
|
+
* / `decision: "block"` / exit 2) — so this descriptor is deliberately thin, and
|
|
8
|
+
* the fact that a second harness needs almost the same values IS the finding.
|
|
9
|
+
* What varies more (config format JSON-vs-TOML, the plugin-root token, the event
|
|
10
|
+
* names) lives in PluginLayout / HarnessDialect, not here.
|
|
11
|
+
*
|
|
12
|
+
* The Claude Code implementation is `claudeCodeHookProtocol` in
|
|
13
|
+
* `src/adapters/claude-code/hook-protocol.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export interface HookProtocol {
|
|
16
|
+
/** Stable identifier, e.g. "claude-code". */
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Exit code a hook process uses to block/deny a tool call (Claude Code: 2). */
|
|
19
|
+
readonly blockExitCode: number;
|
|
20
|
+
/** decision / permissionDecision values that mean "deny" the tool call. */
|
|
21
|
+
readonly denyDecisionValues: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* Env vars a synthesized hook event carries beyond the JSON on stdin (Claude
|
|
24
|
+
* Code passes the event on stdin only; Codex adds session_id/cwd/PLUGIN_ROOT/…).
|
|
25
|
+
*/
|
|
26
|
+
readonly eventEnvVars: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=hook-protocol.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginLayout — the plugin/repo LAYOUT port (hexagonal format axis, the
|
|
3
|
+
* filesystem half). A harness's plugin layout is where its instruction file,
|
|
4
|
+
* skills, subagents, commands, hooks and settings live on disk, plus the env
|
|
5
|
+
* token expanded to the plugin root. The loader (`loadPlugin`) reads those from
|
|
6
|
+
* this descriptor instead of hard-coding Claude Code's `.claude-plugin/` /
|
|
7
|
+
* `.claude/` conventions, so a second harness (Codex) supplies its own
|
|
8
|
+
* `PluginLayout` and reuses the same loader.
|
|
9
|
+
*
|
|
10
|
+
* Paths are repo-relative (POSIX-style, `join`-friendly). The Claude Code
|
|
11
|
+
* implementation is `claudeCodeLayout` in `src/adapters/claude-code/layout.ts`.
|
|
12
|
+
*/
|
|
13
|
+
export interface PluginLayout {
|
|
14
|
+
/** Stable identifier, e.g. "claude-code". */
|
|
15
|
+
readonly name: string;
|
|
16
|
+
/** Plugin manifest, e.g. `.claude-plugin/plugin.json`. */
|
|
17
|
+
readonly manifestPath: string;
|
|
18
|
+
/** Convention path for a standalone hooks file, e.g. `hooks/hooks.json`. */
|
|
19
|
+
readonly hooksConventionPath: string;
|
|
20
|
+
/** Repo settings carrying hooks, e.g. `.claude/settings.json` or `.codex/config.toml`. */
|
|
21
|
+
readonly settingsPath: string;
|
|
22
|
+
/**
|
|
23
|
+
* How the settings file is encoded — `"json"` (Claude Code's settings.json) or
|
|
24
|
+
* `"toml"` (Codex's `config.toml` `[hooks]`). The loader dispatches a parser on
|
|
25
|
+
* it, so a TOML-configured harness's hooks aren't silently read as zero.
|
|
26
|
+
*/
|
|
27
|
+
readonly settingsFormat: "json" | "toml";
|
|
28
|
+
/** Top-level instruction file, e.g. `CLAUDE.md`. */
|
|
29
|
+
readonly instructionFile: string;
|
|
30
|
+
/** Surface dirs materialized into the sandbox, e.g. skills/agents/commands. */
|
|
31
|
+
readonly surfaceDirs: readonly string[];
|
|
32
|
+
/** Dir the surfaces are materialized under, e.g. `.claude`. */
|
|
33
|
+
readonly materializeRoot: string;
|
|
34
|
+
/** Env token expanded to the plugin's absolute root in hook commands. */
|
|
35
|
+
readonly pluginRootToken: string;
|
|
36
|
+
/** Standalone MCP config file, e.g. `.mcp.json`. */
|
|
37
|
+
readonly mcpConfigFile: string;
|
|
38
|
+
/** Manifest key declaring MCP servers, e.g. `mcpServers`. */
|
|
39
|
+
readonly mcpManifestKey: string;
|
|
40
|
+
/** Dirs scanned for dangling intra-plugin file references. */
|
|
41
|
+
readonly intraRefDirs: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelMock — the model-mock PORT (transport axis). A harness test tier runs the
|
|
3
|
+
* real agent against a fake, scripted model server so it needs no API key and is
|
|
4
|
+
* deterministic. This descriptor captures what the mock's WIRE FORMAT is: the API
|
|
5
|
+
* dialect it speaks, the endpoint that consumes a scripted turn, and the optional
|
|
6
|
+
* token-count endpoint a client probes. The research (research/harness-landscape.md)
|
|
7
|
+
* found the two concrete formats differ sharply — Claude Code speaks Anthropic
|
|
8
|
+
* **Messages** SSE at `/v1/messages`; Codex speaks OpenAI **Responses** SSE at
|
|
9
|
+
* `/v1/responses` — which is exactly the second implementation that makes this
|
|
10
|
+
* port designable.
|
|
11
|
+
*
|
|
12
|
+
* The HTTP server + per-event flush mechanics live in the harness's mock module
|
|
13
|
+
* (`startMock` for Claude Code); a second harness implements the same contract
|
|
14
|
+
* with its own renderer. The Claude Code descriptor is `claudeCodeModelMock` in
|
|
15
|
+
* `src/adapters/claude-code/model-mock.ts`.
|
|
16
|
+
*/
|
|
17
|
+
export interface ModelMock {
|
|
18
|
+
/** Stable identifier, e.g. "claude-code". */
|
|
19
|
+
readonly name: string;
|
|
20
|
+
/** The model API wire format, e.g. "anthropic-messages" or "openai-responses". */
|
|
21
|
+
readonly wireApi: string;
|
|
22
|
+
/** URL substring identifying a turn-consuming model request (e.g. "/v1/messages"). */
|
|
23
|
+
readonly modelEndpoint: string;
|
|
24
|
+
/** Token-count endpoint substring a client probes (e.g. "count_tokens"); omit if none. */
|
|
25
|
+
readonly countTokensEndpoint?: string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=model-mock.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RuleSeverity } from "./types.js";
|
|
2
|
+
/** An inline code span with its 1-based source line. */
|
|
3
|
+
export interface Span {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly line: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Extract inline code spans, skipping fenced code blocks (R1). Returns each
|
|
9
|
+
* span's trimmed text and 1-based line.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inlineSpans(markdown: string): Span[];
|
|
12
|
+
/** A parsed file-qualified reference. */
|
|
13
|
+
export interface SymbolRef {
|
|
14
|
+
readonly file: string;
|
|
15
|
+
readonly symbol: string;
|
|
16
|
+
readonly line: number;
|
|
17
|
+
}
|
|
18
|
+
/** A reference that failed verification. */
|
|
19
|
+
export interface SymbolRefError extends SymbolRef {
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
/** Extract the `vigiles:symbol` references from a markdown file. */
|
|
23
|
+
export declare function symbolRefs(markdown: string): SymbolRef[];
|
|
24
|
+
/**
|
|
25
|
+
* Verify the file-qualified symbol references in a markdown file: the named
|
|
26
|
+
* file must exist and define the named symbol. `basePath` is the directory the
|
|
27
|
+
* paths resolve against (the instruction file's own directory).
|
|
28
|
+
*/
|
|
29
|
+
export declare function verifySymbolRefs(markdown: string, basePath: string): SymbolRefError[];
|
|
30
|
+
/**
|
|
31
|
+
* Whether a span is a **linter-rule reference** that ought to be marked
|
|
32
|
+
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
|
|
33
|
+
* rule exists AND is enabled. High-signal only: a slash-scoped name with no file
|
|
34
|
+
* extension. A function-call form `` `foo(args)` `` is reduced to its callee.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately NOT flagged (too noisy / undecidable): bare identifiers
|
|
37
|
+
* (`runHook`, `MAX_RETRIES` — usually API prose, mark opt-in via an explicit
|
|
38
|
+
* `vigiles:symbol`) and file paths (`src/x.ts`, `docs/y.md` — file refs).
|
|
39
|
+
*/
|
|
40
|
+
export declare function isCodeShaped(text: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Unmarked linter-rule references — the spans the refs-hook nudges the agent to
|
|
43
|
+
* mark with `enforce()` / `<!-- vigiles:enforce ... -->` (or opt out of with
|
|
44
|
+
* `<!-- vigiles:ignore -->` / `<!-- vigiles:ignore-file -->`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function unmarkedCodeRefs(markdown: string): Span[];
|
|
47
|
+
/**
|
|
48
|
+
* The reference issues in an instruction file, as one human-readable line each:
|
|
49
|
+
* a `vigiles:symbol` mark whose symbol is missing, plus every unmarked
|
|
50
|
+
* code-shaped span that ought to be a mark. The shared detector behind both the
|
|
51
|
+
* `vigiles refs` CLI and the PostToolUse refs-hook.
|
|
52
|
+
*/
|
|
53
|
+
export declare function collectRefIssues(markdown: string, basePath: string): string[];
|
|
54
|
+
/** What the refs-hook should do given the issue count and configured severity. */
|
|
55
|
+
export type RefsHookAction = "ok" | "nudge" | "block";
|
|
56
|
+
/**
|
|
57
|
+
* Map detected issues + the `unmarked-refs` rule severity to a hook action:
|
|
58
|
+
* no issues or `false` → ok, `"error"` → block (exit 2), anything else
|
|
59
|
+
* (`"warn"`, the default) → a non-blocking nudge.
|
|
60
|
+
*/
|
|
61
|
+
export declare function refsHookAction(issueCount: number, severity: RuleSeverity): RefsHookAction;
|
|
62
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -5,6 +5,8 @@ exports.symbolRefs = symbolRefs;
|
|
|
5
5
|
exports.verifySymbolRefs = verifySymbolRefs;
|
|
6
6
|
exports.isCodeShaped = isCodeShaped;
|
|
7
7
|
exports.unmarkedCodeRefs = unmarkedCodeRefs;
|
|
8
|
+
exports.collectRefIssues = collectRefIssues;
|
|
9
|
+
exports.refsHookAction = refsHookAction;
|
|
8
10
|
/**
|
|
9
11
|
* vigiles — file-qualified symbol reference verification (variant A).
|
|
10
12
|
*
|
|
@@ -97,35 +99,32 @@ function verifySymbolRefs(markdown, basePath) {
|
|
|
97
99
|
// ---------------------------------------------------------------------------
|
|
98
100
|
// Enforcement: force code references to carry the file-qualified mark
|
|
99
101
|
// ---------------------------------------------------------------------------
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
const HAS_EXT = /\.[A-Za-z0-9]+$/; // a file extension → a path/filename, not a rule
|
|
103
|
+
// A linter-rule reference: a slash-scoped `linter/rule` (optionally `@scoped`)
|
|
104
|
+
// with NO file extension — `eslint/no-console`, `@typescript-eslint/no-x`,
|
|
105
|
+
// `boundaries/dependencies`. Deliberately NOT a path (`src/x.ts`) or a bare
|
|
106
|
+
// identifier (`runHook`): those are excluded.
|
|
107
|
+
const RULE_SHAPED = /^@?[a-z][\w-]*(?:\/[\w-]+)+$/;
|
|
103
108
|
const IGNORE_FILE = /<!--\s*vigiles:ignore-file\s*-->/;
|
|
104
109
|
const IGNORE_LINE = /<!--\s*vigiles:ignore\s*-->/;
|
|
105
110
|
/**
|
|
106
|
-
* Whether a span
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
+
* Whether a span is a **linter-rule reference** that ought to be marked
|
|
112
|
+
* (`enforce()` / inline `<!-- vigiles:enforce -->`) so the audit can verify the
|
|
113
|
+
* rule exists AND is enabled. High-signal only: a slash-scoped name with no file
|
|
114
|
+
* extension. A function-call form `` `foo(args)` `` is reduced to its callee.
|
|
115
|
+
*
|
|
116
|
+
* Deliberately NOT flagged (too noisy / undecidable): bare identifiers
|
|
117
|
+
* (`runHook`, `MAX_RETRIES` — usually API prose, mark opt-in via an explicit
|
|
118
|
+
* `vigiles:symbol`) and file paths (`src/x.ts`, `docs/y.md` — file refs).
|
|
111
119
|
*/
|
|
112
120
|
function isCodeShaped(text) {
|
|
113
121
|
const callee = text.replace(/\s*\([^)]*\)\s*$/, ""); // `foo(args)` → `foo`
|
|
114
|
-
|
|
115
|
-
return true;
|
|
116
|
-
if (!PLAIN_ID.test(callee))
|
|
117
|
-
return false;
|
|
118
|
-
const hasUnderscore = callee.includes("_");
|
|
119
|
-
const hasCamel = /[a-z][A-Z]/.test(callee);
|
|
120
|
-
const isPascal = /^[A-Z][a-z]/.test(callee);
|
|
121
|
-
const isScreaming = /^[A-Z][A-Z0-9_]+$/.test(callee);
|
|
122
|
-
return hasUnderscore || hasCamel || isPascal || isScreaming;
|
|
122
|
+
return RULE_SHAPED.test(callee) && !HAS_EXT.test(callee);
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* for the whole file).
|
|
125
|
+
* Unmarked linter-rule references — the spans the refs-hook nudges the agent to
|
|
126
|
+
* mark with `enforce()` / `<!-- vigiles:enforce ... -->` (or opt out of with
|
|
127
|
+
* `<!-- vigiles:ignore -->` / `<!-- vigiles:ignore-file -->`).
|
|
129
128
|
*/
|
|
130
129
|
function unmarkedCodeRefs(markdown) {
|
|
131
130
|
if (IGNORE_FILE.test(markdown))
|
|
@@ -136,9 +135,36 @@ function unmarkedCodeRefs(markdown) {
|
|
|
136
135
|
return false;
|
|
137
136
|
if (SYMBOL_MARK.test(span.text))
|
|
138
137
|
return false; // already a vigiles:symbol mark
|
|
139
|
-
if (PATH_LIKE.test(span.text))
|
|
140
|
-
return false; // a path/filename → file ref
|
|
141
138
|
return isCodeShaped(span.text);
|
|
142
139
|
});
|
|
143
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* The reference issues in an instruction file, as one human-readable line each:
|
|
143
|
+
* a `vigiles:symbol` mark whose symbol is missing, plus every unmarked
|
|
144
|
+
* code-shaped span that ought to be a mark. The shared detector behind both the
|
|
145
|
+
* `vigiles refs` CLI and the PostToolUse refs-hook.
|
|
146
|
+
*/
|
|
147
|
+
function collectRefIssues(markdown, basePath) {
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const b of verifySymbolRefs(markdown, basePath)) {
|
|
150
|
+
out.push(`line ${String(b.line)}: ${b.reason}`);
|
|
151
|
+
}
|
|
152
|
+
for (const u of unmarkedCodeRefs(markdown)) {
|
|
153
|
+
out.push(`line ${String(u.line)}: \`${u.text}\` is an unmarked linter-rule ` +
|
|
154
|
+
`reference — mark it as \`enforce("${u.text}")\` (typed spec) or ` +
|
|
155
|
+
`\`<!-- vigiles:enforce ${u.text} -->\` (markdown) so audit can verify ` +
|
|
156
|
+
`it exists and is enabled, or add <!-- vigiles:ignore --> if it is prose`);
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Map detected issues + the `unmarked-refs` rule severity to a hook action:
|
|
162
|
+
* no issues or `false` → ok, `"error"` → block (exit 2), anything else
|
|
163
|
+
* (`"warn"`, the default) → a non-blocking nudge.
|
|
164
|
+
*/
|
|
165
|
+
function refsHookAction(issueCount, severity) {
|
|
166
|
+
if (issueCount === 0 || severity === false)
|
|
167
|
+
return "ok";
|
|
168
|
+
return severity === "error" ? "block" : "nudge";
|
|
169
|
+
}
|
|
144
170
|
//# sourceMappingURL=refs.js.map
|