vigiles 9.0.0 → 9.1.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 +147 -209
- package/dist/audit-report.d.ts +37 -0
- package/dist/audit-report.js +21 -0
- package/dist/audit-report.template.html +37 -22
- package/dist/audit-score.d.ts +27 -7
- package/dist/audit-score.js +48 -45
- package/dist/cli.js +136 -5
- package/dist/core/adopt.d.ts +28 -0
- package/dist/core/adopt.js +203 -0
- package/dist/leaderboard.d.ts +32 -0
- package/dist/leaderboard.js +92 -44
- package/dist/scan.js +13 -1
- package/package.json +1 -1
package/dist/audit-score.d.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* A single structural-health number (the leaderboard's `scoreReport`) ranks
|
|
5
5
|
* plugins, but it hides WHERE a harness is weak. This buckets the SAME
|
|
6
6
|
* deterministic findings into four categories — Truthfulness, Triggering,
|
|
7
|
-
* Structure, Tested — each a 0–100 ring,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Structure, Tested — each a 0–100 ring, as a DIAGNOSTIC breakdown beneath one
|
|
8
|
+
* headline `overall` = `100 − Σ(all graded penalties)` (the SAME summed model as
|
|
9
|
+
* the leaderboard's single health number, via the shared `computeIntegrityScore`
|
|
10
|
+
* — so the two surfaces never disagree). Same detectors, no re-detection
|
|
11
|
+
* (one-detector-no-drift); all deterministic, no execution. (Safety — "do your
|
|
12
|
+
* hooks actually block?" — is NOT an `audit` ring:
|
|
10
13
|
* it requires executing your hooks, which needs cross-platform confinement
|
|
11
14
|
* that isn't shipped yet, so it lives in the `vigiles/testing` API via
|
|
12
15
|
* `guardrail-check`/`assertBlocksDisasters`, where you opt in explicitly.)
|
|
@@ -23,11 +26,25 @@ export interface CategoryScore {
|
|
|
23
26
|
readonly score: number | null;
|
|
24
27
|
/** Relative weight in the overall (equal by default — tune later). */
|
|
25
28
|
readonly weight: number;
|
|
29
|
+
/**
|
|
30
|
+
* Advisory categories are shown but EXCLUDED from the overall grade. An untested
|
|
31
|
+
* surface (or any best-practice gap) is a HARDENING signal, not a broken harness
|
|
32
|
+
* — it must never drag the grade down, so `audit` doesn't read as F on a clean
|
|
33
|
+
* repo that simply hasn't written tests yet. The grade reflects what's BROKEN.
|
|
34
|
+
*/
|
|
35
|
+
readonly advisory?: boolean;
|
|
26
36
|
/** Human-readable deductions / notes, worst first; empty when clean. */
|
|
27
37
|
readonly findings: readonly string[];
|
|
28
38
|
}
|
|
29
39
|
export interface AuditScore {
|
|
30
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The headline score — `100 − Σ(all graded penalties)`, clamped to [0,100]
|
|
42
|
+
* (the SAME summed model as the leaderboard's single health number, computed by
|
|
43
|
+
* the shared {@link computeIntegrityScore}, so the two surfaces never disagree).
|
|
44
|
+
* The per-category rings below are a DIAGNOSTIC breakdown, not the headline: a
|
|
45
|
+
* plugin whose only issue is Structure −30 shows Structure 70 in the breakdown
|
|
46
|
+
* AND overall 70 (averaging the rings would dilute that to ~90). 0 when empty.
|
|
47
|
+
*/
|
|
31
48
|
readonly overall: number;
|
|
32
49
|
readonly grade: PluginScore["grade"];
|
|
33
50
|
readonly categories: readonly CategoryScore[];
|
|
@@ -35,10 +52,13 @@ export interface AuditScore {
|
|
|
35
52
|
readonly empty: boolean;
|
|
36
53
|
}
|
|
37
54
|
/**
|
|
38
|
-
* Bucket a scan report into the four deterministic Lighthouse categories
|
|
39
|
-
*
|
|
55
|
+
* Bucket a scan report into the four deterministic Lighthouse categories as a
|
|
56
|
+
* DIAGNOSTIC breakdown, with the headline `overall` = `100 − Σ(all graded
|
|
57
|
+
* penalties)` (the shared summed model — NOT the average of the rings — so it
|
|
58
|
+
* equals the leaderboard's single health number). The advisory Tested ring and
|
|
59
|
+
* any n/a ring are shown but excluded from the headline.
|
|
40
60
|
*/
|
|
41
61
|
export declare function auditScore(report: ScanReport): AuditScore;
|
|
42
|
-
/** Render the category rings + the
|
|
62
|
+
/** Render the category rings (diagnostic) + the summed overall for the terminal. */
|
|
43
63
|
export declare function formatAuditScore(s: AuditScore): string;
|
|
44
64
|
//# sourceMappingURL=audit-score.d.ts.map
|
package/dist/audit-score.js
CHANGED
|
@@ -8,9 +8,12 @@ exports.formatAuditScore = formatAuditScore;
|
|
|
8
8
|
* A single structural-health number (the leaderboard's `scoreReport`) ranks
|
|
9
9
|
* plugins, but it hides WHERE a harness is weak. This buckets the SAME
|
|
10
10
|
* deterministic findings into four categories — Truthfulness, Triggering,
|
|
11
|
-
* Structure, Tested — each a 0–100 ring,
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Structure, Tested — each a 0–100 ring, as a DIAGNOSTIC breakdown beneath one
|
|
12
|
+
* headline `overall` = `100 − Σ(all graded penalties)` (the SAME summed model as
|
|
13
|
+
* the leaderboard's single health number, via the shared `computeIntegrityScore`
|
|
14
|
+
* — so the two surfaces never disagree). Same detectors, no re-detection
|
|
15
|
+
* (one-detector-no-drift); all deterministic, no execution. (Safety — "do your
|
|
16
|
+
* hooks actually block?" — is NOT an `audit` ring:
|
|
14
17
|
* it requires executing your hooks, which needs cross-platform confinement
|
|
15
18
|
* that isn't shipped yet, so it lives in the `vigiles/testing` API via
|
|
16
19
|
* `guardrail-check`/`assertBlocksDisasters`, where you opt in explicitly.)
|
|
@@ -19,13 +22,9 @@ exports.formatAuditScore = formatAuditScore;
|
|
|
19
22
|
* overall — never a false 0. Pure over the `ScanReport`, so it's fully testable.
|
|
20
23
|
*/
|
|
21
24
|
const leaderboard_js_1 = require("./leaderboard.js");
|
|
22
|
-
// Per-item penalties
|
|
23
|
-
// the single health number
|
|
24
|
-
|
|
25
|
-
const W_NO_DESCRIPTION = 10;
|
|
26
|
-
const W_DANGLING_REF = 8;
|
|
27
|
-
const W_OVERLAP = 8; // a description collision → the wrong skill fires
|
|
28
|
-
const W_NO_CONTRACT = 5;
|
|
25
|
+
// Per-item penalties are the SHARED leaderboard weights (imported above) so the
|
|
26
|
+
// category rings and the single health number can never drift. W_UNTESTED is
|
|
27
|
+
// audit-only — untested surfaces are advisory (shown, never scored into overall).
|
|
29
28
|
const W_UNTESTED = 3;
|
|
30
29
|
/** Apply deductions to a 100 base, clamped to [0,100], collecting non-zero labels. */
|
|
31
30
|
function scoreFrom(deductions) {
|
|
@@ -48,12 +47,12 @@ function truthfulness(r) {
|
|
|
48
47
|
const { score, findings } = scoreFrom([
|
|
49
48
|
{
|
|
50
49
|
n: r.danglingRefs.length,
|
|
51
|
-
weight: W_DANGLING_REF,
|
|
50
|
+
weight: leaderboard_js_1.W_DANGLING_REF,
|
|
52
51
|
label: "broken intra-plugin reference(s)",
|
|
53
52
|
},
|
|
54
53
|
{
|
|
55
54
|
n: missingHooks,
|
|
56
|
-
weight: W_MISSING_HOOK,
|
|
55
|
+
weight: leaderboard_js_1.W_MISSING_HOOK,
|
|
57
56
|
label: "hook script(s) missing (never run)",
|
|
58
57
|
},
|
|
59
58
|
]);
|
|
@@ -64,12 +63,12 @@ function triggering(r) {
|
|
|
64
63
|
const { score, findings } = scoreFrom([
|
|
65
64
|
{
|
|
66
65
|
n: noDesc,
|
|
67
|
-
weight: W_NO_DESCRIPTION,
|
|
66
|
+
weight: leaderboard_js_1.W_NO_DESCRIPTION,
|
|
68
67
|
label: "skill(s) with no usable description (can't trigger)",
|
|
69
68
|
},
|
|
70
69
|
{
|
|
71
70
|
n: r.descriptionOverlaps.length,
|
|
72
|
-
weight: W_OVERLAP,
|
|
71
|
+
weight: leaderboard_js_1.W_OVERLAP,
|
|
73
72
|
label: "near-identical skill description(s) (wrong one fires)",
|
|
74
73
|
},
|
|
75
74
|
]);
|
|
@@ -83,47 +82,47 @@ function structure(r) {
|
|
|
83
82
|
const { score, findings } = scoreFrom([
|
|
84
83
|
{
|
|
85
84
|
n: deadTools,
|
|
86
|
-
weight: W_DANGLING_REF,
|
|
85
|
+
weight: leaderboard_js_1.W_DANGLING_REF,
|
|
87
86
|
label: "agent tool(s) that don't exist (typo / never-available)",
|
|
88
87
|
},
|
|
89
88
|
{
|
|
90
89
|
n: deadMcpTools,
|
|
91
|
-
weight: W_DANGLING_REF,
|
|
90
|
+
weight: leaderboard_js_1.W_DANGLING_REF,
|
|
92
91
|
label: "agent MCP tool(s) whose server isn't declared",
|
|
93
92
|
},
|
|
94
93
|
{
|
|
95
94
|
n: r.hookEventIssues.length,
|
|
96
|
-
weight: W_MISSING_HOOK,
|
|
95
|
+
weight: leaderboard_js_1.W_MISSING_HOOK,
|
|
97
96
|
label: "hook(s) on an unknown event (never fire)",
|
|
98
97
|
},
|
|
99
98
|
{
|
|
100
99
|
n: r.mcpIssues.length,
|
|
101
|
-
weight: W_DANGLING_REF,
|
|
100
|
+
weight: leaderboard_js_1.W_DANGLING_REF,
|
|
102
101
|
label: "MCP server(s) that can't start (no command/url)",
|
|
103
102
|
},
|
|
104
103
|
{
|
|
105
104
|
n: r.mcpHookIssues.length,
|
|
106
|
-
weight: W_DANGLING_REF,
|
|
105
|
+
weight: leaderboard_js_1.W_DANGLING_REF,
|
|
107
106
|
label: "mcp_tool hook(s) incomplete / undeclared server",
|
|
108
107
|
},
|
|
109
108
|
{
|
|
110
109
|
n: r.frontmatterIssues.length,
|
|
111
|
-
weight: W_NO_DESCRIPTION,
|
|
110
|
+
weight: leaderboard_js_1.W_NO_DESCRIPTION,
|
|
112
111
|
label: "surface(s) missing required frontmatter",
|
|
113
112
|
},
|
|
114
113
|
{
|
|
115
114
|
n: r.frontmatterValueIssues.length,
|
|
116
|
-
weight: W_NO_CONTRACT,
|
|
115
|
+
weight: leaderboard_js_1.W_NO_CONTRACT,
|
|
117
116
|
label: "agent(s) with an invalid model/color (silent fallback)",
|
|
118
117
|
},
|
|
119
118
|
{
|
|
120
119
|
n: deadDisallowed,
|
|
121
|
-
weight: W_NO_CONTRACT,
|
|
120
|
+
weight: leaderboard_js_1.W_NO_CONTRACT,
|
|
122
121
|
label: "disallowedTools typo(s) that block nothing",
|
|
123
122
|
},
|
|
124
123
|
{
|
|
125
124
|
n: noContract,
|
|
126
|
-
weight: W_NO_CONTRACT,
|
|
125
|
+
weight: leaderboard_js_1.W_NO_CONTRACT,
|
|
127
126
|
label: "agent(s) inherit all tools (no contract)",
|
|
128
127
|
},
|
|
129
128
|
]);
|
|
@@ -133,26 +132,29 @@ function tested(r) {
|
|
|
133
132
|
const { score, findings } = scoreFrom([
|
|
134
133
|
{ n: r.untested, weight: W_UNTESTED, label: "untested surface(s)" },
|
|
135
134
|
]);
|
|
136
|
-
|
|
135
|
+
// ADVISORY: untested surfaces are a hardening gap, not breakage — shown, but
|
|
136
|
+
// excluded from the overall grade (so a clean-but-untested repo isn't graded F).
|
|
137
|
+
return { key: "Tested", score, weight: 1, advisory: true, findings };
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// surface is the empty machine.
|
|
148
|
-
return surfaces === 0 && !r.mcp && !r.instructions;
|
|
139
|
+
/**
|
|
140
|
+
* An instruction-only repo (just a CLAUDE.md/AGENTS.md, no plugin surface) is NOT
|
|
141
|
+
* empty — the scan records `instructions` precisely so it isn't graded F/0 "no
|
|
142
|
+
* loadable surface". Only a dir with NO instruction file AND no surface is empty.
|
|
143
|
+
* (The shared `isEmptyMachine` ignores `instructions`; audit additionally treats
|
|
144
|
+
* an instruction file as a surface.)
|
|
145
|
+
*/
|
|
146
|
+
function isEmptyAudit(r) {
|
|
147
|
+
return (0, leaderboard_js_1.isEmptyMachine)(r) && !r.instructions;
|
|
149
148
|
}
|
|
150
149
|
/**
|
|
151
|
-
* Bucket a scan report into the four deterministic Lighthouse categories
|
|
152
|
-
*
|
|
150
|
+
* Bucket a scan report into the four deterministic Lighthouse categories as a
|
|
151
|
+
* DIAGNOSTIC breakdown, with the headline `overall` = `100 − Σ(all graded
|
|
152
|
+
* penalties)` (the shared summed model — NOT the average of the rings — so it
|
|
153
|
+
* equals the leaderboard's single health number). The advisory Tested ring and
|
|
154
|
+
* any n/a ring are shown but excluded from the headline.
|
|
153
155
|
*/
|
|
154
156
|
function auditScore(report) {
|
|
155
|
-
if (
|
|
157
|
+
if (isEmptyAudit(report)) {
|
|
156
158
|
const categories = [
|
|
157
159
|
"Truthfulness",
|
|
158
160
|
"Triggering",
|
|
@@ -177,11 +179,11 @@ function auditScore(report) {
|
|
|
177
179
|
structure(report),
|
|
178
180
|
tested(report),
|
|
179
181
|
];
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
// The headline is the SUMMED model (the shared integrity score), NOT the average
|
|
183
|
+
// of the rings — averaging would let a real problem in one category be diluted
|
|
184
|
+
// by clean siblings. The rings above stay a diagnostic breakdown; Tested
|
|
185
|
+
// (advisory) is never summed in (untested surfaces don't drag the grade).
|
|
186
|
+
const { score: overall } = (0, leaderboard_js_1.computeIntegrityScore)((0, leaderboard_js_1.reportDeductions)(report));
|
|
185
187
|
return { overall, grade: (0, leaderboard_js_1.gradeFor)(overall), categories, empty: false };
|
|
186
188
|
}
|
|
187
189
|
// A 22-cell bar gauge ("ring" in the terminal; the real rings are the HTML).
|
|
@@ -202,14 +204,15 @@ function bar(score) {
|
|
|
202
204
|
const filled = Math.round((score / 100) * BAR_CELLS);
|
|
203
205
|
return "█".repeat(filled) + "░".repeat(BAR_CELLS - filled);
|
|
204
206
|
}
|
|
205
|
-
/** Render the category rings + the
|
|
207
|
+
/** Render the category rings (diagnostic) + the summed overall for the terminal. */
|
|
206
208
|
function formatAuditScore(s) {
|
|
207
209
|
const lines = ["Harness audit", ""];
|
|
208
210
|
for (const c of s.categories) {
|
|
209
211
|
const glyph = bandGlyph(c.score);
|
|
210
212
|
const label = c.key.padEnd(13);
|
|
211
213
|
const num = (c.score === null ? "n/a" : String(c.score)).padStart(4);
|
|
212
|
-
|
|
214
|
+
const tag = c.advisory ? " · advisory (not graded)" : "";
|
|
215
|
+
lines.push(` ${glyph} ${label} ${num} ${bar(c.score)}${tag}`);
|
|
213
216
|
if (c.findings.length > 0) {
|
|
214
217
|
lines.push(` └ ${c.findings.join("; ")}`);
|
|
215
218
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1219,6 +1219,100 @@ function targetHasHash(absPath) {
|
|
|
1219
1219
|
* target by `setupPillar1`. The full onboarding (both layers, deps, CI, plugin)
|
|
1220
1220
|
* is `setup()`.
|
|
1221
1221
|
*/
|
|
1222
|
+
/** Classify an adoption target by its path: a `SKILL.md` is a skill, a file under
|
|
1223
|
+
* an `agents/` dir is a subagent, everything else is an instruction file. Used to
|
|
1224
|
+
* pick the right adopt function so `init --target=skills/x/SKILL.md` (the
|
|
1225
|
+
* per-surface path the audit report points at) makes a `skill()`/`agent()` spec. */
|
|
1226
|
+
function surfaceKind(target) {
|
|
1227
|
+
if (/^SKILL\.md$/i.test((0, node_path_1.basename)(target)))
|
|
1228
|
+
return "skill";
|
|
1229
|
+
if (/(^|[/\\])agents[/\\]/.test(target))
|
|
1230
|
+
return "agent";
|
|
1231
|
+
return "instruction";
|
|
1232
|
+
}
|
|
1233
|
+
function logAdoptedSurface(target, specPath, label, unmappedKeys) {
|
|
1234
|
+
const note = unmappedKeys.length > 0
|
|
1235
|
+
? ` (review the // NOTE — unmapped frontmatter: ${unmappedKeys.join(", ")})`
|
|
1236
|
+
: "";
|
|
1237
|
+
console.log(`Adopted ${label} ${target} → ${specPath}${note}. ` +
|
|
1238
|
+
`Run \`vigiles compile\` and review the diff.`);
|
|
1239
|
+
}
|
|
1240
|
+
/** Discover existing skill (`skills/<x>/SKILL.md`) and subagent (`agents/<x>.md`)
|
|
1241
|
+
* surfaces — under the bare or `.claude/` roots — that don't yet have a spec, so
|
|
1242
|
+
* bare `vigiles init` creates a spec for EVERY surface it can, not just the
|
|
1243
|
+
* instruction file. Shallow (top-level only) so it never walks node_modules or a
|
|
1244
|
+
* vendored plugin. CC paths are intentional here — `init` is the one composition
|
|
1245
|
+
* point allowed to know them (see adapter-aware-lint-rules). */
|
|
1246
|
+
function discoverAdoptableSurfaces(cwd) {
|
|
1247
|
+
const out = [];
|
|
1248
|
+
const unspecced = (rel) => (0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, rel)) &&
|
|
1249
|
+
!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, `${rel}.spec.ts`));
|
|
1250
|
+
for (const root of ["skills", ".claude/skills"]) {
|
|
1251
|
+
const abs = (0, node_path_1.resolve)(cwd, root);
|
|
1252
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
1253
|
+
continue;
|
|
1254
|
+
for (const e of (0, node_fs_1.readdirSync)(abs, { withFileTypes: true })) {
|
|
1255
|
+
const rel = `${root}/${e.name}/SKILL.md`;
|
|
1256
|
+
if (e.isDirectory() && unspecced(rel))
|
|
1257
|
+
out.push(rel);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
for (const root of ["agents", ".claude/agents"]) {
|
|
1261
|
+
const abs = (0, node_path_1.resolve)(cwd, root);
|
|
1262
|
+
if (!(0, node_fs_1.existsSync)(abs))
|
|
1263
|
+
continue;
|
|
1264
|
+
for (const e of (0, node_fs_1.readdirSync)(abs, { withFileTypes: true })) {
|
|
1265
|
+
const rel = `${root}/${e.name}`;
|
|
1266
|
+
if (e.isFile() && e.name.endsWith(".md") && unspecced(rel))
|
|
1267
|
+
out.push(rel);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return out;
|
|
1271
|
+
}
|
|
1272
|
+
/** The full adoptable-surface list `audit` reports: the instruction file (when it
|
|
1273
|
+
* exists hand-written, no spec) PLUS every skill/subagent surface without a spec.
|
|
1274
|
+
* Same notion `init` adopts; surfaced in the AuditReport + the terminal nudge so
|
|
1275
|
+
* the report's "Create spec" / "Create all specs" affordances have their paths.
|
|
1276
|
+
* Composition-root only — CC paths are intentional here (like discoverAdoptableSurfaces). */
|
|
1277
|
+
function discoverAdoptableForAudit(root, instructionFile) {
|
|
1278
|
+
const out = [];
|
|
1279
|
+
const instrAbs = (0, node_path_1.resolve)(root, instructionFile);
|
|
1280
|
+
if ((0, node_fs_1.existsSync)(instrAbs) &&
|
|
1281
|
+
!targetHasHash(instrAbs) &&
|
|
1282
|
+
!(0, node_fs_1.existsSync)((0, node_path_1.resolve)(root, `${instructionFile}.spec.ts`))) {
|
|
1283
|
+
out.push(instructionFile);
|
|
1284
|
+
}
|
|
1285
|
+
out.push(...discoverAdoptableSurfaces(root));
|
|
1286
|
+
return out;
|
|
1287
|
+
}
|
|
1288
|
+
/** The terminal "adoptable surfaces" nudge — N un-spec'd surfaces + the create-all
|
|
1289
|
+
* command and up to ~5 per-surface commands (then "+K more"). "" when nothing to
|
|
1290
|
+
* adopt (a fully spec-managed repo says nothing). */
|
|
1291
|
+
function formatAdoptableNudge(surfaces) {
|
|
1292
|
+
if (surfaces.length === 0)
|
|
1293
|
+
return "";
|
|
1294
|
+
const n = surfaces.length;
|
|
1295
|
+
const lines = [
|
|
1296
|
+
`ℹ ${String(n)} surface${n === 1 ? "" : "s"} not yet spec-managed — create specs with \`npx vigiles init\``,
|
|
1297
|
+
` (or one at a time: \`npx vigiles init --target=<path>\`)`,
|
|
1298
|
+
];
|
|
1299
|
+
const shown = surfaces.slice(0, 5);
|
|
1300
|
+
for (const s of shown)
|
|
1301
|
+
lines.push(` • npx vigiles init --target=${s}`);
|
|
1302
|
+
const more = n - shown.length;
|
|
1303
|
+
if (more > 0)
|
|
1304
|
+
lines.push(` • +${String(more)} more`);
|
|
1305
|
+
return lines.join("\n");
|
|
1306
|
+
}
|
|
1307
|
+
/** A small, terse behavioral nudge — the deterministic read can't tell whether a
|
|
1308
|
+
* skill actually FIRES. "" when there are no model-invocable skills. */
|
|
1309
|
+
function formatTriggerNudge(triggerableSkills) {
|
|
1310
|
+
if (triggerableSkills <= 0)
|
|
1311
|
+
return "";
|
|
1312
|
+
const n = triggerableSkills;
|
|
1313
|
+
return (`ℹ Do your ${String(n)} skill${n === 1 ? "" : "s"} actually fire? The deterministic read can't tell — ` +
|
|
1314
|
+
`run \`audit\` interactively to measure, or test with \`measureTriggerRate\` (vigiles/testing).`);
|
|
1315
|
+
}
|
|
1222
1316
|
function scaffoldSpec(args) {
|
|
1223
1317
|
const targetFlag = args.find((a) => a.startsWith("--target="));
|
|
1224
1318
|
const target = targetFlag ? targetFlag.split("=")[1] : "CLAUDE.md";
|
|
@@ -1237,11 +1331,24 @@ function scaffoldSpec(args) {
|
|
|
1237
1331
|
const targetAbs = (0, node_path_1.resolve)(process.cwd(), target);
|
|
1238
1332
|
if ((0, node_fs_1.existsSync)(targetAbs) && !targetHasHash(targetAbs)) {
|
|
1239
1333
|
const md = (0, node_fs_1.readFileSync)(targetAbs, "utf-8");
|
|
1240
|
-
const { source, tier, sectionCount } = (0, adopt_js_1.adoptMarkdown)(md, (0, node_path_1.basename)(target));
|
|
1241
1334
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(specAbs), { recursive: true });
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1335
|
+
const kind = surfaceKind(target);
|
|
1336
|
+
if (kind === "skill") {
|
|
1337
|
+
const { source, unmappedKeys } = (0, adopt_js_1.adoptSkill)(md, (0, node_path_1.basename)((0, node_path_1.dirname)(target)));
|
|
1338
|
+
(0, node_fs_1.writeFileSync)(specAbs, source);
|
|
1339
|
+
logAdoptedSurface(target, specPath, "skill", unmappedKeys);
|
|
1340
|
+
}
|
|
1341
|
+
else if (kind === "agent") {
|
|
1342
|
+
const { source, unmappedKeys } = (0, adopt_js_1.adoptAgent)(md, (0, node_path_1.basename)(target, ".md"));
|
|
1343
|
+
(0, node_fs_1.writeFileSync)(specAbs, source);
|
|
1344
|
+
logAdoptedSurface(target, specPath, "subagent", unmappedKeys);
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
const { source, tier, sectionCount } = (0, adopt_js_1.adoptMarkdown)(md, (0, node_path_1.basename)(target));
|
|
1348
|
+
(0, node_fs_1.writeFileSync)(specAbs, source);
|
|
1349
|
+
console.log(`Adopted ${target} → ${specPath} (${tier}, ${String(sectionCount)} section${sectionCount === 1 ? "" : "s"}). ` +
|
|
1350
|
+
`Run \`vigiles compile\` and review the diff; the \`/strengthen\` skill upgrades prose to verified rules.`);
|
|
1351
|
+
}
|
|
1245
1352
|
return;
|
|
1246
1353
|
}
|
|
1247
1354
|
// The compiled output is derived from the spec FILE path; the spec's `target`
|
|
@@ -1768,9 +1875,15 @@ async function setupPillar1(detected, targetValue, harnesses) {
|
|
|
1768
1875
|
// An explicit --target is honoured as-is; otherwise collapse a CLAUDE.md⇄
|
|
1769
1876
|
// AGENTS.md mirror (symlink or synced) to one canonical spec, then redirect
|
|
1770
1877
|
// into a sync tool's source slot when one would own the output.
|
|
1771
|
-
const
|
|
1878
|
+
const instructionTargets = targetValue
|
|
1772
1879
|
? determineTargets(detected, targetValue, harnesses)
|
|
1773
1880
|
: redirectSyncToolTargets(cwd, collapseMirroredTargets(determineTargets(detected, targetValue, harnesses), (0, compose_js_1.detectInstructionMirror)(cwd)));
|
|
1881
|
+
// Bare `init` (no explicit --target) also adopts every existing skill +
|
|
1882
|
+
// subagent surface — "create all the specs it can", not just the instruction
|
|
1883
|
+
// file. An explicit --target stays scoped to that one surface.
|
|
1884
|
+
const targets = targetValue
|
|
1885
|
+
? instructionTargets
|
|
1886
|
+
: [...instructionTargets, ...discoverAdoptableSurfaces(cwd)];
|
|
1774
1887
|
// Create specs. An existing hand-written target is faithfully ADOPTED into a
|
|
1775
1888
|
// spec (scaffoldSpec() does the convert), not clobbered with a blank one — so the
|
|
1776
1889
|
// compile below reproduces it (the user reviews the diff). A greenfield target
|
|
@@ -4383,9 +4496,15 @@ async function main() {
|
|
|
4383
4496
|
// HTML renders, `--json` emits, and (later) a hosted dashboard ingests.
|
|
4384
4497
|
// Built ONCE; the rings + fix list are read off it. Pure deterministic —
|
|
4385
4498
|
// nothing executes to produce it.
|
|
4499
|
+
// Surfaces that exist but aren't spec-managed yet — the same notion
|
|
4500
|
+
// `init` adopts (layout-driven instruction file + skill/subagent sweep).
|
|
4501
|
+
// Surfaced in the AuditReport (the report's "Create spec" command-emit
|
|
4502
|
+
// buttons read it) and the terminal nudge below.
|
|
4503
|
+
const adoptableSurfaces = discoverAdoptableForAudit(root, adapter.layout.instructionFile);
|
|
4386
4504
|
const auditReport = (0, audit_report_js_1.buildAuditReport)(report, {
|
|
4387
4505
|
harness: adapter.name,
|
|
4388
4506
|
vigilesVersion: getVersion(),
|
|
4507
|
+
adoptableSurfaces,
|
|
4389
4508
|
});
|
|
4390
4509
|
const sc = auditReport.score;
|
|
4391
4510
|
const plan = (0, optimize_js_1.optimize)(report);
|
|
@@ -4404,6 +4523,18 @@ async function main() {
|
|
|
4404
4523
|
const fixes = (0, optimize_js_1.formatRecommendations)(plan);
|
|
4405
4524
|
if (fixes)
|
|
4406
4525
|
console.log("\n" + fixes);
|
|
4526
|
+
// Adoption nudge: surfaces that exist but aren't spec-managed yet, with
|
|
4527
|
+
// the create-all + per-surface `init` commands (the JSON carries the
|
|
4528
|
+
// data in `adoptable` instead — the terminal stays human-readable).
|
|
4529
|
+
const adoptNudge = formatAdoptableNudge(adoptableSurfaces);
|
|
4530
|
+
if (adoptNudge)
|
|
4531
|
+
console.log("\n" + adoptNudge);
|
|
4532
|
+
// A small behavioral nudge — the deterministic read can't tell whether
|
|
4533
|
+
// skills actually FIRE; point at the interactive measure + the API.
|
|
4534
|
+
const fireNudge = formatTriggerNudge(report.skills.filter((s) => s.hasDescription && !s.userInvoked)
|
|
4535
|
+
.length);
|
|
4536
|
+
if (fireNudge)
|
|
4537
|
+
console.log("\n" + fireNudge);
|
|
4407
4538
|
}
|
|
4408
4539
|
// ONE read-vs-run decision for the EXECUTING checks (live MCP + skill
|
|
4409
4540
|
// firing). A plain `audit` is a deterministic READ; these run only on
|
package/dist/core/adopt.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* vs `enforce()` is deliberately NOT guessed here — that cross-referencing is
|
|
19
19
|
* `strengthen`'s separate, later job; adoption is lossless transcription.
|
|
20
20
|
*/
|
|
21
|
+
import { type SkillSpec, type AgentSpec } from "./spec.js";
|
|
21
22
|
export type AdoptTier = "structured" | "raw";
|
|
22
23
|
export interface AdoptResult {
|
|
23
24
|
/** Generated `.spec.ts` source (compiles back to ~the original file). */
|
|
@@ -62,4 +63,31 @@ export declare function adoptToSpec(markdown: string, target: string): AdoptedSp
|
|
|
62
63
|
* (the deliverable `init` writes).
|
|
63
64
|
*/
|
|
64
65
|
export declare function adoptMarkdown(markdown: string, target: string): AdoptResult;
|
|
66
|
+
export interface AdoptSurfaceResult {
|
|
67
|
+
/** Generated `.spec.ts` source. */
|
|
68
|
+
source: string;
|
|
69
|
+
kind: "skill" | "agent";
|
|
70
|
+
/**
|
|
71
|
+
* The parsed spec object the source builds — exposed so a round-trip test can
|
|
72
|
+
* feed it straight to `compileSkill`/`compileAgent` without evaluating the
|
|
73
|
+
* generated TS (the same split as `adoptToSpec`/`renderSpecSource`).
|
|
74
|
+
*/
|
|
75
|
+
spec: SkillSpec | AgentSpec;
|
|
76
|
+
/**
|
|
77
|
+
* Frontmatter keys present in the source that the typed spec has no field for
|
|
78
|
+
* — emitted as a `// NOTE:` comment in the source so nothing is lost silently.
|
|
79
|
+
*/
|
|
80
|
+
unmappedKeys: string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Adopt an existing SKILL.md into a `skill()` spec. The body is carried verbatim
|
|
84
|
+
* (skills are freeform markdown — `##` headings stay in the body), so a clean
|
|
85
|
+
* skill round-trips below the integrity header.
|
|
86
|
+
*
|
|
87
|
+
* @param markdown the SKILL.md content
|
|
88
|
+
* @param dirName the skill's directory name — the CC fallback for `name` when
|
|
89
|
+
* frontmatter omits it
|
|
90
|
+
*/
|
|
91
|
+
export declare function adoptSkill(markdown: string, dirName: string): AdoptSurfaceResult;
|
|
92
|
+
export declare function adoptAgent(markdown: string, fileBase: string): AdoptSurfaceResult;
|
|
65
93
|
//# sourceMappingURL=adopt.d.ts.map
|