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
package/dist/audit-score.js
CHANGED
|
@@ -450,12 +450,12 @@ function evaluated(r, firingMeasured) {
|
|
|
450
450
|
score: null,
|
|
451
451
|
weight: 1,
|
|
452
452
|
advisory: true,
|
|
453
|
-
findings: ["no surface
|
|
453
|
+
findings: ["no surface a real model could be measured on"],
|
|
454
454
|
};
|
|
455
455
|
}
|
|
456
456
|
const unevaluated = r.unevaluated ?? evaluable;
|
|
457
457
|
const evalCovered = evaluable - unevaluated;
|
|
458
|
-
const gap = `${String(unevaluated)} ${pluralizeLabel(unevaluated, "surface(s)
|
|
458
|
+
const gap = `${String(unevaluated)} ${pluralizeLabel(unevaluated, "surface(s) never measured with a real model")}`;
|
|
459
459
|
// Nothing on disk measures firing AND nothing ran this session → the question
|
|
460
460
|
// was never asked. Say that, and name the command — do NOT score it a 0.
|
|
461
461
|
if (evalCovered === 0 && !firingMeasured) {
|
package/dist/check.d.ts
CHANGED
|
@@ -137,6 +137,26 @@ export declare function onlyTools(allowed: readonly string[]): Check<Trace>;
|
|
|
137
137
|
export declare function skill(id: string): Check<Trace>;
|
|
138
138
|
/** The agent's final output contains a substring / matches a RegExp. */
|
|
139
139
|
export declare function output(matcher: string | RegExp): Check<Trace>;
|
|
140
|
+
/**
|
|
141
|
+
* EVERY reply of the run contains a substring / matches a RegExp — for a rule
|
|
142
|
+
* about each reply ("end with a status block"), where `output` sees only the
|
|
143
|
+
* last one. A run with no replies fails: there is nothing the rule held for.
|
|
144
|
+
*
|
|
145
|
+
* @experimental The matcher may grow a function form, and a check may come to
|
|
146
|
+
* be attached to a spec rule (vigiles#323).
|
|
147
|
+
*/
|
|
148
|
+
export declare function experimental_eachReply(matcher: string | Readonly<RegExp>): Readonly<Check<Trace>>;
|
|
149
|
+
/**
|
|
150
|
+
* How many replies of the run match, within bounds — `{ max: 1 }` is "at most
|
|
151
|
+
* one status block for one user message", which a Stop hook that makes the
|
|
152
|
+
* agent reply again can break while `output` still shows one.
|
|
153
|
+
*
|
|
154
|
+
* @experimental See {@link experimental_eachReply}.
|
|
155
|
+
*/
|
|
156
|
+
export declare function experimental_replyCount(matcher: string | Readonly<RegExp>, opts: {
|
|
157
|
+
readonly min?: number;
|
|
158
|
+
readonly max?: number;
|
|
159
|
+
}): Readonly<Check<Trace>>;
|
|
140
160
|
/** A hook fired for this event (e.g. `"PreToolUse"`, `"Stop"`). */
|
|
141
161
|
export declare function hookFired(event: string): Check<Trace>;
|
|
142
162
|
/**
|
package/dist/check.js
CHANGED
|
@@ -8,6 +8,8 @@ exports.notTool = notTool;
|
|
|
8
8
|
exports.onlyTools = onlyTools;
|
|
9
9
|
exports.skill = skill;
|
|
10
10
|
exports.output = output;
|
|
11
|
+
exports.experimental_eachReply = experimental_eachReply;
|
|
12
|
+
exports.experimental_replyCount = experimental_replyCount;
|
|
11
13
|
exports.hookFired = hookFired;
|
|
12
14
|
exports.received = received;
|
|
13
15
|
exports.turns = turns;
|
|
@@ -75,6 +77,29 @@ function truncate(s, n = 120) {
|
|
|
75
77
|
const flat = s.replace(/\s+/g, " ").trim();
|
|
76
78
|
return flat.length > n ? `${flat.slice(0, n)}…` : flat;
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Does `text` match? A RegExp is copied first: with `g` / `y`, `test` advances
|
|
82
|
+
* `lastIndex` on the object it is called on, so reusing the caller's object
|
|
83
|
+
* would miss on its second reply or its second trial.
|
|
84
|
+
*/
|
|
85
|
+
const matches = (matcher, text) => typeof matcher === "string"
|
|
86
|
+
? text.includes(matcher)
|
|
87
|
+
: new RegExp(matcher).test(text);
|
|
88
|
+
/** A matcher as the report prints it: the string, or the RegExp literal. */
|
|
89
|
+
const show = (matcher) => typeof matcher === "string" ? matcher : `/${matcher.source}/${matcher.flags}`;
|
|
90
|
+
/** Why a reply check cannot run: the trace carries no replies to read. */
|
|
91
|
+
const NO_REPLIES = "the run has no `replies` (the harness does not tell replies apart, or the run was not streamed — pass `transcript: true`)";
|
|
92
|
+
/** Why a `{ min, max }` bound cannot mean anything, or null when it can. */
|
|
93
|
+
function boundsProblem(opts) {
|
|
94
|
+
const given = [opts.min, opts.max].filter((n) => n !== undefined);
|
|
95
|
+
if (given.length === 0)
|
|
96
|
+
return "give `min`, `max` or both";
|
|
97
|
+
if (given.some((n) => !Number.isInteger(n) || n < 0))
|
|
98
|
+
return "bounds are whole numbers, 0 or more";
|
|
99
|
+
if (opts.min !== undefined && opts.max !== undefined && opts.min > opts.max)
|
|
100
|
+
return `min ${String(opts.min)} is above max ${String(opts.max)}`;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
78
103
|
// ---------------------------------------------------------------------------
|
|
79
104
|
// Trace checks (an agent run — runHarness / runEval)
|
|
80
105
|
// ---------------------------------------------------------------------------
|
|
@@ -248,7 +273,7 @@ function output(matcher) {
|
|
|
248
273
|
return {
|
|
249
274
|
kind: "output",
|
|
250
275
|
eval: (t) => {
|
|
251
|
-
const pass =
|
|
276
|
+
const pass = matches(matcher, t.output);
|
|
252
277
|
return pass
|
|
253
278
|
? ok(`output matched ${String(matcher)}`)
|
|
254
279
|
: no(`expected output to ${isRe ? "match" : "contain"} ${String(matcher)}; got "${truncate(t.output) || "(empty)"}"`);
|
|
@@ -256,6 +281,66 @@ function output(matcher) {
|
|
|
256
281
|
toJSON: () => ({ kind: "output", matcher: String(matcher), regex: isRe }),
|
|
257
282
|
};
|
|
258
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* EVERY reply of the run contains a substring / matches a RegExp — for a rule
|
|
286
|
+
* about each reply ("end with a status block"), where `output` sees only the
|
|
287
|
+
* last one. A run with no replies fails: there is nothing the rule held for.
|
|
288
|
+
*
|
|
289
|
+
* @experimental The matcher may grow a function form, and a check may come to
|
|
290
|
+
* be attached to a spec rule (vigiles#323).
|
|
291
|
+
*/
|
|
292
|
+
function experimental_eachReply(matcher) {
|
|
293
|
+
return {
|
|
294
|
+
kind: "eachReply",
|
|
295
|
+
eval: (t) => {
|
|
296
|
+
if (t.replies === undefined)
|
|
297
|
+
return no(NO_REPLIES);
|
|
298
|
+
if (t.replies.length === 0)
|
|
299
|
+
return no("the run wrote no replies");
|
|
300
|
+
const miss = t.replies.findIndex((r) => !matches(matcher, r));
|
|
301
|
+
return miss === -1
|
|
302
|
+
? ok(`all ${String(t.replies.length)} replies matched ${show(matcher)}`)
|
|
303
|
+
: no(`reply ${String(miss + 1)} of ${String(t.replies.length)} did not match ${show(matcher)}: "${truncate(t.replies[miss] ?? "") || "(empty)"}"`);
|
|
304
|
+
},
|
|
305
|
+
toJSON: () => ({
|
|
306
|
+
kind: "eachReply",
|
|
307
|
+
matcher: show(matcher),
|
|
308
|
+
regex: typeof matcher !== "string",
|
|
309
|
+
}),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* How many replies of the run match, within bounds — `{ max: 1 }` is "at most
|
|
314
|
+
* one status block for one user message", which a Stop hook that makes the
|
|
315
|
+
* agent reply again can break while `output` still shows one.
|
|
316
|
+
*
|
|
317
|
+
* @experimental See {@link experimental_eachReply}.
|
|
318
|
+
*/
|
|
319
|
+
function experimental_replyCount(matcher, opts) {
|
|
320
|
+
const problem = boundsProblem(opts);
|
|
321
|
+
if (problem !== null)
|
|
322
|
+
throw new Error(`replyCount: ${problem}`);
|
|
323
|
+
return {
|
|
324
|
+
kind: "replyCount",
|
|
325
|
+
eval: (t) => {
|
|
326
|
+
if (t.replies === undefined)
|
|
327
|
+
return no(NO_REPLIES);
|
|
328
|
+
const n = t.replies.filter((r) => matches(matcher, r)).length;
|
|
329
|
+
const pass = (opts.min === undefined || n >= opts.min) &&
|
|
330
|
+
(opts.max === undefined || n <= opts.max);
|
|
331
|
+
const bound = `min ${String(opts.min ?? "-")}, max ${String(opts.max ?? "-")}`;
|
|
332
|
+
const says = `${String(n)} of ${String(t.replies.length)} replies matched ${show(matcher)} (${bound})`;
|
|
333
|
+
return pass ? ok(says) : no(`expected ${bound}; ${says}`);
|
|
334
|
+
},
|
|
335
|
+
toJSON: () => ({
|
|
336
|
+
kind: "replyCount",
|
|
337
|
+
matcher: show(matcher),
|
|
338
|
+
regex: typeof matcher !== "string",
|
|
339
|
+
min: opts.min,
|
|
340
|
+
max: opts.max,
|
|
341
|
+
}),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
259
344
|
/** A hook fired for this event (e.g. `"PreToolUse"`, `"Stop"`). */
|
|
260
345
|
function hookFired(event) {
|
|
261
346
|
return {
|
|
@@ -284,7 +369,7 @@ function received(matcher) {
|
|
|
284
369
|
const text = t.modelRequests
|
|
285
370
|
.map((r) => `${r.system} ${r.messages.map((m) => m.text).join(" ")}`)
|
|
286
371
|
.join(" ");
|
|
287
|
-
const pass =
|
|
372
|
+
const pass = matches(matcher, text);
|
|
288
373
|
return pass
|
|
289
374
|
? ok(`the model received ${String(matcher)}`)
|
|
290
375
|
: no(`expected the model to receive ${String(matcher)} (a slash-command expansion or injected context); ${t.modelRequests.length === 0 ? "no requests captured (eval tier captures none — use runHarness)" : `got "${truncate(text)}"`}`);
|
package/dist/cli-main.js
CHANGED
|
@@ -39,11 +39,13 @@ const frame_js_1 = require("./core/frame.js");
|
|
|
39
39
|
const surface_discovery_js_1 = require("./core/surface-discovery.js");
|
|
40
40
|
const scan_trigger_suggest_js_1 = require("./scan-trigger-suggest.js");
|
|
41
41
|
const install_reader_js_1 = require("./core/install-reader.js");
|
|
42
|
+
const skill_links_js_1 = require("./skill-links.js");
|
|
42
43
|
const plugin_declaration_js_1 = require("./plugin-declaration.js");
|
|
43
44
|
const local_files_tracked_js_1 = require("./local-files-tracked.js");
|
|
44
45
|
const local_files_js_1 = require("./local-files.js");
|
|
45
46
|
const scan_behavioral_js_1 = require("./scan-behavioral.js");
|
|
46
47
|
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
48
|
+
const layout_js_1 = require("./core/layout.js");
|
|
47
49
|
const skill_harness_js_1 = require("./skill-harness.js");
|
|
48
50
|
const leaderboard_js_1 = require("./leaderboard.js");
|
|
49
51
|
const optimize_js_1 = require("./optimize.js");
|
|
@@ -3177,29 +3179,226 @@ function reportInstall(plan, outcome) {
|
|
|
3177
3179
|
}
|
|
3178
3180
|
/**
|
|
3179
3181
|
* Install vigiles's skills/hooks for the chosen harness(es) via the per-harness
|
|
3180
|
-
* `planPluginInstall` decision
|
|
3181
|
-
*
|
|
3182
|
-
*
|
|
3182
|
+
* `planPluginInstall` decision. Two carriers: the SKILLS are linked into the
|
|
3183
|
+
* repo (`linkVigilesSkills`), so every clone and container has them after
|
|
3184
|
+
* `npm install`; the GLOBAL install runs where it carries something the links
|
|
3185
|
+
* cannot (`shouldRunGlobalInstall` — Claude Code's plugin carries the hooks).
|
|
3186
|
+
* The decisions are pure and unit-tested; this is the thin IO.
|
|
3187
|
+
*
|
|
3188
|
+
* Returns the links it created or relinked, for the commit hint.
|
|
3183
3189
|
*/
|
|
3184
3190
|
function installPlugins(harnesses) {
|
|
3185
3191
|
const { execSync: exec } = require("node:child_process");
|
|
3186
3192
|
const plans = (0, setup_plan_js_1.planPluginInstall)(harnesses, {
|
|
3187
3193
|
hasClaude: harnesses.includes("claude") && harnessBinaryPresent("claude"),
|
|
3188
3194
|
});
|
|
3189
|
-
|
|
3195
|
+
const written = plans.flatMap((plan) => {
|
|
3190
3196
|
console.log("");
|
|
3191
|
-
|
|
3192
|
-
|
|
3197
|
+
const home = plan.linkSkills ? harnessSkillsHome(plan.harness) : null;
|
|
3198
|
+
const outcome = home === null ? null : linkVigilesSkills(home, process.cwd());
|
|
3199
|
+
if (outcome !== null)
|
|
3200
|
+
(0, skill_links_js_1.formatSkillLinks)(outcome).forEach((l) => {
|
|
3201
|
+
console.log(l);
|
|
3202
|
+
});
|
|
3203
|
+
const linked = outcome !== null && (0, skill_links_js_1.skillLinksUsable)(outcome);
|
|
3204
|
+
if ((0, setup_plan_js_1.shouldRunGlobalInstall)(plan, linked))
|
|
3205
|
+
reportInstall(plan, runInstall(plan, exec));
|
|
3206
|
+
else
|
|
3207
|
+
console.log(` No global ${plan.harness} skills install — the linked skills replace it, in every clone.`);
|
|
3208
|
+
return outcome === null ? [] : changedLinks(outcome);
|
|
3209
|
+
});
|
|
3193
3210
|
// Claude Code gets its hooks from the global marketplace plugin; Codex has no
|
|
3194
3211
|
// global store, so wire vigiles's proactive nudge hooks into the repo's
|
|
3195
3212
|
// .codex/config.toml directly (the idiomatic, repo-committed place).
|
|
3196
3213
|
if (harnesses.includes("codex"))
|
|
3197
3214
|
wireCodexHooks();
|
|
3198
3215
|
// Claude Code additionally gets a committed DECLARATION, so a collaborator who
|
|
3199
|
-
// clones and never runs `init` is told the project wants this plugin
|
|
3200
|
-
//
|
|
3216
|
+
// clones and never runs `init` is told the project wants this plugin (its
|
|
3217
|
+
// hooks) instead of silence. It does not install anything.
|
|
3201
3218
|
if (harnesses.includes("claude"))
|
|
3202
3219
|
declareVigilesPlugin();
|
|
3220
|
+
return written;
|
|
3221
|
+
}
|
|
3222
|
+
/** The repo-relative skills home of the harness `init` calls `name`, or null. */
|
|
3223
|
+
function harnessSkillsHome(name) {
|
|
3224
|
+
const adapter = (0, adapter_registry_js_1.getAdapter)(name);
|
|
3225
|
+
return adapter === undefined ? null : (0, layout_js_1.skillsHome)(adapter.layout);
|
|
3226
|
+
}
|
|
3227
|
+
/** The repo-relative paths of the links this run created or rewrote — the ones git sees change. */
|
|
3228
|
+
function changedLinks(outcome) {
|
|
3229
|
+
return outcome.kind === "linked"
|
|
3230
|
+
? outcome.results
|
|
3231
|
+
.filter((r) => r.status === "created" || r.status === "relinked")
|
|
3232
|
+
.map((r) => `${outcome.physicalHome}/${r.name}`)
|
|
3233
|
+
: [];
|
|
3234
|
+
}
|
|
3235
|
+
/** The manifest file name the skill-linking code reads. */
|
|
3236
|
+
const PACKAGE_JSON = "package.json";
|
|
3237
|
+
/** One top-level field of `<dir>/package.json`, or undefined. Never throws. */
|
|
3238
|
+
function packageField(dir, field) {
|
|
3239
|
+
try {
|
|
3240
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, PACKAGE_JSON), "utf-8"));
|
|
3241
|
+
return typeof pkg === "object" && pkg !== null
|
|
3242
|
+
? Object.entries(pkg).find(([k]) => k === field)?.[1]
|
|
3243
|
+
: undefined;
|
|
3244
|
+
}
|
|
3245
|
+
catch {
|
|
3246
|
+
return undefined;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
/** `package.json`'s `name`, or null. Never throws. */
|
|
3250
|
+
function packageNameAt(dir) {
|
|
3251
|
+
const name = packageField(dir, "name");
|
|
3252
|
+
return typeof name === "string" ? name : null;
|
|
3253
|
+
}
|
|
3254
|
+
/** A thrown value's message. */
|
|
3255
|
+
function errorText(e) {
|
|
3256
|
+
return e instanceof Error ? e.message : String(e);
|
|
3257
|
+
}
|
|
3258
|
+
/** A path's real location, or null when it does not resolve. Never throws. */
|
|
3259
|
+
function realOrNull(path) {
|
|
3260
|
+
try {
|
|
3261
|
+
return (0, node_fs_1.realpathSync)(path);
|
|
3262
|
+
}
|
|
3263
|
+
catch {
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
/** What occupies `path`, for `planSkillLinks`. */
|
|
3268
|
+
function observeSkillEntry(path) {
|
|
3269
|
+
const st = (0, node_fs_1.lstatSync)(path, { throwIfNoEntry: false });
|
|
3270
|
+
if (st === undefined)
|
|
3271
|
+
return { kind: "missing" };
|
|
3272
|
+
if (st.isSymbolicLink())
|
|
3273
|
+
return {
|
|
3274
|
+
kind: "link",
|
|
3275
|
+
target: (0, node_fs_1.readlinkSync)(path),
|
|
3276
|
+
resolvesTo: realOrNull(path),
|
|
3277
|
+
};
|
|
3278
|
+
return st.isDirectory() ? { kind: "directory" } : { kind: "file" };
|
|
3279
|
+
}
|
|
3280
|
+
const LINK_IO = {
|
|
3281
|
+
// "dir" matters only on Windows, where a directory link must say so.
|
|
3282
|
+
symlink: (target, path) => {
|
|
3283
|
+
(0, node_fs_1.symlinkSync)(target, path, "dir");
|
|
3284
|
+
},
|
|
3285
|
+
rename: node_fs_1.renameSync,
|
|
3286
|
+
unlink: node_fs_1.unlinkSync,
|
|
3287
|
+
};
|
|
3288
|
+
/** Carry out one decision. A failed `symlink` is reported, never thrown. */
|
|
3289
|
+
function applySkillLink(decision, absHome) {
|
|
3290
|
+
switch (decision.action) {
|
|
3291
|
+
case "keep":
|
|
3292
|
+
return { name: decision.name, status: "present" };
|
|
3293
|
+
case "refuse":
|
|
3294
|
+
return {
|
|
3295
|
+
name: decision.name,
|
|
3296
|
+
status: "skipped",
|
|
3297
|
+
reason: decision.reason,
|
|
3298
|
+
};
|
|
3299
|
+
case "create":
|
|
3300
|
+
case "replace":
|
|
3301
|
+
try {
|
|
3302
|
+
// Only a link `decideSkillLink` proved ours is ever replaced.
|
|
3303
|
+
(0, skill_links_js_1.writeSkillLink)(LINK_IO, (0, node_path_1.join)(absHome, decision.name), decision.target, decision.action);
|
|
3304
|
+
return {
|
|
3305
|
+
name: decision.name,
|
|
3306
|
+
status: decision.action === "replace" ? "relinked" : "created",
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
catch (e) {
|
|
3310
|
+
const code = e instanceof Error && "code" in e && typeof e.code === "string"
|
|
3311
|
+
? e.code
|
|
3312
|
+
: "";
|
|
3313
|
+
return {
|
|
3314
|
+
name: decision.name,
|
|
3315
|
+
status: "skipped",
|
|
3316
|
+
reason: (0, skill_links_js_1.linkFailureReason)(code, errorText(e)),
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Why `init` must not write under `absHome`, or null when it may. `.claude` or
|
|
3323
|
+
* its `skills` can be a link to a dotfiles directory outside the repo; creating
|
|
3324
|
+
* the home would follow it and put links where no commit of this repo reaches.
|
|
3325
|
+
* What `mkdir -p` would write through is the nearest part of the path that
|
|
3326
|
+
* already exists, so that is the part judged.
|
|
3327
|
+
*/
|
|
3328
|
+
function homeEscape(project, home, absHome) {
|
|
3329
|
+
const nearest = (p) => (0, node_fs_1.lstatSync)(p, { throwIfNoEntry: false }) === undefined
|
|
3330
|
+
? nearest((0, node_path_1.dirname)(p))
|
|
3331
|
+
: p;
|
|
3332
|
+
const anchor = nearest(absHome);
|
|
3333
|
+
const real = realOrNull(anchor);
|
|
3334
|
+
if (real === null)
|
|
3335
|
+
return `${(0, node_path_1.relative)(project, anchor)} is a link that does not resolve, so ${home} cannot be created`;
|
|
3336
|
+
return (0, skill_links_js_1.isWithinProject)(project, real)
|
|
3337
|
+
? null
|
|
3338
|
+
: `${home} leads to ${real}, outside the repository, and a link written there would not travel with a clone`;
|
|
3339
|
+
}
|
|
3340
|
+
/** The outcome when `init` makes no links, with the reason it prints. */
|
|
3341
|
+
function notLinked(reason) {
|
|
3342
|
+
return { kind: "not-linked", reason };
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Link vigiles's shipped skills into `home` (repo-relative) — the IO half of
|
|
3346
|
+
* `src/skill-links.ts`, which decides everything: where the package is, what
|
|
3347
|
+
* each entry becomes, and what the report says.
|
|
3348
|
+
*/
|
|
3349
|
+
function linkVigilesSkills(home, cwd) {
|
|
3350
|
+
// Physical, so the relative target computed against the (physical) skills
|
|
3351
|
+
// home is right even when the checkout sits under a symlink (macOS /tmp).
|
|
3352
|
+
const project = (0, node_fs_1.realpathSync)(cwd);
|
|
3353
|
+
const site = (0, skill_links_js_1.locatePackage)(project, {
|
|
3354
|
+
isPackage: (dir) => packageNameAt(dir) === skill_links_js_1.VIGILES_PACKAGE,
|
|
3355
|
+
isRepoRoot: (dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, ".git")),
|
|
3356
|
+
isWorkspaceRoot: (dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, "pnpm-workspace.yaml")) ||
|
|
3357
|
+
packageField(dir, "workspaces") !== undefined,
|
|
3358
|
+
});
|
|
3359
|
+
const pkgText = (0, node_fs_1.existsSync)((0, node_path_1.join)(project, PACKAGE_JSON))
|
|
3360
|
+
? (0, node_fs_1.readFileSync)((0, node_path_1.join)(project, PACKAGE_JSON), "utf-8")
|
|
3361
|
+
: null;
|
|
3362
|
+
const blocked = (0, skill_links_js_1.linkPrecondition)({
|
|
3363
|
+
isVigilesItself: packageNameAt(project) === skill_links_js_1.VIGILES_PACKAGE,
|
|
3364
|
+
dependsOnVigiles: pkgText !== null && (0, install_reader_js_1.declaresVigilesDependency)(pkgText),
|
|
3365
|
+
installed: site.kind === "installed",
|
|
3366
|
+
});
|
|
3367
|
+
if (blocked !== null)
|
|
3368
|
+
return notLinked(blocked);
|
|
3369
|
+
if (site.kind === "unsure")
|
|
3370
|
+
return notLinked(site.reason);
|
|
3371
|
+
// Names from the installed package when there is one, else from this CLI's
|
|
3372
|
+
// own — the version `init` just declared as the devDependency.
|
|
3373
|
+
const source = site.kind === "installed" ? site.dir : selfRoot();
|
|
3374
|
+
const names = setup_plan_js_1.SHIPPED_SKILLS.filter((n) => (0, node_fs_1.existsSync)((0, node_path_1.join)(source, "skills", n, "SKILL.md")));
|
|
3375
|
+
if (names.length === 0)
|
|
3376
|
+
return notLinked(`no shipped skills found under ${(0, node_path_1.join)(source, "skills")}`);
|
|
3377
|
+
const absHome = (0, node_path_1.resolve)(project, home);
|
|
3378
|
+
const escape = homeEscape(project, home, absHome);
|
|
3379
|
+
if (escape !== null)
|
|
3380
|
+
return notLinked(escape);
|
|
3381
|
+
try {
|
|
3382
|
+
(0, node_fs_1.mkdirSync)(absHome, { recursive: true });
|
|
3383
|
+
}
|
|
3384
|
+
catch (e) {
|
|
3385
|
+
return notLinked(`cannot create ${home}: ${errorText(e)}`);
|
|
3386
|
+
}
|
|
3387
|
+
const physicalHome = (0, node_fs_1.realpathSync)(absHome);
|
|
3388
|
+
const plan = (0, skill_links_js_1.planSkillLinks)({
|
|
3389
|
+
names,
|
|
3390
|
+
site,
|
|
3391
|
+
physicalHome,
|
|
3392
|
+
entries: (n) => observeSkillEntry((0, node_path_1.join)(absHome, n)),
|
|
3393
|
+
realOf: (n) => realOrNull((0, node_path_1.join)(site.dir, "skills", n)),
|
|
3394
|
+
});
|
|
3395
|
+
return {
|
|
3396
|
+
kind: "linked",
|
|
3397
|
+
home,
|
|
3398
|
+
physicalHome: (0, skill_links_js_1.portableTarget)((0, node_path_1.relative)(project, physicalHome)),
|
|
3399
|
+
pending: plan.pending,
|
|
3400
|
+
results: plan.decisions.map((d) => applySkillLink(d, absHome)),
|
|
3401
|
+
};
|
|
3203
3402
|
}
|
|
3204
3403
|
/**
|
|
3205
3404
|
* Write the project-level plugin declaration into `.claude/settings.json`.
|
|
@@ -3208,8 +3407,9 @@ function installPlugins(harnesses) {
|
|
|
3208
3407
|
* collaborator: an external-source plugin declared project-level does not load
|
|
3209
3408
|
* until each person installs it on their own machine (the boundary is
|
|
3210
3409
|
* deliberate — plugins run arbitrary code with the user's privileges). What it
|
|
3211
|
-
* buys is that Claude Code then PROMPTS them with the install command
|
|
3212
|
-
*
|
|
3410
|
+
* buys is that Claude Code then PROMPTS them with the install command — for the
|
|
3411
|
+
* HOOKS, which only the plugin carries. The skills reach them through the
|
|
3412
|
+
* committed links (`linkVigilesSkills`), with or without the plugin.
|
|
3213
3413
|
*
|
|
3214
3414
|
* MERGES, never clobbers: this file holds the user's hooks, permissions and
|
|
3215
3415
|
* other plugins. The merge rules are the pure, unit-tested
|
|
@@ -3249,8 +3449,9 @@ function declareVigilesPlugin() {
|
|
|
3249
3449
|
(0, node_fs_1.writeFileSync)(path, JSON.stringify(edit.settings, null, 2) + "\n");
|
|
3250
3450
|
console.log("✓ Declared the vigiles plugin in .claude/settings.json (commit it)\n" +
|
|
3251
3451
|
" This does NOT install it for anyone else — Claude Code will PROMPT a\n" +
|
|
3252
|
-
" collaborator to run `claude plugin install vigiles@vigiles`,
|
|
3253
|
-
" the
|
|
3452
|
+
" collaborator to run `claude plugin install vigiles@vigiles`, which is how\n" +
|
|
3453
|
+
" they get the hooks. The skills don't wait for it: they come with the\n" +
|
|
3454
|
+
" linked .claude/skills/ entries.\n" +
|
|
3254
3455
|
" Nothing is vendored: the entry is a reference; the plugin still lives in\n" +
|
|
3255
3456
|
" the global cache, one copy shared across your repos.");
|
|
3256
3457
|
}
|
|
@@ -3460,10 +3661,9 @@ async function setup(args) {
|
|
|
3460
3661
|
console.log("");
|
|
3461
3662
|
written.push(...wireGha(plan, harnesses));
|
|
3462
3663
|
}
|
|
3463
|
-
//
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
}
|
|
3664
|
+
// Skills linked into the repo + the per-harness global install (Claude
|
|
3665
|
+
// marketplace for the hooks / Codex only when nothing could be linked).
|
|
3666
|
+
const linkedSkills = plan.plugin ? installPlugins(harnesses) : [];
|
|
3467
3667
|
// Agent-specific guidance.
|
|
3468
3668
|
if (targets.includes("AGENTS.md")) {
|
|
3469
3669
|
console.log("\n Codex / Copilot reads AGENTS.md directly — no hooks needed.");
|
|
@@ -3483,7 +3683,14 @@ async function setup(args) {
|
|
|
3483
3683
|
lint: plan.lint,
|
|
3484
3684
|
written,
|
|
3485
3685
|
});
|
|
3486
|
-
printSetupSummary({
|
|
3686
|
+
printSetupSummary({
|
|
3687
|
+
plan,
|
|
3688
|
+
strict,
|
|
3689
|
+
targets,
|
|
3690
|
+
adopted,
|
|
3691
|
+
// The skill links the plugin step created, so the commit hint names them.
|
|
3692
|
+
written: [...written, ...linkedSkills],
|
|
3693
|
+
});
|
|
3487
3694
|
// Surface the OTHER mode so both directions are discoverable — the two branches
|
|
3488
3695
|
// are mutually exclusive (a run is either gate-only or full):
|
|
3489
3696
|
// • a gate-only setup → a ONE-LINE nudge to graduate to the full layer;
|
|
@@ -3595,6 +3802,18 @@ function harnessLayoutFor(root, config, flag) {
|
|
|
3595
3802
|
return adapter_registry_js_1.defaultAdapter.layout;
|
|
3596
3803
|
}
|
|
3597
3804
|
}
|
|
3805
|
+
/**
|
|
3806
|
+
* The rule that gates each surface kind, one row per kind. `satisfies` makes a
|
|
3807
|
+
* missing row a compile error: the nested ternary this replaced ended in the
|
|
3808
|
+
* hook rule, so a kind it did not name was gated by `untested-hook` and still
|
|
3809
|
+
* compiled.
|
|
3810
|
+
*/
|
|
3811
|
+
const UNTESTED_RULE = {
|
|
3812
|
+
skill: "untested-skill",
|
|
3813
|
+
agent: "untested-subagent",
|
|
3814
|
+
hook: "untested-hook",
|
|
3815
|
+
"output-style": "untested-output-style",
|
|
3816
|
+
};
|
|
3598
3817
|
/**
|
|
3599
3818
|
* The `untested-*` rules AS THE DETECTOR TAKES THEM: which kinds this repo
|
|
3600
3819
|
* enabled, and the discovery options merged from whichever of the three rules
|
|
@@ -3611,21 +3830,21 @@ function harnessLayoutFor(root, config, flag) {
|
|
|
3611
3830
|
*/
|
|
3612
3831
|
function untestedRules(config) {
|
|
3613
3832
|
const rules = config?.rules;
|
|
3614
|
-
const
|
|
3615
|
-
const
|
|
3616
|
-
const
|
|
3617
|
-
|
|
3618
|
-
...(0, types_js_1.ruleOptions)(rules?.[
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
};
|
|
3833
|
+
const severity = (kind) => (0, types_js_1.ruleSeverity)(rules?.[UNTESTED_RULE[kind]]);
|
|
3834
|
+
const names = Object.values(UNTESTED_RULE);
|
|
3835
|
+
const opts = names.reduce((acc, rule) => ({
|
|
3836
|
+
...acc,
|
|
3837
|
+
...(0, types_js_1.ruleOptions)(rules?.[rule]),
|
|
3838
|
+
}), {});
|
|
3839
|
+
const on = (kind) => severity(kind) !== false;
|
|
3622
3840
|
return {
|
|
3623
|
-
severity
|
|
3624
|
-
anyEnabled:
|
|
3841
|
+
severity,
|
|
3842
|
+
anyEnabled: names.some((rule) => (0, types_js_1.ruleSeverity)(rules?.[rule]) !== false),
|
|
3625
3843
|
options: {
|
|
3626
|
-
skills:
|
|
3627
|
-
agents:
|
|
3628
|
-
hooks:
|
|
3844
|
+
skills: on("skill"),
|
|
3845
|
+
agents: on("agent"),
|
|
3846
|
+
hooks: on("hook"),
|
|
3847
|
+
outputStyles: on("output-style"),
|
|
3629
3848
|
include: opts.include,
|
|
3630
3849
|
exclude: opts.exclude,
|
|
3631
3850
|
// Without this the `testExtension` documented on TestCoverageOptions was a
|
package/dist/core/adapter.d.ts
CHANGED
|
@@ -182,6 +182,13 @@ export interface InstallReader {
|
|
|
182
182
|
* outside its own surface.
|
|
183
183
|
*/
|
|
184
184
|
readonly repo: (repoRelative: string) => string | null;
|
|
185
|
+
/**
|
|
186
|
+
* The stored target of a repo SYMLINK (what `readlink` returns), or null when
|
|
187
|
+
* the path is not a link, is missing, or is NOT a path this adapter claims.
|
|
188
|
+
* It answers the one question `repo` cannot: a committed link whose target is
|
|
189
|
+
* not installed yet reads as null through `repo`, exactly like no link at all.
|
|
190
|
+
*/
|
|
191
|
+
readonly repoLink: (repoRelative: string) => string | null;
|
|
185
192
|
/**
|
|
186
193
|
* A file under the user's HOME, or null. This is a read of the MACHINE, never
|
|
187
194
|
* of the repository — the plugin-install record lives there — and everything
|
|
@@ -109,6 +109,15 @@ export declare const vigilesConfigSchema: z.ZodObject<{
|
|
|
109
109
|
exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
|
|
110
110
|
testExtension: z.ZodOptional<z.ZodString>;
|
|
111
111
|
}, z.core.$strict>], null>]>>;
|
|
112
|
+
"untested-output-style": z.ZodDefault<z.ZodUnion<readonly [z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "warn" | "error", boolean | 0 | 1 | 2 | "warn" | "error" | "off">>, z.ZodTuple<[z.ZodLiteral<"warn">, z.ZodObject<{
|
|
113
|
+
include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
|
|
114
|
+
exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
|
|
115
|
+
testExtension: z.ZodOptional<z.ZodString>;
|
|
116
|
+
}, z.core.$strict>], null>, z.ZodTuple<[z.ZodLiteral<"error">, z.ZodObject<{
|
|
117
|
+
include: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
|
|
118
|
+
exclude: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<readonly string[], string | string[]>>>;
|
|
119
|
+
testExtension: z.ZodOptional<z.ZodString>;
|
|
120
|
+
}, z.core.$strict>], null>]>>;
|
|
112
121
|
"unmarked-refs": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "warn" | "error", boolean | 0 | 1 | 2 | "warn" | "error" | "off">>>;
|
|
113
122
|
"subagent-tool-contract": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "warn" | "error", boolean | 0 | 1 | 2 | "warn" | "error" | "off">>>;
|
|
114
123
|
"hook-events": z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodLiteral<"warn">, z.ZodLiteral<"error">, z.ZodLiteral<false>, z.ZodLiteral<"off">, z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<true>]>, z.ZodTransform<false | "warn" | "error", boolean | 0 | 1 | 2 | "warn" | "error" | "off">>>;
|
|
@@ -141,6 +141,7 @@ const rulesSchema = zod_1.z
|
|
|
141
141
|
"untested-skill": withOptions(testCoverageConfig).default("warn"),
|
|
142
142
|
"untested-subagent": withOptions(testCoverageConfig).default("warn"),
|
|
143
143
|
"untested-hook": withOptions(testCoverageConfig).default("warn"),
|
|
144
|
+
"untested-output-style": withOptions(testCoverageConfig).default("warn"),
|
|
144
145
|
"unmarked-refs": severitySchema.default("warn"),
|
|
145
146
|
"subagent-tool-contract": severitySchema.default("warn"),
|
|
146
147
|
"hook-events": severitySchema.default("warn"),
|
|
@@ -71,6 +71,14 @@ export interface Trace {
|
|
|
71
71
|
readonly hooks: readonly HookFire[];
|
|
72
72
|
/** The agent's final answer text (the terminal `result` event), or "". */
|
|
73
73
|
readonly output: string;
|
|
74
|
+
/**
|
|
75
|
+
* Every reply the agent ended a turn with, in order — the `output` is only
|
|
76
|
+
* the last. A run a Stop hook continued has two, and anything the first one
|
|
77
|
+
* printed (a status footer) shows only here. Text the agent writes before a
|
|
78
|
+
* tool call is narration, not a reply. Absent when the harness's run output
|
|
79
|
+
* does not tell replies apart, or the run was not streamed (`transcript: true`).
|
|
80
|
+
*/
|
|
81
|
+
readonly replies?: readonly string[];
|
|
74
82
|
/**
|
|
75
83
|
* The requests the model received, captured by the scripted mock — each with
|
|
76
84
|
* its `system` prompt and `messages`, flattened to text. Lets a test assert
|
|
@@ -153,6 +161,8 @@ export interface ParsedModelRun {
|
|
|
153
161
|
readonly toolCalls: ToolCall[];
|
|
154
162
|
readonly hooks: HookFire[];
|
|
155
163
|
readonly subagents: SubagentTrace[];
|
|
164
|
+
/** Every reply, in order; absent when the harness cannot tell them apart. */
|
|
165
|
+
readonly replies?: readonly string[];
|
|
156
166
|
readonly usage: EvalUsage;
|
|
157
167
|
}
|
|
158
168
|
export type ModelOutputParser = (out: RunOut) => ParsedModelRun;
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lenient frontmatter reader — ONE reader for the SKILL.md / subagent `---` block,
|
|
3
|
+
* shared by `scan` and the PreToolUse rail (`agent-runtime`). It is deliberately
|
|
4
|
+
* fault-tolerant: it audits arbitrary third-party files, so it must never throw
|
|
5
|
+
* and must salvage the few scalar/list fields it needs even from a block that
|
|
6
|
+
* isn't valid YAML.
|
|
7
|
+
*
|
|
8
|
+
* The strategy is "real parser, with a safety net": try `js-yaml` (so block
|
|
9
|
+
* scalars, quoted/multi-line values, and flow arrays parse correctly for free);
|
|
10
|
+
* if the block isn't valid YAML, fall back to a regex salvage of the requested
|
|
11
|
+
* field and record `malformed: true` (the signal the `frontmatter-valid` rule
|
|
12
|
+
* reports). A single bad line therefore never blanks out a whole file's metadata.
|
|
13
|
+
*
|
|
14
|
+
* This replaces three divergent hand-parsers (the old `readField` in scan.ts and
|
|
15
|
+
* the regex parse in agent-runtime.ts); `core/frontmatter.ts` is a DIFFERENT
|
|
16
|
+
* concern (the Level-1 `vigiles:` rule block) and is untouched.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 🔴 `js-yaml` IS REQUIRED LAZILY, AND THAT IS A MEASUREMENT, NOT A STYLE. The
|
|
20
|
+
* same wrapper is in `core/settings-codec.ts` and `core/markdown.ts` with the
|
|
21
|
+
* reason recorded: a top-level import puts the parser into the module graph of
|
|
22
|
+
* every HOOK DECISION, because the hook runtime reaches the adapter registry
|
|
23
|
+
* and from there a layout. This module became reachable that way on 2026-09-21
|
|
24
|
+
* through `PluginLayout.instructionChain` (a rule's `paths:` frontmatter is
|
|
25
|
+
* what decides whether it loads at launch) — measured by
|
|
26
|
+
* `src/hook-runtime-graph.test.ts`: 37 modules became 71 with an eager import.
|
|
27
|
+
* `tsc` lowers this to CommonJS, so the `require` does not run until a
|
|
28
|
+
* frontmatter block is actually parsed, and {@link frontmatterBody}, which needs
|
|
29
|
+
* no YAML at all, never triggers it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const yaml: () => typeof import("js-yaml");
|
|
1
32
|
export interface FrontmatterRead {
|
|
2
33
|
/** Parsed mapping when the block is valid YAML, else null (malformed or scalar). */
|
|
3
34
|
readonly data: Record<string, unknown> | null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.yaml = void 0;
|
|
3
4
|
exports.frontmatterBody = frontmatterBody;
|
|
4
5
|
exports.readFrontmatter = readFrontmatter;
|
|
5
6
|
exports.frontmatterScalar = frontmatterScalar;
|
|
@@ -35,6 +36,7 @@ exports.frontmatterList = frontmatterList;
|
|
|
35
36
|
* no YAML at all, never triggers it.
|
|
36
37
|
*/
|
|
37
38
|
const yaml = () => require("js-yaml");
|
|
39
|
+
exports.yaml = yaml;
|
|
38
40
|
// Frontmatter is the very first thing in the file. Anchoring at the start — not
|
|
39
41
|
// `(?:^|\n)` — means a `---` horizontal rule in the BODY is never mistaken for
|
|
40
42
|
// frontmatter (which matters for the malformed-YAML verdict). A leading BOM is
|
|
@@ -67,7 +69,7 @@ function readFrontmatter(markdown) {
|
|
|
67
69
|
return { data: null, block: null, malformed: false };
|
|
68
70
|
const block = m[1];
|
|
69
71
|
try {
|
|
70
|
-
const parsed = yaml().load(block);
|
|
72
|
+
const parsed = (0, exports.yaml)().load(block);
|
|
71
73
|
if (parsed !== null &&
|
|
72
74
|
typeof parsed === "object" &&
|
|
73
75
|
!Array.isArray(parsed)) {
|
|
@@ -82,7 +84,7 @@ function readFrontmatter(markdown) {
|
|
|
82
84
|
return { data: null, block, malformed: false };
|
|
83
85
|
}
|
|
84
86
|
catch (e) {
|
|
85
|
-
if (e instanceof yaml().YAMLException)
|
|
87
|
+
if (e instanceof (0, exports.yaml)().YAMLException)
|
|
86
88
|
return { data: null, block, malformed: true };
|
|
87
89
|
throw e;
|
|
88
90
|
}
|