vigiles 29.1.0 → 30.0.1
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/dist/adapter-conformance.d.ts +1 -1
- package/dist/adapter-conformance.js +106 -25
- package/dist/adapter-registry.d.ts +61 -14
- package/dist/adapter-registry.js +78 -10
- package/dist/adapter.d.ts +23 -2
- package/dist/adapter.js +13 -1
- package/dist/adapters/claude-code/adapter.d.ts +32 -2
- package/dist/adapters/claude-code/adapter.js +44 -23
- package/dist/adapters/claude-code/agent-runtime.js +3 -1
- package/dist/adapters/claude-code/dialect.js +87 -21
- package/dist/adapters/claude-code/effect-region.js +3 -1
- package/dist/adapters/claude-code/hook-protocol.js +16 -0
- package/dist/adapters/claude-code/instruction-chain.d.ts +25 -0
- package/dist/adapters/claude-code/instruction-chain.js +626 -0
- package/dist/adapters/claude-code/layout.d.ts +2 -2
- package/dist/adapters/claude-code/layout.js +42 -8
- package/dist/adapters/claude-code/model-access.d.ts +41 -0
- package/dist/adapters/claude-code/model-access.js +46 -0
- package/dist/adapters/claude-code/skill-reachability.d.ts +125 -0
- package/dist/adapters/claude-code/skill-reachability.js +111 -0
- package/dist/adapters/claude-code/skill-runtime.js +3 -1
- package/dist/adapters/codex/adapter.d.ts +39 -2
- package/dist/adapters/codex/adapter.js +29 -29
- package/dist/adapters/codex/dialect.js +11 -6
- package/dist/adapters/codex/eval.d.ts +10 -0
- package/dist/adapters/codex/eval.js +48 -1
- package/dist/adapters/codex/hook-protocol.d.ts +2 -1
- package/dist/adapters/codex/hook-protocol.js +10 -0
- package/dist/adapters/codex/instruction-chain.d.ts +40 -0
- package/dist/adapters/codex/instruction-chain.js +105 -0
- package/dist/adapters/codex/layout.d.ts +1 -1
- package/dist/adapters/codex/layout.js +41 -14
- package/dist/adapters/opencode/adapter.d.ts +33 -2
- package/dist/adapters/opencode/adapter.js +36 -36
- package/dist/adapters/opencode/dialect.js +2 -2
- package/dist/adapters/opencode/instruction-chain.d.ts +37 -0
- package/dist/adapters/opencode/instruction-chain.js +70 -0
- package/dist/adapters/opencode/layout.d.ts +19 -0
- package/dist/adapters/opencode/layout.js +34 -15
- package/dist/adoptability.d.ts +31 -1
- package/dist/adoptability.js +57 -0
- package/dist/cli-main.js +185 -102
- package/dist/core/adapter.d.ts +213 -61
- package/dist/core/compile.d.ts +2 -2
- package/dist/core/compile.js +57 -46
- package/dist/core/compose.d.ts +5 -3
- package/dist/core/compose.js +5 -3
- package/dist/core/config-schema.d.ts +14 -2
- package/dist/core/config-schema.js +20 -7
- package/dist/core/dialect.d.ts +54 -12
- package/dist/core/dialect.js +56 -0
- package/dist/core/eval-driver.d.ts +194 -0
- package/dist/core/eval-driver.js +3 -0
- package/dist/core/frontmatter-read.d.ts +10 -0
- package/dist/core/frontmatter-read.js +30 -3
- package/dist/core/guards.js +3 -1
- package/dist/core/hook-program.d.ts +27 -2
- package/dist/core/hook-program.js +29 -24
- package/dist/core/hook-protocol.d.ts +54 -0
- package/dist/core/install-reader.d.ts +18 -0
- package/dist/core/install-reader.js +88 -0
- package/dist/core/instruction-chain.d.ts +444 -0
- package/dist/core/instruction-chain.js +292 -0
- package/dist/core/instruction-weight.d.ts +96 -14
- package/dist/core/instruction-weight.js +65 -30
- package/dist/core/layout.d.ts +220 -33
- package/dist/core/layout.js +115 -1
- package/dist/core/lethal-trifecta.d.ts +12 -7
- package/dist/core/lethal-trifecta.js +13 -13
- package/dist/core/live-driver.d.ts +137 -0
- package/dist/core/live-driver.js +14 -0
- package/dist/core/markdown.d.ts +23 -0
- package/dist/core/markdown.js +77 -28
- package/dist/core/orphans.js +9 -7
- package/dist/core/settings-codec.d.ts +17 -0
- package/dist/core/settings-codec.js +56 -0
- package/dist/core/surface-discovery.d.ts +2 -2
- package/dist/core/surface-discovery.js +24 -12
- package/dist/core/surface-scopes.d.ts +26 -6
- package/dist/core/surface-scopes.js +52 -11
- package/dist/core/validate.js +16 -3
- package/dist/coverage-artifact.d.ts +3 -2
- package/dist/coverage-artifact.js +6 -5
- package/dist/eval-cache.d.ts +6 -1
- package/dist/eval-cache.js +11 -1
- package/dist/eval.d.ts +16 -108
- package/dist/eval.js +36 -2
- package/dist/harness-test.d.ts +3 -63
- package/dist/hook-install.d.ts +12 -1
- package/dist/hook-install.js +12 -1
- package/dist/hook-runtime.js +4 -2
- package/dist/hook-state-store.js +3 -1
- package/dist/local-files-tracked.d.ts +17 -0
- package/dist/local-files-tracked.js +70 -0
- package/dist/local-files.d.ts +62 -0
- package/dist/local-files.js +183 -0
- package/dist/observe.d.ts +3 -2
- package/dist/observe.js +7 -6
- package/dist/plugin-loader.d.ts +1 -1
- package/dist/plugin-loader.js +43 -36
- package/dist/scan-behavioral.d.ts +34 -25
- package/dist/scan-behavioral.js +122 -58
- package/dist/scan-core.js +37 -18
- package/dist/scan-files.d.ts +1 -1
- package/dist/scan-files.js +53 -33
- package/dist/scan-trigger-suggest.d.ts +0 -21
- package/dist/scan-trigger-suggest.js +0 -23
- package/dist/scan.d.ts +4 -4
- package/dist/scan.js +120 -84
- package/dist/skill-harness.d.ts +21 -5
- package/dist/skill-harness.js +29 -11
- package/dist/surface-discovery-fs.d.ts +2 -0
- package/dist/surface-discovery-fs.js +108 -6
- package/dist/test-coverage-files.js +24 -17
- package/dist/test-coverage.d.ts +9 -3
- package/dist/test-coverage.js +32 -22
- package/dist/verify-plugin-guards.js +1 -1
- package/package.json +1 -1
- package/dist/skill-reachability.d.ts +0 -68
- package/dist/skill-reachability.js +0 -205
- /package/dist/{dialect-drift.d.ts → adapters/claude-code/dialect-drift.d.ts} +0 -0
- /package/dist/{dialect-drift.js → adapters/claude-code/dialect-drift.js} +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isEventFiring = isEventFiring;
|
|
4
|
+
/**
|
|
5
|
+
* Narrows the DRIVER, which an inline `d.firing.kind === "event"` does not:
|
|
6
|
+
* TypeScript narrows the discriminated PROPERTY, so the driver itself stays
|
|
7
|
+
* `HarnessLiveDriver` and passing it where an {@link EventFiringDriver} is
|
|
8
|
+
* wanted is still an error (measured — probe P2 in the design doc). The
|
|
9
|
+
* negative arm keeps its `caveat`, which is what the n/a note prints.
|
|
10
|
+
*/
|
|
11
|
+
function isEventFiring(d) {
|
|
12
|
+
return d.firing.kind === "event";
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=live-driver.js.map
|
package/dist/core/markdown.d.ts
CHANGED
|
@@ -10,6 +10,29 @@
|
|
|
10
10
|
* indices line up 1:1 with the caller's line array.
|
|
11
11
|
*/
|
|
12
12
|
export declare function fencedLineFlags(src: string): boolean[];
|
|
13
|
+
/**
|
|
14
|
+
* The document's PROSE, as CommonMark sees it: one entry per source line that
|
|
15
|
+
* carries author text, with fenced code, indented code, raw HTML blocks, inline
|
|
16
|
+
* code spans and inline HTML removed, and blank lines dropped.
|
|
17
|
+
*
|
|
18
|
+
* 🔴 WHY A CALLER MUST NOT DO THIS BY SPLITTING LINES. The module header already
|
|
19
|
+
* records that the naive `inFence = !inFence` toggle was copy-pasted into five
|
|
20
|
+
* detectors and is wrong on nested or unbalanced fences. The same applies to
|
|
21
|
+
* every other part of "what is the author actually saying here": a `<!-- -->`
|
|
22
|
+
* spanning three lines, a four-backtick block containing a bare three-backtick
|
|
23
|
+
* line, an indented continuation. The parser has all of it.
|
|
24
|
+
*
|
|
25
|
+
* What it also does, and what a hand-written reader gets wrong quietly: block
|
|
26
|
+
* parsing strips the leading INDENT, normalises CRLF, and drops trailing
|
|
27
|
+
* whitespace and trailing blank lines — so `@AGENTS.md`, ` @AGENTS.md`,
|
|
28
|
+
* `@AGENTS.md ` and a CRLF-terminated copy all arrive as the same string
|
|
29
|
+
* (measured against this repo's markdown-it, 2026-09-21).
|
|
30
|
+
*
|
|
31
|
+
* ⚠️ THE ONE THING IT DOES NOT NORMALISE IS A UTF-8 BOM: markdown-it hands back
|
|
32
|
+
* `"\uFEFF@AGENTS.md"` for a BOM-prefixed first line, so this strips it. That is
|
|
33
|
+
* a measurement, not a precaution.
|
|
34
|
+
*/
|
|
35
|
+
export declare function proseLines(src: string): readonly string[];
|
|
13
36
|
/** One fenced code block: its BODY (delimiters excluded) and where the body starts. */
|
|
14
37
|
export interface FencedBlock {
|
|
15
38
|
/** The block's contents, without the opening/closing fence lines. */
|
package/dist/core/markdown.js
CHANGED
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.fencedLineFlags = fencedLineFlags;
|
|
4
|
+
exports.proseLines = proseLines;
|
|
7
5
|
exports.fencedCodeBlocks = fencedCodeBlocks;
|
|
8
6
|
exports.markdownRefs = markdownRefs;
|
|
9
|
-
|
|
10
|
-
* vigiles — the ONE markdown-structure helper.
|
|
11
|
-
*
|
|
12
|
-
* Every place that needs to know "is this source line inside a fenced code
|
|
13
|
-
* block?" routes through here, so no detector hand-rolls a fence toggle again.
|
|
14
|
-
* The naive `inFence = !inFence` toggle (copy-pasted across five detectors
|
|
15
|
-
* before this) is WRONG on nested / unbalanced fences: a 4-backtick block
|
|
16
|
-
* containing a bare ``` line mis-toggles, and a `##` inside the block leaks out
|
|
17
|
-
* as a real heading (demonstrated against `adopt`'s block splitter). Backed by
|
|
18
|
-
* markdown-it (CommonMark) — the same "use the real parser, not regex"
|
|
19
|
-
* discipline this repo already applies to Bash (mvdan-sh), code (ast-grep),
|
|
20
|
-
* YAML (js-yaml), and TOML (@iarna/toml). Markdown was the one structured format
|
|
21
|
-
* still parsed by hand.
|
|
22
|
-
*
|
|
23
|
-
* Node-free by construction: markdown-it is pure JS with no node builtins, so
|
|
24
|
-
* this bundles clean in the browser scan engine (reached via skill-resources).
|
|
25
|
-
*/
|
|
26
|
-
const markdown_it_1 = __importDefault(require("markdown-it"));
|
|
7
|
+
const MarkdownIt = () => require("markdown-it");
|
|
27
8
|
// One reusable parser; parse() is stateless across calls.
|
|
28
|
-
|
|
9
|
+
let mdCached = null;
|
|
10
|
+
const md = () => (mdCached ??= new (MarkdownIt())());
|
|
29
11
|
/**
|
|
30
12
|
* A SECOND parser, used only by {@link markdownRefs}, with link handling turned
|
|
31
13
|
* down to "report exactly what the author wrote":
|
|
@@ -40,9 +22,16 @@ const md = new markdown_it_1.default();
|
|
|
40
22
|
* turn "a destination this tool declines to resolve" into "no destination at
|
|
41
23
|
* all". Skipping by scheme is the caller's job and it already does it.
|
|
42
24
|
*/
|
|
43
|
-
|
|
44
|
-
mdRefs
|
|
45
|
-
|
|
25
|
+
let mdRefsCached = null;
|
|
26
|
+
const mdRefs = () => {
|
|
27
|
+
if (mdRefsCached === null) {
|
|
28
|
+
const parser = new (MarkdownIt())();
|
|
29
|
+
parser.normalizeLink = (url) => url;
|
|
30
|
+
parser.validateLink = () => true;
|
|
31
|
+
mdRefsCached = parser;
|
|
32
|
+
}
|
|
33
|
+
return mdRefsCached;
|
|
34
|
+
};
|
|
46
35
|
/**
|
|
47
36
|
* A boolean per source line (0-based): `true` when the line lies inside a fenced
|
|
48
37
|
* code block (` ``` ` or `~~~`), the delimiter lines included — matching the
|
|
@@ -57,7 +46,7 @@ mdRefs.validateLink = () => true;
|
|
|
57
46
|
function fencedLineFlags(src) {
|
|
58
47
|
const lineCount = src.split("\n").length;
|
|
59
48
|
const flags = new Array(lineCount).fill(false);
|
|
60
|
-
for (const tok of md.parse(src, {})) {
|
|
49
|
+
for (const tok of md().parse(src, {})) {
|
|
61
50
|
// Only real ``` / ~~~ fences (a block-level token); markdown-it's `map` is
|
|
62
51
|
// a [start, end) 0-based line range covering the delimiters + body.
|
|
63
52
|
if (tok.type !== "fence" || tok.map === null)
|
|
@@ -68,6 +57,66 @@ function fencedLineFlags(src) {
|
|
|
68
57
|
}
|
|
69
58
|
return flags;
|
|
70
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* A THIRD parser, with `html: true`, used only by {@link proseLines}.
|
|
62
|
+
*
|
|
63
|
+
* The default `md` above has HTML disabled, so `<!-- maintainer note -->` comes
|
|
64
|
+
* back as ordinary paragraph TEXT rather than as an `html_block` token — which
|
|
65
|
+
* would put an author's commented-out line into the prose a caller is reading.
|
|
66
|
+
* Measured: with the default parser, `"<!-- note -->\n\n@AGENTS.md"` yields two
|
|
67
|
+
* prose lines; with this one, one. Enabling html on the shared parser would
|
|
68
|
+
* change what every existing caller sees, so this is a sibling rather than a
|
|
69
|
+
* setting, exactly as `mdRefs` is.
|
|
70
|
+
*/
|
|
71
|
+
let mdProseCached = null;
|
|
72
|
+
const mdProse = () => (mdProseCached ??= new (MarkdownIt())({ html: true }));
|
|
73
|
+
/**
|
|
74
|
+
* The document's PROSE, as CommonMark sees it: one entry per source line that
|
|
75
|
+
* carries author text, with fenced code, indented code, raw HTML blocks, inline
|
|
76
|
+
* code spans and inline HTML removed, and blank lines dropped.
|
|
77
|
+
*
|
|
78
|
+
* 🔴 WHY A CALLER MUST NOT DO THIS BY SPLITTING LINES. The module header already
|
|
79
|
+
* records that the naive `inFence = !inFence` toggle was copy-pasted into five
|
|
80
|
+
* detectors and is wrong on nested or unbalanced fences. The same applies to
|
|
81
|
+
* every other part of "what is the author actually saying here": a `<!-- -->`
|
|
82
|
+
* spanning three lines, a four-backtick block containing a bare three-backtick
|
|
83
|
+
* line, an indented continuation. The parser has all of it.
|
|
84
|
+
*
|
|
85
|
+
* What it also does, and what a hand-written reader gets wrong quietly: block
|
|
86
|
+
* parsing strips the leading INDENT, normalises CRLF, and drops trailing
|
|
87
|
+
* whitespace and trailing blank lines — so `@AGENTS.md`, ` @AGENTS.md`,
|
|
88
|
+
* `@AGENTS.md ` and a CRLF-terminated copy all arrive as the same string
|
|
89
|
+
* (measured against this repo's markdown-it, 2026-09-21).
|
|
90
|
+
*
|
|
91
|
+
* ⚠️ THE ONE THING IT DOES NOT NORMALISE IS A UTF-8 BOM: markdown-it hands back
|
|
92
|
+
* `"\uFEFF@AGENTS.md"` for a BOM-prefixed first line, so this strips it. That is
|
|
93
|
+
* a measurement, not a precaution.
|
|
94
|
+
*/
|
|
95
|
+
function proseLines(src) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const tok of mdProse().parse(src, {})) {
|
|
98
|
+
if (tok.type !== "inline")
|
|
99
|
+
continue;
|
|
100
|
+
let line = "";
|
|
101
|
+
for (const child of tok.children ?? []) {
|
|
102
|
+
if (child.type === "softbreak" || child.type === "hardbreak") {
|
|
103
|
+
out.push(line);
|
|
104
|
+
line = "";
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// A code span and an inline `<!-- -->` are not the author's prose; the
|
|
108
|
+
// corpus that motivated this is full of `@dataclass`-shaped text inside
|
|
109
|
+
// them, and none of it names a file.
|
|
110
|
+
if (child.type === "code_inline" || child.type === "html_inline")
|
|
111
|
+
continue;
|
|
112
|
+
line += child.content;
|
|
113
|
+
}
|
|
114
|
+
out.push(line);
|
|
115
|
+
}
|
|
116
|
+
return out
|
|
117
|
+
.map((line) => line.replace(/^\uFEFF/, ""))
|
|
118
|
+
.filter((line) => line !== "");
|
|
119
|
+
}
|
|
71
120
|
/**
|
|
72
121
|
* Every fenced code block in `src`, in document order — the same parser
|
|
73
122
|
* {@link fencedLineFlags} uses, for callers that want the CONTENTS rather than a
|
|
@@ -94,7 +143,7 @@ function fencedLineFlags(src) {
|
|
|
94
143
|
*/
|
|
95
144
|
function fencedCodeBlocks(src) {
|
|
96
145
|
const out = [];
|
|
97
|
-
for (const tok of md.parse(src, {})) {
|
|
146
|
+
for (const tok of md().parse(src, {})) {
|
|
98
147
|
if (tok.type !== "fence" || tok.map === null)
|
|
99
148
|
continue;
|
|
100
149
|
// `content` is the body only; `map[0]` is the OPENING delimiter's 0-based
|
|
@@ -146,7 +195,7 @@ function markdownRefs(src) {
|
|
|
146
195
|
// lines of a real corpus are that.
|
|
147
196
|
if (fenced[i] || (!line.includes("`") && !line.includes("](")))
|
|
148
197
|
continue;
|
|
149
|
-
for (const tok of mdRefs.parseInline(line, {})) {
|
|
198
|
+
for (const tok of mdRefs().parseInline(line, {})) {
|
|
150
199
|
refsInInline(tok.children ?? [], i + 1, out);
|
|
151
200
|
}
|
|
152
201
|
}
|
package/dist/core/orphans.js
CHANGED
|
@@ -40,8 +40,8 @@ const SKILL_FILE = "SKILL.md";
|
|
|
40
40
|
/**
|
|
41
41
|
* Files the HARNESS loads directly — its instruction file
|
|
42
42
|
* (`layout.instructionFile`, e.g. `CLAUDE.md` / `AGENTS.md`), a skill
|
|
43
|
-
* (`SKILL.md`), a subagent (`<
|
|
44
|
-
* (`<
|
|
43
|
+
* (`SKILL.md`), a subagent (`<surfaces.agent>/*.md`), or a slash command
|
|
44
|
+
* (`<surfaces.command>/*.md`) — are load-bearing by their NAME/LOCATION, not because
|
|
45
45
|
* another `.md` links to them. They are categorically NOT docs, so they are
|
|
46
46
|
* never orphans even when `orphans.include` broadens to the whole repo.
|
|
47
47
|
*
|
|
@@ -61,16 +61,18 @@ function isHarnessLoadedFile(path, layouts) {
|
|
|
61
61
|
if (base === layout.instructionFile)
|
|
62
62
|
return true;
|
|
63
63
|
// Subagent / slash-command surfaces live at a REAL surface root — the repo
|
|
64
|
-
// root
|
|
65
|
-
//
|
|
66
|
-
//
|
|
64
|
+
// root or the user-surface root (e.g. `.claude/`) — NOT any nested dir that
|
|
65
|
+
// merely shares the name. A doc under `docs/prompts/` is documentation, not
|
|
66
|
+
// Codex's `prompts` command surface. (This listed the user-surface root and
|
|
67
|
+
// the materialize root separately; they are one field now, so the set it
|
|
68
|
+
// built is unchanged and can no longer hold two different values.)
|
|
67
69
|
const roots = [
|
|
68
70
|
"",
|
|
69
|
-
...[layout.userSurfaceRoot
|
|
71
|
+
...[layout.userSurfaceRoot]
|
|
70
72
|
.filter((r) => !!r)
|
|
71
73
|
.map((r) => `${r}/`),
|
|
72
74
|
];
|
|
73
|
-
for (const dir of [layout.
|
|
75
|
+
for (const dir of [layout.surfaces.agent, layout.surfaces.command]) {
|
|
74
76
|
if (dir && roots.some((r) => norm.startsWith(`${r}${dir}/`))) {
|
|
75
77
|
return true;
|
|
76
78
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SettingsCodec {
|
|
2
|
+
/**
|
|
3
|
+
* For messages and the generated JSON Schema only. The core never branches
|
|
4
|
+
* on it — that is the whole reason this is a codec and not an enum, so a
|
|
5
|
+
* branch on this field would put the defect straight back.
|
|
6
|
+
*/
|
|
7
|
+
readonly label: string;
|
|
8
|
+
/** Throws on malformed text; every caller today already catches. */
|
|
9
|
+
parse(text: string): Record<string, unknown>;
|
|
10
|
+
/** The inverse, with the trailing newline the harness's own tooling writes. */
|
|
11
|
+
render(value: Record<string, unknown>): string;
|
|
12
|
+
}
|
|
13
|
+
/** JSON settings (`.claude/settings.json`, `opencode.json`). */
|
|
14
|
+
export declare const jsonSettingsCodec: SettingsCodec;
|
|
15
|
+
/** TOML settings (Codex's `.codex/config.toml`). */
|
|
16
|
+
export declare const tomlSettingsCodec: SettingsCodec;
|
|
17
|
+
//# sourceMappingURL=settings-codec.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tomlSettingsCodec = exports.jsonSettingsCodec = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `SettingsCodec` — the BYTES-TO-VALUE half of a harness's settings file, and
|
|
6
|
+
* the two encodings vigiles ships.
|
|
7
|
+
*
|
|
8
|
+
* 🔴 IT REPLACES A TWO-VALUED ENUM IN THE CORE. `PluginLayout.settingsFormat`
|
|
9
|
+
* was `"json" | "toml"`, and nine call sites branched on it: six about the
|
|
10
|
+
* ENCODING (parse a manifest, parse a settings file, serialize a merged
|
|
11
|
+
* config) and three about the hooks-entry SHAPE, which is a different question
|
|
12
|
+
* entirely and now lives on `HookProtocol`. Every one of those branches was a
|
|
13
|
+
* per-value `if`, so a third-party adapter whose settings are YAML could
|
|
14
|
+
* declare nothing the core would honour: the type had exactly two inhabitants
|
|
15
|
+
* and they were both ours.
|
|
16
|
+
*
|
|
17
|
+
* A codec closes that by construction — there is no enum left to be outside
|
|
18
|
+
* of. The grep that checks it is in the design: `'"json" | "toml"'` over
|
|
19
|
+
* `src/` must return only the adapter files that NAME their encoding, never a
|
|
20
|
+
* core module branching on one.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ WHAT A CODEC IS NOT: it does not know what the value MEANS. Whether a
|
|
23
|
+
* hooks entry nests `{matcher, hooks: [{type, command}]}` or is flat
|
|
24
|
+
* `{matcher, command}` is the harness's SHAPE, and it is `HookProtocol`'s —
|
|
25
|
+
* see `registration` there. Reading and writing an encoding, and reading and
|
|
26
|
+
* writing a shape, were the two halves `settingsFormat` was standing in for.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* 🔴 `@iarna/toml` IS REQUIRED LAZILY, AND THAT IS A MEASUREMENT, NOT A STYLE.
|
|
30
|
+
* The same wrapper exists in `hook-install.ts` and `core/hook-program.ts` with
|
|
31
|
+
* the reason recorded: a top-level import puts the TOML parser into the graph
|
|
32
|
+
* of every hook decision, because the hook runtime reaches those modules —
|
|
33
|
+
* measured 2026-09-08 at 56 ms per spawn. A codec that every `PluginLayout`
|
|
34
|
+
* now carries is reached from strictly MORE places than either of them, so a
|
|
35
|
+
* top-level import here would undo that fix and widen it. `tsc` lowers these
|
|
36
|
+
* to CommonJS, so the `require` genuinely does not run until a codec method is
|
|
37
|
+
* called, and a layout that is only READ (the common case) costs nothing.
|
|
38
|
+
*/
|
|
39
|
+
const toml = () => require("@iarna/toml");
|
|
40
|
+
/** JSON settings (`.claude/settings.json`, `opencode.json`). */
|
|
41
|
+
exports.jsonSettingsCodec = {
|
|
42
|
+
label: "json",
|
|
43
|
+
parse: (text) => JSON.parse(text),
|
|
44
|
+
render: (value) => JSON.stringify(value, null, 2) + "\n",
|
|
45
|
+
};
|
|
46
|
+
/** TOML settings (Codex's `.codex/config.toml`). */
|
|
47
|
+
exports.tomlSettingsCodec = {
|
|
48
|
+
label: "toml",
|
|
49
|
+
parse: (text) => toml().parse(text),
|
|
50
|
+
// `trimEnd` before the newline: `stringifyToml` already ends with one, and
|
|
51
|
+
// two would be a diff on every write against a file the harness itself wrote.
|
|
52
|
+
render: (value) => toml()
|
|
53
|
+
.stringify(value)
|
|
54
|
+
.trimEnd() + "\n",
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=settings-codec.js.map
|
|
@@ -124,8 +124,8 @@ export declare function discoverSurfaces(paths: readonly string[]): readonly Dis
|
|
|
124
124
|
* (as `codexLayout` just did, `.codex/skills` → `.agents/skills`) moves its claim
|
|
125
125
|
* with it and the two cannot drift.
|
|
126
126
|
*
|
|
127
|
-
* ⚠️
|
|
128
|
-
*
|
|
127
|
+
* ⚠️ The materialize prefix goes through {@link located} because `codexLayout`
|
|
128
|
+
* has none (`""`) and a directory prefix of `""` is meaningless. It is DEFENCE IN DEPTH,
|
|
129
129
|
* not the load-bearing guard, and the difference was MEASURED rather than
|
|
130
130
|
* assumed: removing this filter alone leaves `layoutClaims(codexLayout,
|
|
131
131
|
* "src/index.ts")` at `false`, because the boundary form `path.startsWith(`${d}/`)`
|
|
@@ -168,8 +168,8 @@ function located(dir) {
|
|
|
168
168
|
* (as `codexLayout` just did, `.codex/skills` → `.agents/skills`) moves its claim
|
|
169
169
|
* with it and the two cannot drift.
|
|
170
170
|
*
|
|
171
|
-
* ⚠️
|
|
172
|
-
*
|
|
171
|
+
* ⚠️ The materialize prefix goes through {@link located} because `codexLayout`
|
|
172
|
+
* has none (`""`) and a directory prefix of `""` is meaningless. It is DEFENCE IN DEPTH,
|
|
173
173
|
* not the load-bearing guard, and the difference was MEASURED rather than
|
|
174
174
|
* assumed: removing this filter alone leaves `layoutClaims(codexLayout,
|
|
175
175
|
* "src/index.ts")` at `false`, because the boundary form `path.startsWith(`${d}/`)`
|
|
@@ -186,9 +186,9 @@ function layoutLocations(layout) {
|
|
|
186
186
|
if (d !== null)
|
|
187
187
|
dirs.add(d);
|
|
188
188
|
};
|
|
189
|
-
add(located(
|
|
189
|
+
add(located((0, layout_js_1.materializePrefix)(layout)));
|
|
190
190
|
add(user);
|
|
191
|
-
for (const s of
|
|
191
|
+
for (const s of (0, layout_js_1.surfaceDirs)(layout)) {
|
|
192
192
|
add(located(s));
|
|
193
193
|
if (user !== null)
|
|
194
194
|
add(located(`${user}/${s}`));
|
|
@@ -204,6 +204,11 @@ function layoutLocations(layout) {
|
|
|
204
204
|
layout.settingsPath,
|
|
205
205
|
layout.hooksConventionPath,
|
|
206
206
|
]) {
|
|
207
|
+
// `hooksConventionPath` is optional now — a harness whose hooks are code
|
|
208
|
+
// modules has none. Absent contributes no dir and no file, which is what
|
|
209
|
+
// the old `""` sentinel was filtered into below anyway.
|
|
210
|
+
if (p === undefined)
|
|
211
|
+
continue;
|
|
207
212
|
const at = p.lastIndexOf("/");
|
|
208
213
|
if (at > 0)
|
|
209
214
|
add(p.slice(0, at));
|
|
@@ -216,7 +221,7 @@ function layoutLocations(layout) {
|
|
|
216
221
|
layout.manifestPath,
|
|
217
222
|
layout.settingsPath,
|
|
218
223
|
layout.hooksConventionPath,
|
|
219
|
-
].filter((f) => f !== ""),
|
|
224
|
+
].filter((f) => f !== undefined && f !== ""),
|
|
220
225
|
};
|
|
221
226
|
}
|
|
222
227
|
/**
|
|
@@ -269,7 +274,7 @@ function declaredRootClaims(layout, roots, path) {
|
|
|
269
274
|
function declaredRootDirs(layout, roots) {
|
|
270
275
|
const out = [];
|
|
271
276
|
for (const r of roots)
|
|
272
|
-
for (const surface of
|
|
277
|
+
for (const surface of (0, layout_js_1.surfaceDirs)(layout)) {
|
|
273
278
|
const dir = located(`${r}/${surface}`);
|
|
274
279
|
if (dir !== null && !out.includes(dir))
|
|
275
280
|
out.push(dir);
|
|
@@ -365,7 +370,10 @@ function plural(kind, n) {
|
|
|
365
370
|
* reader to move their skills somewhere no harness reads.
|
|
366
371
|
*/
|
|
367
372
|
function knownHomes(layouts, kind) {
|
|
368
|
-
|
|
373
|
+
// `DiscoveredKind` and `SurfaceKind` are the same three words; the cast is the
|
|
374
|
+
// one place they are related, and it is here rather than in the port because a
|
|
375
|
+
// discovered kind is a fact about a PATH the domain found, not about a layout.
|
|
376
|
+
const dirOf = (l) => l.surfaces[kind];
|
|
369
377
|
const homes = new Set();
|
|
370
378
|
for (const l of layouts) {
|
|
371
379
|
const dir = located(dirOf(l));
|
|
@@ -410,15 +418,19 @@ function unclaimedSurfaceFindings(paths, layouts, declared) {
|
|
|
410
418
|
for (const scope of declared ?? [])
|
|
411
419
|
if (scope.roots.length > 0)
|
|
412
420
|
claimers.push((path) => declaredRootClaims(scope.layout, scope.roots, path));
|
|
421
|
+
// The worked EXAMPLE config below names a harness, and it used to name a
|
|
422
|
+
// FIXED one — `claude-code`, spelled into the core, in a diagnostic shown to
|
|
423
|
+
// a repo that may target something else entirely. `layouts` was already in
|
|
424
|
+
// scope and already knew which harness the audit ran under; the example now
|
|
425
|
+
// reads the name off it. Empty `layouts` yields a placeholder rather than an
|
|
426
|
+
// invented name: an example that cannot be copied is better than one that
|
|
427
|
+
// names the wrong harness.
|
|
428
|
+
const exampleHarness = layouts[0]?.name ?? "<harness>";
|
|
413
429
|
return unclaimedDirs(unclaimedSurfaces(discoverSurfaces(paths), claimers)).map((d) => ({
|
|
414
430
|
...d,
|
|
415
431
|
message: `${d.dir}/ holds ${plural(d.kind, d.count)} that no harness vigiles knows about reads, ` +
|
|
416
432
|
`so none of it is in this grade. Three ways out: keep it where it is and say so in ` +
|
|
417
|
-
|
|
418
|
-
// harness the repo being audited may not even target, while `layouts` is
|
|
419
|
-
// already in scope here and knows which one it does.
|
|
420
|
-
// eslint-disable-next-line local/no-harness-names -- example config text
|
|
421
|
-
`.vigilesrc.json (\`{"harnesses":{"claude-code":{"roots":["${d.root === "" ? "." : d.root}"]}}}\` ` +
|
|
433
|
+
`.vigilesrc.json (\`{"harnesses":{"${exampleHarness}":{"roots":["${d.root === "" ? "." : d.root}"]}}}\` ` +
|
|
422
434
|
`— see docs/configuration.md), audit it on its own ` +
|
|
423
435
|
`(\`vigiles audit ${d.root === "" ? "." : d.root}\`), or move it somewhere a harness ` +
|
|
424
436
|
`loads from (${knownHomes(layouts, d.kind)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 🔴 THIS EXISTS BECAUSE THE LOADER USED TO CHOOSE. It read the repo-root
|
|
6
6
|
* `skills/` **or** the project-level `.claude/skills/` — never both — and
|
|
7
7
|
* materialized whichever it picked under the SAME canonical
|
|
8
|
-
* `<
|
|
8
|
+
* `<materializePrefix>/<surface>/…` key. Two real files, one key: the loser was
|
|
9
9
|
* never read, and the winner's content sat under the loser's name. Measured
|
|
10
10
|
* 2026-08-18 on `nyldn/claude-octopus` (pinned corpus): **50 skill names exist in
|
|
11
11
|
* both `skills/` and `.claude/skills/`, and all 50 pairs differ** — the `.claude/`
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* and call THIS for the decision, so the pair that this repo has repeatedly been
|
|
32
32
|
* bitten by fixing on one side only cannot disagree about scoping.
|
|
33
33
|
*/
|
|
34
|
-
import type
|
|
34
|
+
import { type PluginLayout } from "./layout.js";
|
|
35
35
|
/**
|
|
36
36
|
* One discovery level, and the prefix its files are materialized under.
|
|
37
37
|
*
|
|
@@ -60,6 +60,20 @@ export interface SurfaceScope {
|
|
|
60
60
|
export type SurfaceSource = {
|
|
61
61
|
readonly kind: "single-skill";
|
|
62
62
|
readonly skillName: string;
|
|
63
|
+
/**
|
|
64
|
+
* The layout's skill dir, CARRIED rather than re-read by the consumer.
|
|
65
|
+
*
|
|
66
|
+
* 🔴 IT USED TO BE RE-DERIVED AT BOTH CONSUMERS, AND THE BRANCH THAT
|
|
67
|
+
* FOLLOWED WAS UNREACHABLE. `layout.surfaces.skill` is optional, so each
|
|
68
|
+
* of `plugin-loader.ts` and `scan-files.ts` narrowed it again and wrote a
|
|
69
|
+
* `return` for the `undefined` case — a case this variant is never
|
|
70
|
+
* constructed in, since the test below is the condition for making one.
|
|
71
|
+
* Two dead returns, in two engines that must agree, plus a comment in each
|
|
72
|
+
* explaining why the code beneath it cannot run. Carrying the value is the
|
|
73
|
+
* same construction the rest of this port uses: keep one copy, where it is
|
|
74
|
+
* KNOWN, instead of re-deriving it where it is not.
|
|
75
|
+
*/
|
|
76
|
+
readonly skillDir: string;
|
|
63
77
|
} | {
|
|
64
78
|
readonly kind: "scopes";
|
|
65
79
|
readonly scopes: readonly SurfaceScope[];
|
|
@@ -110,7 +124,7 @@ export declare function normalizeSurfaceRoots(roots: readonly string[] | undefin
|
|
|
110
124
|
* Classify the target and list every scope to read, HIGHEST-PRECEDENCE FIRST.
|
|
111
125
|
*
|
|
112
126
|
* Precedence decides only one thing: which scope keeps the canonical
|
|
113
|
-
* `<
|
|
127
|
+
* `<materializePrefix>/…` key. The project scope takes it, because that key IS
|
|
114
128
|
* where a project skill lives — `.claude/skills/deploy/SKILL.md` is loaded from
|
|
115
129
|
* exactly that path and answers to `/deploy`. A plugin scope keeps its own real
|
|
116
130
|
* location (`skills/deploy/SKILL.md`), which is likewise where the harness reads
|
|
@@ -118,12 +132,18 @@ export declare function normalizeSurfaceRoots(roots: readonly string[] | undefin
|
|
|
118
132
|
*
|
|
119
133
|
* 🔴 THE COLLISION IS STRUCTURAL, NOT CHECKED. Only the FIRST scope is relocated;
|
|
120
134
|
* every later one keeps `base` as its prefix. Since the first scope is the only
|
|
121
|
-
* one that can produce a `<
|
|
135
|
+
* one that can produce a `<materializePrefix>/…` key, and every other prefix is a
|
|
122
136
|
* distinct real directory, two scopes cannot mint the same key — there is no
|
|
123
137
|
* ordering, no "if already taken", and no last-write-wins to get wrong.
|
|
124
138
|
* {@link assertDistinctScopeKeys} is the LOUD backstop for a future
|
|
125
|
-
* `PluginLayout` that breaks the premise (e.g. one naming `.claude` as BOTH
|
|
126
|
-
*
|
|
139
|
+
* `PluginLayout` that breaks the premise (e.g. one naming `.claude` as BOTH the
|
|
140
|
+
* materialize prefix and a second scope's base).
|
|
141
|
+
*
|
|
142
|
+
* ⚠️ HALF OF WHAT IT GUARDED IS NOW UNREPRESENTABLE. The premise used to have
|
|
143
|
+
* two ways to break: a declared root colliding with the prefix, and a layout
|
|
144
|
+
* whose `materializeRoot` differed from its `userSurfaceRoot` at all. The second
|
|
145
|
+
* field is gone — the prefix IS `userSurfaceRoot ?? ""` — so only the first
|
|
146
|
+
* remains, and this backstop is kept for it.
|
|
127
147
|
*/
|
|
128
148
|
export declare function surfaceSource(layout: PluginLayout, probe: SurfaceProbe): SurfaceSource;
|
|
129
149
|
/** The `LoadedPlugin.files` key for one file under one scope. */
|
|
@@ -5,6 +5,40 @@ exports.surfaceSource = surfaceSource;
|
|
|
5
5
|
exports.scopeKey = scopeKey;
|
|
6
6
|
exports.assertDistinctScopeKeys = assertDistinctScopeKeys;
|
|
7
7
|
exports.multiScopeWarning = multiScopeWarning;
|
|
8
|
+
/**
|
|
9
|
+
* WHERE a repo's model surfaces (skills/agents/commands) live — and the key each
|
|
10
|
+
* one is materialized under.
|
|
11
|
+
*
|
|
12
|
+
* 🔴 THIS EXISTS BECAUSE THE LOADER USED TO CHOOSE. It read the repo-root
|
|
13
|
+
* `skills/` **or** the project-level `.claude/skills/` — never both — and
|
|
14
|
+
* materialized whichever it picked under the SAME canonical
|
|
15
|
+
* `<materializePrefix>/<surface>/…` key. Two real files, one key: the loser was
|
|
16
|
+
* never read, and the winner's content sat under the loser's name. Measured
|
|
17
|
+
* 2026-08-18 on `nyldn/claude-octopus` (pinned corpus): **50 skill names exist in
|
|
18
|
+
* both `skills/` and `.claude/skills/`, and all 50 pairs differ** — the `.claude/`
|
|
19
|
+
* copies carry multi-line unquoted `description:` blocks that a strict YAML
|
|
20
|
+
* loader rejects. vigiles reported those fifty skills as clean without ever
|
|
21
|
+
* having opened the files it named.
|
|
22
|
+
*
|
|
23
|
+
* The vendor settles it — Claude Code loads BOTH, in two namespaces:
|
|
24
|
+
*
|
|
25
|
+
* > Plugin skills use a `plugin-name:skill-name` namespace, so they can't
|
|
26
|
+
* > conflict with other levels.
|
|
27
|
+
* > For example, `my-plugin/skills/deploy/SKILL.md` becomes `/my-plugin:deploy`
|
|
28
|
+
* > and loads alongside a `deploy` skill in your project's `.claude/skills/`.
|
|
29
|
+
* > — https://code.claude.com/docs/en/skills § "Where skills live"
|
|
30
|
+
*
|
|
31
|
+
* So "pick one" was never a tie-break to get right; it was a question that has no
|
|
32
|
+
* answer, asked because the key shape forced one. The fix removes the question:
|
|
33
|
+
* every scope present is read, and **a scope's key prefix is derived from the
|
|
34
|
+
* scope, not from a winner**, so two files can no longer claim one key.
|
|
35
|
+
*
|
|
36
|
+
* Node-free and IO-free on purpose: the disk loader (`src/plugin-loader.ts`) and
|
|
37
|
+
* the browser file-map twin (`src/scan-files.ts`) each probe their own storage
|
|
38
|
+
* and call THIS for the decision, so the pair that this repo has repeatedly been
|
|
39
|
+
* bitten by fixing on one side only cannot disagree about scoping.
|
|
40
|
+
*/
|
|
41
|
+
const layout_js_1 = require("./layout.js");
|
|
8
42
|
/**
|
|
9
43
|
* The repo owner's `.vigilesrc.json#harnesses["<name>"].roots`, normalized — or
|
|
10
44
|
* dropped. (The flat top-level `surfaceRoots` key this once read was removed in
|
|
@@ -44,7 +78,7 @@ function normalizeSurfaceRoots(roots) {
|
|
|
44
78
|
* Classify the target and list every scope to read, HIGHEST-PRECEDENCE FIRST.
|
|
45
79
|
*
|
|
46
80
|
* Precedence decides only one thing: which scope keeps the canonical
|
|
47
|
-
* `<
|
|
81
|
+
* `<materializePrefix>/…` key. The project scope takes it, because that key IS
|
|
48
82
|
* where a project skill lives — `.claude/skills/deploy/SKILL.md` is loaded from
|
|
49
83
|
* exactly that path and answers to `/deploy`. A plugin scope keeps its own real
|
|
50
84
|
* location (`skills/deploy/SKILL.md`), which is likewise where the harness reads
|
|
@@ -52,29 +86,36 @@ function normalizeSurfaceRoots(roots) {
|
|
|
52
86
|
*
|
|
53
87
|
* 🔴 THE COLLISION IS STRUCTURAL, NOT CHECKED. Only the FIRST scope is relocated;
|
|
54
88
|
* every later one keeps `base` as its prefix. Since the first scope is the only
|
|
55
|
-
* one that can produce a `<
|
|
89
|
+
* one that can produce a `<materializePrefix>/…` key, and every other prefix is a
|
|
56
90
|
* distinct real directory, two scopes cannot mint the same key — there is no
|
|
57
91
|
* ordering, no "if already taken", and no last-write-wins to get wrong.
|
|
58
92
|
* {@link assertDistinctScopeKeys} is the LOUD backstop for a future
|
|
59
|
-
* `PluginLayout` that breaks the premise (e.g. one naming `.claude` as BOTH
|
|
60
|
-
*
|
|
93
|
+
* `PluginLayout` that breaks the premise (e.g. one naming `.claude` as BOTH the
|
|
94
|
+
* materialize prefix and a second scope's base).
|
|
95
|
+
*
|
|
96
|
+
* ⚠️ HALF OF WHAT IT GUARDED IS NOW UNREPRESENTABLE. The premise used to have
|
|
97
|
+
* two ways to break: a declared root colliding with the prefix, and a layout
|
|
98
|
+
* whose `materializeRoot` differed from its `userSurfaceRoot` at all. The second
|
|
99
|
+
* field is gone — the prefix IS `userSurfaceRoot ?? ""` — so only the first
|
|
100
|
+
* remains, and this backstop is kept for it.
|
|
61
101
|
*/
|
|
62
102
|
function surfaceSource(layout, probe) {
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
const skillDir = layout.surfaces.skill;
|
|
104
|
+
if (skillDir !== undefined && probe.hasRootSkillFile) {
|
|
105
|
+
return { kind: "single-skill", skillName: probe.skillName, skillDir };
|
|
65
106
|
}
|
|
66
107
|
const scopes = [];
|
|
67
108
|
if (layout.userSurfaceRoot !== undefined && probe.userHasLoadable) {
|
|
68
109
|
scopes.push({
|
|
69
110
|
base: layout.userSurfaceRoot,
|
|
70
|
-
materializeUnder: layout
|
|
111
|
+
materializeUnder: (0, layout_js_1.materializePrefix)(layout),
|
|
71
112
|
label: "project",
|
|
72
113
|
});
|
|
73
114
|
}
|
|
74
115
|
if (probe.rootHasLoadable || probe.isPluginShaped) {
|
|
75
116
|
scopes.push({
|
|
76
117
|
base: "",
|
|
77
|
-
materializeUnder: scopes.length === 0 ? layout
|
|
118
|
+
materializeUnder: scopes.length === 0 ? (0, layout_js_1.materializePrefix)(layout) : "",
|
|
78
119
|
label: "plugin",
|
|
79
120
|
});
|
|
80
121
|
}
|
|
@@ -84,7 +125,7 @@ function surfaceSource(layout, probe) {
|
|
|
84
125
|
if (scopes.length === 0 && layout.userSurfaceRoot !== undefined) {
|
|
85
126
|
scopes.push({
|
|
86
127
|
base: layout.userSurfaceRoot,
|
|
87
|
-
materializeUnder: layout
|
|
128
|
+
materializeUnder: (0, layout_js_1.materializePrefix)(layout),
|
|
88
129
|
label: "project",
|
|
89
130
|
});
|
|
90
131
|
}
|
|
@@ -93,7 +134,7 @@ function surfaceSource(layout, probe) {
|
|
|
93
134
|
// declaration says WHERE, the layout still says what a surface is and how it
|
|
94
135
|
// is read. A root already serving as a scope's base is skipped (declaring
|
|
95
136
|
// `.claude` to Claude Code is a no-op, not a second reading of one tree), and
|
|
96
|
-
// so is one equal to
|
|
137
|
+
// so is one equal to the materialize prefix — that key belongs to the scope holding
|
|
97
138
|
// it, and two scopes minting one prefix is what {@link assertDistinctScopeKeys}
|
|
98
139
|
// exists to refuse. Each keeps its OWN base as the key prefix, like a non-first
|
|
99
140
|
// plugin scope, so nothing is relocated on top of anything.
|
|
@@ -107,7 +148,7 @@ function surfaceSource(layout, probe) {
|
|
|
107
148
|
// `plugin-loader.test.ts` ("an EMPTY declared root does not cancel the project
|
|
108
149
|
// scope"), which asserts the full key list both before and after filling it.
|
|
109
150
|
for (const base of probe.declaredRoots ?? []) {
|
|
110
|
-
if (base ===
|
|
151
|
+
if (base === (0, layout_js_1.materializePrefix)(layout))
|
|
111
152
|
continue;
|
|
112
153
|
if (scopes.some((sc) => sc.base === base))
|
|
113
154
|
continue;
|
package/dist/core/validate.js
CHANGED
|
@@ -30,6 +30,7 @@ const cosmiconfig_1 = require("cosmiconfig");
|
|
|
30
30
|
// sources — it failed every suite that touches `loadConfig`. A deferral that
|
|
31
31
|
// only works in one of the two worlds the code runs in is not one line of
|
|
32
32
|
// hygiene, it is a second module system.
|
|
33
|
+
const dialect_js_1 = require("./dialect.js");
|
|
33
34
|
const config_schema_js_1 = require("./config-schema.js");
|
|
34
35
|
// ---------------------------------------------------------------------------
|
|
35
36
|
// Constants & regex
|
|
@@ -44,7 +45,12 @@ const CHECKBOX_RE = /^- \[([ xX])\]\s+(.+)$/;
|
|
|
44
45
|
// The instruction filenames vigiles recognizes when no dialect is injected — a
|
|
45
46
|
// validator-level default, not a harness dialect (the concrete dialects live in
|
|
46
47
|
// the adapters; an injected ValidateOptions.dialect overrides this).
|
|
47
|
-
|
|
48
|
+
//
|
|
49
|
+
// 🔴 READ FROM `core/dialect.ts`, NOT RESTATED HERE, and NOT derived from the
|
|
50
|
+
// adapter registry: `core ⊄ adapter`. The header on
|
|
51
|
+
// `DEFAULT_INSTRUCTION_TARGETS` carries the measurement behind that refusal and
|
|
52
|
+
// names the test that keeps this list and the registry in agreement instead.
|
|
53
|
+
const INSTRUCTION_FILES = dialect_js_1.DEFAULT_INSTRUCTION_TARGETS;
|
|
48
54
|
// The default instruction file to validate when no config names one.
|
|
49
55
|
const DEFAULT_FILES = [INSTRUCTION_FILES[0]];
|
|
50
56
|
/**
|
|
@@ -130,8 +136,15 @@ function loadConfig(searchFrom, { onInvalid = "throw" } = {}) {
|
|
|
130
136
|
const problems = [];
|
|
131
137
|
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
132
138
|
const present = config_schema_js_1.REPLACED_KEYS.filter((k) => k.key in raw);
|
|
133
|
-
if (present.length > 0)
|
|
134
|
-
|
|
139
|
+
if (present.length > 0) {
|
|
140
|
+
// The names the user's OWN removed `harness` key held, so the worked
|
|
141
|
+
// example in the message shows their migration rather than a fixed pair
|
|
142
|
+
// of names spelled into the core. A non-string entry is dropped: this is
|
|
143
|
+
// a config we have already refused, so the message must survive garbage.
|
|
144
|
+
const legacy = raw.harness;
|
|
145
|
+
const names = (Array.isArray(legacy) ? legacy : legacy === undefined ? [] : [legacy]).filter((n) => typeof n === "string" && n.length > 0);
|
|
146
|
+
problems.push((0, config_schema_js_1.replacedKeyMessage)(present, names));
|
|
147
|
+
}
|
|
135
148
|
}
|
|
136
149
|
if (problems.length === 0) {
|
|
137
150
|
const parsed = config_schema_js_1.vigilesConfigSchema.safeParse(raw);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ProbeOrigin, SurfaceProbe } from "./check-count.js";
|
|
2
|
+
import { COVERAGE_ARTIFACT_FILE } from "./local-files.js";
|
|
2
3
|
import type { Surface, SurfaceKind } from "./test-coverage.js";
|
|
3
4
|
/** Bumped when the record shape changes in a non-additive way. */
|
|
4
5
|
export declare const COVERAGE_ARTIFACT_VERSION = 1;
|
|
5
|
-
/** The artifact filename under `.vigiles
|
|
6
|
-
export
|
|
6
|
+
/** The artifact filename under `.vigiles/` — defined on the one local-files list. */
|
|
7
|
+
export { COVERAGE_ARTIFACT_FILE };
|
|
7
8
|
/**
|
|
8
9
|
* Which runner produced a record. Mirrors the discovery split in
|
|
9
10
|
* `test-coverage.ts`: `vigiles test` runs `*.harness.*` (free, every push),
|