vigiles 33.1.0 โ 33.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/adapter-conformance.js +14 -10
- package/dist/adapter.d.ts +3 -1
- package/dist/adapters/claude-code/layout.js +3 -0
- package/dist/adapters/claude-code/output-style.d.ts +3 -0
- package/dist/adapters/claude-code/output-style.js +81 -0
- package/dist/adapters/claude-code/replies.d.ts +14 -0
- package/dist/adapters/claude-code/replies.js +78 -0
- package/dist/adapters/claude-code/runtime.js +70 -0
- package/dist/adapters/claude-code/skill-reachability.d.ts +42 -21
- package/dist/adapters/claude-code/skill-reachability.js +91 -28
- package/dist/audit-report.d.ts +5 -0
- package/dist/audit-report.js +29 -20
- package/dist/audit-report.template.html +25 -25
- package/dist/audit-score.js +2 -2
- package/dist/check.d.ts +20 -0
- package/dist/check.js +87 -2
- package/dist/cli-main.js +249 -30
- package/dist/core/adapter.d.ts +7 -0
- package/dist/core/config-schema.d.ts +9 -0
- package/dist/core/config-schema.js +1 -0
- package/dist/core/eval-driver.d.ts +10 -0
- package/dist/core/frontmatter-read.d.ts +31 -0
- package/dist/core/frontmatter-read.js +4 -2
- package/dist/core/harness-driver.d.ts +2 -0
- package/dist/core/install-reader.js +15 -0
- package/dist/core/layout.d.ts +17 -0
- package/dist/core/layout.js +17 -0
- package/dist/core/output-style.d.ts +84 -0
- package/dist/core/output-style.js +90 -0
- package/dist/core/rule-meta.d.ts +1 -1
- package/dist/core/rule-meta.js +8 -0
- package/dist/core/run-env.d.ts +50 -0
- package/dist/core/run-env.js +51 -0
- package/dist/core/runtime.d.ts +44 -0
- package/dist/core/skill-link-target.d.ts +20 -0
- package/dist/core/skill-link-target.js +33 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/coverage-evidence.d.ts +42 -0
- package/dist/coverage-evidence.js +39 -0
- package/dist/eval.d.ts +65 -42
- package/dist/eval.js +116 -120
- package/dist/harness-test.d.ts +65 -1
- package/dist/harness-test.js +114 -22
- package/dist/optimize.js +1 -5
- package/dist/output-style-arms.d.ts +27 -0
- package/dist/output-style-arms.js +51 -0
- package/dist/plugin-declaration.d.ts +6 -6
- package/dist/plugin-declaration.js +6 -6
- package/dist/scan-files.js +1 -1
- package/dist/scan.d.ts +6 -0
- package/dist/scan.js +14 -4
- package/dist/score-core.d.ts +3 -0
- package/dist/score-core.js +5 -1
- package/dist/setup-plan.d.ts +50 -10
- package/dist/setup-plan.js +103 -68
- package/dist/skill-links.d.ts +183 -0
- package/dist/skill-links.js +255 -0
- package/dist/test-coverage-files.d.ts +3 -1
- package/dist/test-coverage-files.js +20 -6
- package/dist/test-coverage.d.ts +18 -1
- package/dist/test-coverage.js +101 -15
- package/dist/test.d.ts +3 -1
- package/dist/test.js +8 -2
- package/dist/vigilesrc.schema.json +134 -0
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VIGILES_PACKAGE = void 0;
|
|
4
|
+
exports.decideSkillLink = decideSkillLink;
|
|
5
|
+
exports.locatePackage = locatePackage;
|
|
6
|
+
exports.isWithinProject = isWithinProject;
|
|
7
|
+
exports.linkPrecondition = linkPrecondition;
|
|
8
|
+
exports.portableTarget = portableTarget;
|
|
9
|
+
exports.planSkillLinks = planSkillLinks;
|
|
10
|
+
exports.relinkTempPath = relinkTempPath;
|
|
11
|
+
exports.writeSkillLink = writeSkillLink;
|
|
12
|
+
exports.linkFailureReason = linkFailureReason;
|
|
13
|
+
exports.skillLinksUsable = skillLinksUsable;
|
|
14
|
+
exports.formatSkillLinks = formatSkillLinks;
|
|
15
|
+
/**
|
|
16
|
+
* The pure half of how `vigiles init` makes vigiles's skills visible to the
|
|
17
|
+
* agent in EVERY clone: one relative symlink per shipped skill,
|
|
18
|
+
* `<skills home>/<name> -> ../../node_modules/vigiles/skills/<name>`, committed
|
|
19
|
+
* with the repo. The IO half (lstat, readlink, symlink) is `linkVigilesSkills`
|
|
20
|
+
* in `cli-main.ts`; everything that decides something is here, so it is tested
|
|
21
|
+
* without a filesystem.
|
|
22
|
+
*
|
|
23
|
+
* ๐ด WHY LINKS AND NOT ONLY THE PLUGIN. `npm install vigiles` puts the skills at
|
|
24
|
+
* `node_modules/vigiles/skills/`, which no harness scans. The Claude Code plugin
|
|
25
|
+
* install carries them, but it lands in `~/.claude/plugins/` โ a fact about ONE
|
|
26
|
+
* machine. A fresh clone or a fresh container has an empty home directory, and
|
|
27
|
+
* the project's `enabledPlugins` declaration only makes Claude Code PROMPT for
|
|
28
|
+
* an install (`./plugin-declaration.ts`); headless, nobody sees the prompt. The
|
|
29
|
+
* skills were on disk and silently absent. A link is part of the repository, so
|
|
30
|
+
* it arrives with `git clone` and resolves as soon as `npm install` has run โ
|
|
31
|
+
* the same step every other dependency already needs.
|
|
32
|
+
*
|
|
33
|
+
* Both harnesses document following a symlinked skill folder:
|
|
34
|
+
*
|
|
35
|
+
* > Claude Code: "a `<skill-name>` entry in the enterprise, personal, or project
|
|
36
|
+
* > location can be a symlink to a directory elsewhere on disk. Claude Code
|
|
37
|
+
* > reads `SKILL.md` from the target" (code.claude.com/docs/en/skills, fetched
|
|
38
|
+
* > 2026-10-07).
|
|
39
|
+
*
|
|
40
|
+
* > Codex: "Codex supports symlinked skill folders and follows the symlink
|
|
41
|
+
* > target when scanning these locations" (learn.chatgpt.com/docs/build-skills,
|
|
42
|
+
* > fetched 2026-10-07).
|
|
43
|
+
*
|
|
44
|
+
* The layout is the one paperlint's `init` already ships (its `link-skills.ts`),
|
|
45
|
+
* kept on purpose: the links that work in a real consumer are the proven shape.
|
|
46
|
+
*
|
|
47
|
+
* โโ THE RULES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
48
|
+
* what to link every skill the package ships: the installed package's
|
|
49
|
+
* `skills/` when it is installed, the running CLI's own
|
|
50
|
+
* otherwise (the version `init` just declared).
|
|
51
|
+
* where it points `node_modules/vigiles` as the PROJECT spells it, found by
|
|
52
|
+
* walking up no further than the repository root. A link that
|
|
53
|
+
* leaves the repository resolves on this machine only โ the
|
|
54
|
+
* defect this module exists to remove. Under pnpm the real
|
|
55
|
+
* path is a version-stamped store directory; a link spelled
|
|
56
|
+
* through `node_modules/vigiles` survives the next upgrade.
|
|
57
|
+
* what it touches only what is missing. An entry that already leads to the
|
|
58
|
+
* shipped skill is kept; ANY other entry of the same name โ
|
|
59
|
+
* the user's own skill directory, a file, a link elsewhere โ
|
|
60
|
+
* is theirs, and is reported, never replaced.
|
|
61
|
+
*
|
|
62
|
+
* Names are not namespaced: a linked skill is `test-harness`, where the plugin's
|
|
63
|
+
* copy is `vigiles:test-harness`. A user's own skill of the same name therefore
|
|
64
|
+
* wins by construction (we never replace it) and the report names the skill
|
|
65
|
+
* they did not get.
|
|
66
|
+
*/
|
|
67
|
+
const node_path_1 = require("node:path");
|
|
68
|
+
const skill_link_target_js_1 = require("./core/skill-link-target.js");
|
|
69
|
+
/** The package whose skills are linked. */
|
|
70
|
+
exports.VIGILES_PACKAGE = "vigiles";
|
|
71
|
+
/**
|
|
72
|
+
* Decide one entry. A link with OUR target is ours even while it dangles โ that
|
|
73
|
+
* is exactly how a committed link looks in a clone that has not run
|
|
74
|
+
* `npm install` yet, and calling it foreign would make re-running `init` noisy
|
|
75
|
+
* in precisely the place it is meant to help.
|
|
76
|
+
*/
|
|
77
|
+
function decideSkillLink(name, entry, want) {
|
|
78
|
+
switch (entry.kind) {
|
|
79
|
+
case "missing":
|
|
80
|
+
return { name, action: "create", target: want.target };
|
|
81
|
+
case "link":
|
|
82
|
+
if (entry.target === want.target ||
|
|
83
|
+
(entry.resolvesTo !== null && entry.resolvesTo === want.real))
|
|
84
|
+
return { name, action: "keep" };
|
|
85
|
+
// Provably ours (the shape `init` writes, same skill) but aimed elsewhere โ
|
|
86
|
+
// e.g. linked before a workspace install hoisted the package. Rewrite it.
|
|
87
|
+
if ((0, skill_link_target_js_1.isVigilesSkillTarget)(entry.target, name))
|
|
88
|
+
return { name, action: "replace", target: want.target };
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
action: "refuse",
|
|
92
|
+
reason: entry.resolvesTo === null
|
|
93
|
+
? `a dangling link to ${entry.target}`
|
|
94
|
+
: `a link to ${entry.target}`,
|
|
95
|
+
};
|
|
96
|
+
case "directory":
|
|
97
|
+
return { name, action: "refuse", reason: "a directory (your own skill)" };
|
|
98
|
+
case "file":
|
|
99
|
+
return { name, action: "refuse", reason: "a file" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Is there a workspace root strictly above `project`, up to the repo root? */
|
|
103
|
+
function insideWorkspace(project, probe) {
|
|
104
|
+
const walk = (dir) => {
|
|
105
|
+
if (dir !== project && probe.isWorkspaceRoot(dir))
|
|
106
|
+
return true;
|
|
107
|
+
if (probe.isRepoRoot(dir) || (0, node_path_1.dirname)(dir) === dir)
|
|
108
|
+
return false;
|
|
109
|
+
return walk((0, node_path_1.dirname)(dir));
|
|
110
|
+
};
|
|
111
|
+
return walk(project);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Node's own lookup โ the first `node_modules/vigiles` up the tree โ bounded by
|
|
115
|
+
* the repository root, because a committed link that leaves the repository is a
|
|
116
|
+
* link only this machine can follow.
|
|
117
|
+
*/
|
|
118
|
+
function locatePackage(project, probe) {
|
|
119
|
+
// The walk stops at the repository root; with no repository yet (before
|
|
120
|
+
// `git init`) the project itself is the boundary, or an unrelated ancestor's
|
|
121
|
+
// node_modules would be linked into it.
|
|
122
|
+
const repoRoot = (dir) => {
|
|
123
|
+
if (probe.isRepoRoot(dir))
|
|
124
|
+
return dir;
|
|
125
|
+
return (0, node_path_1.dirname)(dir) === dir ? null : repoRoot((0, node_path_1.dirname)(dir));
|
|
126
|
+
};
|
|
127
|
+
const boundary = repoRoot(project) ?? project;
|
|
128
|
+
const walk = (dir) => {
|
|
129
|
+
const candidate = (0, node_path_1.join)(dir, "node_modules", exports.VIGILES_PACKAGE);
|
|
130
|
+
if (probe.isPackage(candidate))
|
|
131
|
+
return { kind: "installed", dir: candidate };
|
|
132
|
+
if (dir === boundary || (0, node_path_1.dirname)(dir) === dir)
|
|
133
|
+
return null;
|
|
134
|
+
return walk((0, node_path_1.dirname)(dir));
|
|
135
|
+
};
|
|
136
|
+
const installed = walk(project);
|
|
137
|
+
if (installed !== null)
|
|
138
|
+
return installed;
|
|
139
|
+
return insideWorkspace(project, probe)
|
|
140
|
+
? {
|
|
141
|
+
kind: "unsure",
|
|
142
|
+
reason: "vigiles isn't installed yet and this package is in a workspace, where the install decides whether it lands in this package's node_modules or the workspace root's โ run npm install, then npx vigiles init",
|
|
143
|
+
}
|
|
144
|
+
: { kind: "expected", dir: (0, node_path_1.join)(project, "node_modules", exports.VIGILES_PACKAGE) };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Is the real path `path` the project's own real path or somewhere under it?
|
|
148
|
+
* Both must be physical (symlink-free), or a link could pass for a child.
|
|
149
|
+
*/
|
|
150
|
+
function isWithinProject(project, path) {
|
|
151
|
+
const rel = (0, node_path_1.relative)(project, path);
|
|
152
|
+
// A child can be NAMED `..cache`; only a whole `..` segment leaves the project.
|
|
153
|
+
return rel.split(node_path_1.sep)[0] !== ".." && !(0, node_path_1.isAbsolute)(rel);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Why NOT to link, or null to go ahead. A link into a `node_modules/vigiles`
|
|
157
|
+
* that no install will ever create is a dangling link committed to the repo โ a
|
|
158
|
+
* worse state than no link, because it looks like the job was done.
|
|
159
|
+
*/
|
|
160
|
+
function linkPrecondition(facts) {
|
|
161
|
+
if (facts.isVigilesItself)
|
|
162
|
+
return "this is the vigiles repository itself; its skills are the plugin's own skills/";
|
|
163
|
+
if (!facts.installed && !facts.dependsOnVigiles)
|
|
164
|
+
return "this repo has no package.json dependency on vigiles, so no install will create node_modules/vigiles for a link to point at; the global install carries the skills here instead";
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A link target as it is committed: `/` separators on every OS. `relative()` on
|
|
169
|
+
* Windows yields `..\\..\\node_modules\\โฆ`, and a clone on Linux or macOS
|
|
170
|
+
* reads those backslashes as ordinary filename characters, so the link dangles.
|
|
171
|
+
*/
|
|
172
|
+
function portableTarget(target, separator = node_path_1.sep) {
|
|
173
|
+
return target.split(separator).join("/");
|
|
174
|
+
}
|
|
175
|
+
function planSkillLinks(input) {
|
|
176
|
+
const skillsDir = (0, node_path_1.join)(input.site.dir, "skills");
|
|
177
|
+
return {
|
|
178
|
+
pending: input.site.kind === "expected",
|
|
179
|
+
decisions: input.names.map((name) => decideSkillLink(name, input.entries(name), {
|
|
180
|
+
target: portableTarget((0, node_path_1.relative)(input.physicalHome, (0, node_path_1.join)(skillsDir, name))),
|
|
181
|
+
real: input.realOf(name),
|
|
182
|
+
})),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Where a replacement link is built before it takes the old one's place. */
|
|
186
|
+
function relinkTempPath(entry) {
|
|
187
|
+
return (0, node_path_1.join)((0, node_path_1.dirname)(entry), `.${(0, node_path_1.basename)(entry)}.vigiles-new`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Write the link `decision` asks for. A `replace` never leaves a gap: the new
|
|
191
|
+
* link is built under a temporary name beside the old one and renamed over it,
|
|
192
|
+
* which is atomic. Removing the old link first would lose it for good when the
|
|
193
|
+
* new one cannot be created (a Windows account without the symlink privilege,
|
|
194
|
+
* a full disk) โ a skill that worked yesterday gone, and only a warning to show
|
|
195
|
+
* for it. Any failure leaves the old link in place and throws.
|
|
196
|
+
*/
|
|
197
|
+
function writeSkillLink(io, entry, target, action) {
|
|
198
|
+
if (action === "create") {
|
|
199
|
+
io.symlink(target, entry);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const temp = relinkTempPath(entry);
|
|
203
|
+
// Whatever already sits at the temporary name is not ours to delete: the
|
|
204
|
+
// `symlink` then fails (EEXIST) and is reported, and both entries stay.
|
|
205
|
+
io.symlink(target, temp);
|
|
206
|
+
try {
|
|
207
|
+
io.rename(temp, entry);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
try {
|
|
211
|
+
// Only the link this call just created is removed.
|
|
212
|
+
io.unlink(temp);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Already gone: the rename error is the news.
|
|
216
|
+
}
|
|
217
|
+
throw e;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Why a `symlink` call failed, with the fix when there is a known one. */
|
|
221
|
+
function linkFailureReason(code, message) {
|
|
222
|
+
const base = `could not create the link: ${message}`;
|
|
223
|
+
return code === "EPERM"
|
|
224
|
+
? `${base} โ on Windows, creating a symlink needs Developer Mode (or an elevated shell), and git needs core.symlinks=true to check one out`
|
|
225
|
+
: base;
|
|
226
|
+
}
|
|
227
|
+
/** At least one shipped skill now reaches the agent through a link. */
|
|
228
|
+
function skillLinksUsable(outcome) {
|
|
229
|
+
return (outcome.kind === "linked" &&
|
|
230
|
+
outcome.results.some((r) => r.status !== "skipped"));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The lines `init` prints. Skipped skills are named one by one: the user loses
|
|
234
|
+
* that skill, and the reason is theirs to act on.
|
|
235
|
+
*/
|
|
236
|
+
function formatSkillLinks(outcome) {
|
|
237
|
+
if (outcome.kind === "not-linked")
|
|
238
|
+
return [`โ vigiles's skills were NOT linked โ ${outcome.reason}`];
|
|
239
|
+
const count = (s) => outcome.results.filter((r) => r.status === s).length;
|
|
240
|
+
const skipped = outcome.results.filter((r) => r.status === "skipped");
|
|
241
|
+
const mark = skipped.length > 0 ? "โ " : "โ";
|
|
242
|
+
return [
|
|
243
|
+
`${mark} vigiles's ${String(outcome.results.length)} skills โ ${outcome.home}/ (relative links into node_modules; commit them): ` +
|
|
244
|
+
`${String(count("created"))} linked now, ${String(count("present"))} already linked, ` +
|
|
245
|
+
(count("relinked") > 0 ? `${String(count("relinked"))} relinked, ` : "") +
|
|
246
|
+
`${String(skipped.length)} skipped`,
|
|
247
|
+
...skipped.map((r) => ` ${outcome.home}/${r.name} left alone โ it is ${r.reason}. Rename it to get vigiles's ${r.name}.`),
|
|
248
|
+
...(outcome.pending
|
|
249
|
+
? [
|
|
250
|
+
" They resolve once vigiles is installed: run npm install (every clone and container needs it anyway).",
|
|
251
|
+
]
|
|
252
|
+
: []),
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=skill-links.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PluginLayout } from "./core/layout.js";
|
|
2
2
|
import type { CoverageDecision, CoverageTier, Surface } from "./test-coverage.js";
|
|
3
3
|
/**
|
|
4
|
-
* The untested harness surfaces (skills / agents / hooks) in a file map โ the
|
|
4
|
+
* The untested harness surfaces (skills / agents / hooks / output styles) in a file map โ the
|
|
5
5
|
* browser-safe twin of `findUntestedSurfaces`, returning the union `untested` list
|
|
6
6
|
* plus the same per-tier split (`scanFiles` needs the counts). Same coverage rules
|
|
7
7
|
* as the disk detector; the tier split is by suffix, exactly as on disk.
|
|
@@ -17,5 +17,7 @@ repoName: string): {
|
|
|
17
17
|
decisions: readonly CoverageDecision[];
|
|
18
18
|
harness: CoverageTier;
|
|
19
19
|
evals: CoverageTier;
|
|
20
|
+
/** Surfaces opted out via `vigiles:ignore-test`; still part of the inventory. */
|
|
21
|
+
exemptSurfaces: readonly Surface[];
|
|
20
22
|
};
|
|
21
23
|
//# sourceMappingURL=test-coverage-files.d.ts.map
|
|
@@ -34,6 +34,7 @@ exports.findUntestedSurfacesInFiles = findUntestedSurfacesInFiles;
|
|
|
34
34
|
const posix_path_js_1 = require("./posix-path.js");
|
|
35
35
|
const layout_js_1 = require("./core/layout.js");
|
|
36
36
|
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
37
|
+
const output_style_js_1 = require("./core/output-style.js");
|
|
37
38
|
const source_kinds_js_1 = require("./source-kinds.js");
|
|
38
39
|
const IGNORE_MARKER = "vigiles:ignore-test";
|
|
39
40
|
/** Mirror of the DEFAULT_IGNORE globs (root-anchored), as a key predicate. */
|
|
@@ -136,6 +137,13 @@ function discoverAgents(files, layout) {
|
|
|
136
137
|
}
|
|
137
138
|
return out;
|
|
138
139
|
}
|
|
140
|
+
/** Twin of test-coverage.ts `discoverOutputStyles`: the harness decides, over the map. */
|
|
141
|
+
function discoverOutputStyles(files, layout) {
|
|
142
|
+
const scan = (0, output_style_js_1.findOutputStyles)(layout, new Map(Object.entries(files).filter(([path]) => !isIgnored(path))));
|
|
143
|
+
return scan.kind === "not-supported"
|
|
144
|
+
? []
|
|
145
|
+
: scan.styles.map((style) => (0, coverage_evidence_js_1.outputStyleSurface)(style.path, files[style.path] ?? ""));
|
|
146
|
+
}
|
|
139
147
|
/**
|
|
140
148
|
* Hook-script paths referenced from a manifest's `hooks` block (file hooks only).
|
|
141
149
|
*
|
|
@@ -226,8 +234,17 @@ function tierOf(considered, tests, globs) {
|
|
|
226
234
|
}
|
|
227
235
|
return { covered, untested, decisions };
|
|
228
236
|
}
|
|
237
|
+
/** Every surface kind in the map โ twin of test-coverage.ts `discoverSurfaces`. */
|
|
238
|
+
function discoverSurfaces(files, layout, repoName) {
|
|
239
|
+
return [
|
|
240
|
+
...discoverSkills(files, layout, repoName),
|
|
241
|
+
...discoverAgents(files, layout),
|
|
242
|
+
...discoverHooks(files, layout),
|
|
243
|
+
...discoverOutputStyles(files, layout),
|
|
244
|
+
];
|
|
245
|
+
}
|
|
229
246
|
/**
|
|
230
|
-
* The untested harness surfaces (skills / agents / hooks) in a file map โ the
|
|
247
|
+
* The untested harness surfaces (skills / agents / hooks / output styles) in a file map โ the
|
|
231
248
|
* browser-safe twin of `findUntestedSurfaces`, returning the union `untested` list
|
|
232
249
|
* plus the same per-tier split (`scanFiles` needs the counts). Same coverage rules
|
|
233
250
|
* as the disk detector; the tier split is by suffix, exactly as on disk.
|
|
@@ -239,11 +256,7 @@ function findUntestedSurfacesInFiles(files, layout,
|
|
|
239
256
|
* `name:`. Same value `scanFiles` already threads for the skill inventory.
|
|
240
257
|
*/
|
|
241
258
|
repoName) {
|
|
242
|
-
const surfaces =
|
|
243
|
-
...discoverSkills(files, layout, repoName),
|
|
244
|
-
...discoverAgents(files, layout),
|
|
245
|
-
...discoverHooks(files, layout),
|
|
246
|
-
];
|
|
259
|
+
const surfaces = discoverSurfaces(files, layout, repoName);
|
|
247
260
|
const considered = surfaces.filter((s) => !s.ignored);
|
|
248
261
|
const tests = discoverTests(files);
|
|
249
262
|
// NO configured `{surface}` globs here, and that is a property of this twin
|
|
@@ -257,6 +270,7 @@ repoName) {
|
|
|
257
270
|
return {
|
|
258
271
|
untested: [...union.untested],
|
|
259
272
|
decisions: union.decisions,
|
|
273
|
+
exemptSurfaces: surfaces.filter((s) => s.ignored),
|
|
260
274
|
harness: tierOf(considered, tests.filter((t) => !(0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path))), noConfiguredGlobs),
|
|
261
275
|
evals: tierOf(considered, tests.filter((t) => (0, coverage_evidence_js_1.isEvalScript)((0, posix_path_js_1.basename)(t.path))), noConfiguredGlobs),
|
|
262
276
|
};
|
package/dist/test-coverage.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ import { type ExcludeSet } from "./exclude.js";
|
|
|
64
64
|
import type { RepoPath } from "./core/frame.js";
|
|
65
65
|
import { type PluginLayout } from "./core/layout.js";
|
|
66
66
|
import { type CoverageEvidence, type EvidenceCounts } from "./coverage-evidence.js";
|
|
67
|
-
export type SurfaceKind = "skill" | "agent" | "hook";
|
|
67
|
+
export type SurfaceKind = "skill" | "agent" | "hook" | "output-style";
|
|
68
68
|
export interface Surface {
|
|
69
69
|
readonly kind: SurfaceKind;
|
|
70
70
|
/** Repo-relative path to the surface file (SKILL.md / agent .md / hook script). */
|
|
@@ -119,6 +119,11 @@ export interface UntestedReport {
|
|
|
119
119
|
readonly untested: readonly Surface[];
|
|
120
120
|
/** Surfaces explicitly opted out via `vigiles:ignore-test`. */
|
|
121
121
|
readonly exempt: number;
|
|
122
|
+
/**
|
|
123
|
+
* The exempt surfaces themselves. The marker waives a surface's test, not its
|
|
124
|
+
* existence, so an inventory counts them (an exempt style still ships).
|
|
125
|
+
*/
|
|
126
|
+
readonly exemptSurfaces: readonly Surface[];
|
|
122
127
|
/**
|
|
123
128
|
* Test files still carrying the RETIRED `vigiles:covers` marker.
|
|
124
129
|
*
|
|
@@ -201,6 +206,18 @@ export interface TestCoverageOptions {
|
|
|
201
206
|
readonly agents?: boolean;
|
|
202
207
|
/** Scan hook scripts referenced from plugin.json / settings.json. Default true. */
|
|
203
208
|
readonly hooks?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Scan output styles, in every folder the layout's `outputStyles` names.
|
|
211
|
+
* Default true; a layout with no output styles finds none either way.
|
|
212
|
+
*/
|
|
213
|
+
readonly outputStyles?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* Every harness the repository declares, when it declares more than one.
|
|
216
|
+
* Surfaces are found in each of them, one per file: a repository can list a
|
|
217
|
+
* harness first and keep its skills, agents or styles in another's folder.
|
|
218
|
+
* Defaults to `[layout]`.
|
|
219
|
+
*/
|
|
220
|
+
readonly harnessLayouts?: readonly PluginLayout[];
|
|
204
221
|
/** Globs of test files that count as coverage. */
|
|
205
222
|
readonly include?: readonly string[];
|
|
206
223
|
/**
|
package/dist/test-coverage.js
CHANGED
|
@@ -78,6 +78,7 @@ const ts_runner_caps_js_1 = require("./ts-runner-caps.js");
|
|
|
78
78
|
const glob_1 = require("glob");
|
|
79
79
|
const glob_ignore_js_1 = require("./core/glob-ignore.js");
|
|
80
80
|
const exclude_js_1 = require("./exclude.js");
|
|
81
|
+
const output_style_js_1 = require("./core/output-style.js");
|
|
81
82
|
const source_kinds_js_1 = require("./source-kinds.js");
|
|
82
83
|
const layout_js_1 = require("./core/layout.js");
|
|
83
84
|
const coverage_evidence_js_1 = require("./coverage-evidence.js");
|
|
@@ -146,6 +147,42 @@ function read(path) {
|
|
|
146
147
|
return "";
|
|
147
148
|
}
|
|
148
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Is this skill directory a DEPENDENCY's โ does it really live under a
|
|
152
|
+
* `node_modules` that is not the scanned base's own ancestor?
|
|
153
|
+
*
|
|
154
|
+
* The `node_modules/**` floor (`EXCLUDE_FLOOR`) is applied to the path as
|
|
155
|
+
* SPELLED, and `.claude/skills/<name>` spells no `node_modules` even when it is
|
|
156
|
+
* a link into one โ which is exactly what `vigiles init` (and paperlint's)
|
|
157
|
+
* commits so the agent sees a package's skills in every clone. Those skills are
|
|
158
|
+
* the package's work, tested by the package; counting them here would report
|
|
159
|
+
* the repo's own `init` as six untested skills, and fail `--strict`.
|
|
160
|
+
*
|
|
161
|
+
* Both the link as written and its real target are asked (see the body).
|
|
162
|
+
* Measured on the path RELATIVE to the base's real path, so auditing a package
|
|
163
|
+
* that itself sits in `node_modules` still sees its own skills, and a pnpm
|
|
164
|
+
* store target (`node_modules/.pnpm/โฆ`) or a hoisted workspace root
|
|
165
|
+
* (`../../node_modules/โฆ`) both count as dependencies. A link elsewhere (one
|
|
166
|
+
* shared skills folder linked per skill) is the repo's own and stays graded.
|
|
167
|
+
* Unresolvable โ not a dependency (the skill stays visible: silence is the
|
|
168
|
+
* worse error here).
|
|
169
|
+
*/
|
|
170
|
+
function isDependencySkill(basePath, skillDir) {
|
|
171
|
+
try {
|
|
172
|
+
const base = (0, node_fs_1.realpathSync)(basePath);
|
|
173
|
+
const inNodeModules = (p) => (0, node_path_1.relative)(base, p).split(node_path_1.sep).includes("node_modules");
|
|
174
|
+
// Both the link as WRITTEN and where it finally lands: under `npm link`, a
|
|
175
|
+
// `file:` dependency or a workspace, `node_modules/<pkg>` is itself a link
|
|
176
|
+
// to a source checkout, so the real path alone names no `node_modules`.
|
|
177
|
+
const spelled = (0, node_fs_1.lstatSync)(skillDir).isSymbolicLink()
|
|
178
|
+
? (0, node_path_1.resolve)((0, node_fs_1.realpathSync)((0, node_path_1.dirname)(skillDir)), (0, node_fs_1.readlinkSync)(skillDir))
|
|
179
|
+
: skillDir;
|
|
180
|
+
return inNodeModules(spelled) || inNodeModules((0, node_fs_1.realpathSync)(skillDir));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
149
186
|
function discoverSkills(basePath, ignore, layout) {
|
|
150
187
|
const out = [];
|
|
151
188
|
// ๐ด ONE GATE FOR THE WHOLE FUNCTION, and it is structural rather than
|
|
@@ -158,7 +195,7 @@ function discoverSkills(basePath, ignore, layout) {
|
|
|
158
195
|
const skillDir = layout.surfaces.skill;
|
|
159
196
|
if (skillDir === undefined)
|
|
160
197
|
return out;
|
|
161
|
-
const found = (0, glob_1.globSync)(surfaceGlobs(skillDir, "*/SKILL.md", (0, layout_js_1.materializePrefix)(layout)), { cwd: basePath, ignore });
|
|
198
|
+
const found = (0, glob_1.globSync)(surfaceGlobs(skillDir, "*/SKILL.md", (0, layout_js_1.materializePrefix)(layout)), { cwd: basePath, ignore }).filter((path) => !isDependencySkill(basePath, (0, node_path_1.join)(basePath, (0, node_path_1.dirname)(path))));
|
|
162
199
|
for (const path of found.sort()) {
|
|
163
200
|
const name = (0, node_path_1.basename)((0, node_path_1.dirname)(path));
|
|
164
201
|
const content = read((0, node_path_1.join)(basePath, path));
|
|
@@ -249,6 +286,46 @@ function discoverAgents(basePath, ignore, layout) {
|
|
|
249
286
|
}
|
|
250
287
|
return out;
|
|
251
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Output styles, by the layout's own rules: every file in a style home that
|
|
291
|
+
* the harness would load. {@link findOutputStyles} decides; this only reads the
|
|
292
|
+
* candidates off the disk, so it and the browser twin cannot disagree on which
|
|
293
|
+
* files are styles.
|
|
294
|
+
*/
|
|
295
|
+
function discoverOutputStyles(basePath, ignore, layout) {
|
|
296
|
+
const candidates = new Map((0, output_style_js_1.outputStyleHomes)(layout).flatMap((home) => (0, glob_1.globSync)(`${home}/**/*`, {
|
|
297
|
+
cwd: basePath,
|
|
298
|
+
ignore: [...ignore],
|
|
299
|
+
dot: true,
|
|
300
|
+
nodir: true,
|
|
301
|
+
}).map((path) => [path.split(node_path_1.sep).join("/"), ""])));
|
|
302
|
+
const scan = (0, output_style_js_1.findOutputStyles)(layout, candidates);
|
|
303
|
+
return scan.kind === "not-supported"
|
|
304
|
+
? []
|
|
305
|
+
: scan.styles.map((style) => (0, coverage_evidence_js_1.outputStyleSurface)(style.path, read((0, node_path_1.join)(basePath, style.path))));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Every kind the options switch on, found under the bundle. Discovery gets only
|
|
309
|
+
* the frame-free ignore floor; the rule's and the repo's excludes are applied
|
|
310
|
+
* later, from `root`, by the caller.
|
|
311
|
+
*/
|
|
312
|
+
function discoverSurfaces(basePath, layout, options) {
|
|
313
|
+
const on = (flag) => flag !== false;
|
|
314
|
+
const found = (options.harnessLayouts ?? [layout]).flatMap((each) => [
|
|
315
|
+
...(on(options.skills)
|
|
316
|
+
? discoverSkills(basePath, [...DEFAULT_IGNORE], each)
|
|
317
|
+
: []),
|
|
318
|
+
...(on(options.agents)
|
|
319
|
+
? discoverAgents(basePath, [...DEFAULT_IGNORE], each)
|
|
320
|
+
: []),
|
|
321
|
+
...(on(options.hooks) ? discoverHooks(basePath, each) : []),
|
|
322
|
+
...(on(options.outputStyles)
|
|
323
|
+
? discoverOutputStyles(basePath, DEFAULT_IGNORE, each)
|
|
324
|
+
: []),
|
|
325
|
+
]);
|
|
326
|
+
// Two declared harnesses can read the same file: one surface per file.
|
|
327
|
+
return [...new Map(found.map((s) => [`${s.kind}\0${s.path}`, s])).values()];
|
|
328
|
+
}
|
|
252
329
|
/**
|
|
253
330
|
* Hook-script paths referenced from a manifest's `hooks` block (file hooks only).
|
|
254
331
|
*
|
|
@@ -419,6 +496,11 @@ function tierOf(considered, tests, index, tier, globs) {
|
|
|
419
496
|
// ---------------------------------------------------------------------------
|
|
420
497
|
// Public API
|
|
421
498
|
// ---------------------------------------------------------------------------
|
|
499
|
+
/** The exempt surfaces and their count: waived from testing, still in the inventory. */
|
|
500
|
+
function exemptOf(surfaces) {
|
|
501
|
+
const exemptSurfaces = surfaces.filter((s) => s.ignored);
|
|
502
|
+
return { exempt: exemptSurfaces.length, exemptSurfaces };
|
|
503
|
+
}
|
|
422
504
|
/**
|
|
423
505
|
* Find harness surfaces (skills / agents / hooks) that no test or eval covers.
|
|
424
506
|
* A surface is covered by a colocated `*.{harness,eval}.mjs` OR any discovered
|
|
@@ -449,24 +531,15 @@ function findUntestedSurfaces(options) {
|
|
|
449
531
|
// Discovery globs from the BUNDLE, so it gets only the frame-free floor; the
|
|
450
532
|
// rule's patterns are root-relative and the repo's carry their own root, so
|
|
451
533
|
// both are applied below, to the root-relative path.
|
|
452
|
-
const
|
|
453
|
-
const found = [];
|
|
454
|
-
if (options.skills !== false)
|
|
455
|
-
found.push(...discoverSkills(basePath, floor, layout));
|
|
456
|
-
if (options.agents !== false)
|
|
457
|
-
found.push(...discoverAgents(basePath, floor, layout));
|
|
458
|
-
if (options.hooks !== false)
|
|
459
|
-
found.push(...discoverHooks(basePath, layout));
|
|
534
|
+
const found = discoverSurfaces(basePath, layout, options);
|
|
460
535
|
// Hooks were never subject to `ignore` (a compiled hook lives under the
|
|
461
536
|
// default-ignored `.vigiles/`); keep that, and re-apply exclude to the kinds
|
|
462
537
|
// whose discovery glob used it โ now from `root`, where the patterns live.
|
|
463
538
|
const surfaces = found
|
|
464
539
|
.map(toRoot)
|
|
465
540
|
.filter((s) => s.kind === "hook" || !excluded(s.path));
|
|
466
|
-
//
|
|
467
|
-
// `vigiles:ignore-test` marker exempts a surface (a visible, deliberate skip).
|
|
541
|
+
// Only an explicit `vigiles:ignore-test` marker exempts a surface.
|
|
468
542
|
const considered = surfaces.filter((s) => !s.ignored);
|
|
469
|
-
const exempt = surfaces.length - considered.length;
|
|
470
543
|
const tests = discoverTests(root, globs, ignore);
|
|
471
544
|
const split = partitionTests(tests);
|
|
472
545
|
// The run record, if there is one. NO artifact โ an empty index โ every
|
|
@@ -487,7 +560,7 @@ function findUntestedSurfaces(options) {
|
|
|
487
560
|
total: considered.length,
|
|
488
561
|
covered: union.covered,
|
|
489
562
|
untested: union.untested,
|
|
490
|
-
|
|
563
|
+
...exemptOf(surfaces),
|
|
491
564
|
staleRuns: staleRunsFor(considered, runIndex),
|
|
492
565
|
testExt: (0, test_file_ext_js_1.testFileExt)({
|
|
493
566
|
configured: options.testExtension,
|
|
@@ -683,6 +756,19 @@ function evalTierQuestion(kind) {
|
|
|
683
756
|
`the tool contract, \`assertAgentOk\` for the typed result. Those two ` +
|
|
684
757
|
`calls drive Claude Code and take no \`evalDriver\`, so on another ` +
|
|
685
758
|
`harness this tier has no public dispatch yet.`);
|
|
759
|
+
case "output-style":
|
|
760
|
+
// A style is not selected by the model, so there is no firing to
|
|
761
|
+
// measure; the real-model question is whether a model handed the style
|
|
762
|
+
// FOLLOWS it. The deterministic test that covers it drove a scripted
|
|
763
|
+
// model, which proves delivery and nothing about following.
|
|
764
|
+
return (`A deterministic test covers it, but that test drove a SCRIPTED model: ` +
|
|
765
|
+
`it can show the style reached the model, never that a real model ` +
|
|
766
|
+
`follows it.\n` +
|
|
767
|
+
`See the \`test-harness\` skill: compare runs with and without the style ` +
|
|
768
|
+
`(\`measureArms\`), check the output with deterministic checks where the ` +
|
|
769
|
+
`rule has a checkable shape and \`judged\` where it does not, and gate ` +
|
|
770
|
+
`with \`assertRates\`. NOT \`measureTriggerRate\`: a style is not ` +
|
|
771
|
+
`selected by the model, so there is no trigger to measure.`);
|
|
686
772
|
case "hook":
|
|
687
773
|
// Nothing. A hook is deterministic by construction: `runHook` answers
|
|
688
774
|
// "does it block event X?" at the unit tier, free, for every event type โ
|
|
@@ -859,8 +945,8 @@ function formatUntestedReport(report) {
|
|
|
859
945
|
// model budget.
|
|
860
946
|
lines.push(` Two gaps, two costs: ${String(report.harness.untested.length)} with no ` +
|
|
861
947
|
`deterministic harness (free, every push) ยท ` +
|
|
862
|
-
`${String(report.evals.untested.length)}
|
|
863
|
-
`(
|
|
948
|
+
`${String(report.evals.untested.length)} never measured with a real model ` +
|
|
949
|
+
`(paid, run on a schedule).`);
|
|
864
950
|
// What the surfaces that DID pass are resting on.
|
|
865
951
|
if (provenance)
|
|
866
952
|
lines.push(` ${provenance}`);
|
package/dist/test.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export { loadHook } from "./load-hook.js";
|
|
|
62
62
|
export { experimental_hookState } from "./hook-state-store.js";
|
|
63
63
|
export type { HookStateHandle, SeedStateOptions } from "./hook-state-store.js";
|
|
64
64
|
export type { StateFact, Duration } from "./core/hook-state.js";
|
|
65
|
-
export { evalChecks, assertChecks, tool, toolWith, notTool, onlyTools, skill, output, hookFired, received, turns, wrote, didNotWrite, subagent, blocked, allowed, mcp, cost, latency, tokens, inputTokens, outputTokens, cacheTokens, } from "./check.js";
|
|
65
|
+
export { evalChecks, assertChecks, tool, toolWith, notTool, onlyTools, skill, output, experimental_eachReply, experimental_replyCount, hookFired, received, turns, wrote, didNotWrite, subagent, blocked, allowed, mcp, cost, latency, tokens, inputTokens, outputTokens, cacheTokens, } from "./check.js";
|
|
66
66
|
export type { ArgMatcher, Check, CheckJSON, CheckResult, JudgeFn, } from "./check.js";
|
|
67
67
|
export { DISASTER_CATALOG, verifyGuardrail, unblockedDisasters, assertBlocksDisasters, formatGuardrailReport, experimental_alternateSpellings, } from "./guardrail-check.js";
|
|
68
68
|
export type { DisasterEvent, DisasterCategory, GuardrailResult, VerifyGuardrailOptions, } from "./guardrail-check.js";
|
|
@@ -70,6 +70,8 @@ export { experimental_verifyPluginGuards, experimental_formatPluginGuardReport,
|
|
|
70
70
|
export type { PluginGuardReport, SweptHook, SweptHookOutcome, NonCommandHookAction, VerifyPluginGuardsOptions, } from "./verify-plugin-guards.js";
|
|
71
71
|
export * from "./tool-stub.js";
|
|
72
72
|
export { runHarnessTest, runHarness, parseToolCalls, parseSubagents, parseResultEvent, parseOutput, parseHooks, decideSandbox, specTrusted, sandboxAvailable, } from "./harness-test.js";
|
|
73
|
+
export { experimental_outputStyleArms } from "./output-style-arms.js";
|
|
74
|
+
export type { OutputStyleArms } from "./output-style-arms.js";
|
|
73
75
|
export type { HarnessTestSpec, Trace, SubagentTrace, HarnessTestResult, RunHarnessTestOptions, ModelTurn, ModelRequest, ToolCall, HookFire, HarnessTestDriver, SandboxMode, } from "./harness-test.js";
|
|
74
76
|
export { commandsIn, mustInclude, mustNotInclude } from "./doc-commands.js";
|
|
75
77
|
export type { DocCommand } from "./doc-commands.js";
|
package/dist/test.js
CHANGED
|
@@ -71,8 +71,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
71
71
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
72
72
|
};
|
|
73
73
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
-
exports.
|
|
75
|
-
exports.experimental_makeDockerRuntime = exports.experimental_dockerRuntime = exports.experimental_withServices = exports.experimental_startServices = exports.stubSkillBody = exports.parseClaudeRun = exports.formatTriggerRateReport = exports.formatEvalReport = exports.formatCheckReport = exports.checkReportToJUnit = exports.checkPromptDiversity = exports.assertPromptDiversity = exports.assertRates = exports.defineEval = exports.formatContainment = exports.compareContainment = exports.skillContract = exports.mustNotInclude = exports.mustInclude = exports.commandsIn = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.parseHooks = exports.parseOutput = exports.parseResultEvent = void 0;
|
|
74
|
+
exports.runHarness = exports.runHarnessTest = exports.experimental_formatPluginGuardReport = exports.experimental_verifyPluginGuards = exports.experimental_alternateSpellings = exports.formatGuardrailReport = exports.assertBlocksDisasters = exports.unblockedDisasters = exports.verifyGuardrail = exports.DISASTER_CATALOG = exports.cacheTokens = exports.outputTokens = exports.inputTokens = exports.tokens = exports.latency = exports.cost = exports.mcp = exports.allowed = exports.blocked = exports.subagent = exports.didNotWrite = exports.wrote = exports.turns = exports.received = exports.hookFired = exports.experimental_replyCount = exports.experimental_eachReply = exports.output = exports.skill = exports.onlyTools = exports.notTool = exports.toolWith = exports.tool = exports.assertChecks = exports.evalChecks = exports.experimental_hookState = exports.loadHook = exports.experimental_assertEmittedOk = exports.experimental_parseEmitted = exports.experimental_emitTool = exports.egressRoutes = exports.fileToolEvents = exports.propertyHook = exports.decideHook = exports.parseHookOutput = exports.runHook = exports.runScript = exports.cleanupTmpDir = exports.makeTmpDir = exports.recordCheck = void 0;
|
|
75
|
+
exports.experimental_makeDockerRuntime = exports.experimental_dockerRuntime = exports.experimental_withServices = exports.experimental_startServices = exports.stubSkillBody = exports.parseClaudeRun = exports.formatTriggerRateReport = exports.formatEvalReport = exports.formatCheckReport = exports.checkReportToJUnit = exports.checkPromptDiversity = exports.assertPromptDiversity = exports.assertRates = exports.defineEval = exports.formatContainment = exports.compareContainment = exports.skillContract = exports.mustNotInclude = exports.mustInclude = exports.commandsIn = exports.experimental_outputStyleArms = exports.sandboxAvailable = exports.specTrusted = exports.decideSandbox = exports.parseHooks = exports.parseOutput = exports.parseResultEvent = exports.parseSubagents = exports.parseToolCalls = void 0;
|
|
76
76
|
// --- reporting: how much did this script actually do? ---
|
|
77
77
|
// `vigiles test` can otherwise see only an exit code, so a file that runs NOTHING
|
|
78
78
|
// prints the same `โ` as one that ran and passed (measured 2026-08-08 on a file
|
|
@@ -159,6 +159,8 @@ Object.defineProperty(exports, "notTool", { enumerable: true, get: function () {
|
|
|
159
159
|
Object.defineProperty(exports, "onlyTools", { enumerable: true, get: function () { return check_js_1.onlyTools; } });
|
|
160
160
|
Object.defineProperty(exports, "skill", { enumerable: true, get: function () { return check_js_1.skill; } });
|
|
161
161
|
Object.defineProperty(exports, "output", { enumerable: true, get: function () { return check_js_1.output; } });
|
|
162
|
+
Object.defineProperty(exports, "experimental_eachReply", { enumerable: true, get: function () { return check_js_1.experimental_eachReply; } });
|
|
163
|
+
Object.defineProperty(exports, "experimental_replyCount", { enumerable: true, get: function () { return check_js_1.experimental_replyCount; } });
|
|
162
164
|
Object.defineProperty(exports, "hookFired", { enumerable: true, get: function () { return check_js_1.hookFired; } });
|
|
163
165
|
Object.defineProperty(exports, "received", { enumerable: true, get: function () { return check_js_1.received; } });
|
|
164
166
|
Object.defineProperty(exports, "turns", { enumerable: true, get: function () { return check_js_1.turns; } });
|
|
@@ -213,6 +215,10 @@ Object.defineProperty(exports, "parseHooks", { enumerable: true, get: function (
|
|
|
213
215
|
Object.defineProperty(exports, "decideSandbox", { enumerable: true, get: function () { return harness_test_js_1.decideSandbox; } });
|
|
214
216
|
Object.defineProperty(exports, "specTrusted", { enumerable: true, get: function () { return harness_test_js_1.specTrusted; } });
|
|
215
217
|
Object.defineProperty(exports, "sandboxAvailable", { enumerable: true, get: function () { return harness_test_js_1.sandboxAvailable; } });
|
|
218
|
+
// The two arms of an output-style eval, after one free run proves the style
|
|
219
|
+
// reaches the model. Free: the run uses the scripted model.
|
|
220
|
+
var output_style_arms_js_1 = require("./output-style-arms.js");
|
|
221
|
+
Object.defineProperty(exports, "experimental_outputStyleArms", { enumerable: true, get: function () { return output_style_arms_js_1.experimental_outputStyleArms; } });
|
|
216
222
|
// A document's own rule about its own COMMANDS, made checkable. vigiles cannot
|
|
217
223
|
// infer "always pass -g" from prose; what was missing was a cheap way to declare
|
|
218
224
|
// it โ measured at ~95 lines of markdown parsing around ~5 lines of rule, which
|