vigiles 27.3.0 โ 29.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapter-registry.d.ts +39 -0
- package/dist/adapter-registry.js +45 -0
- package/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +10 -1
- package/dist/adapters/claude-code/adapter.js +9 -2
- package/dist/adapters/claude-code/layout.d.ts +5 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +10 -1
- package/dist/adapters/claude-code/plugin-loader.js +10 -1
- package/dist/adapters/codex/adapter.js +7 -2
- package/dist/adapters/codex/layout.d.ts +51 -5
- package/dist/adapters/codex/layout.js +13 -4
- package/dist/adapters/opencode/adapter.js +6 -0
- package/dist/audit-report.template.html +1 -1
- package/dist/audit-score.d.ts +7 -0
- package/dist/audit-score.js +49 -2
- package/dist/cli-main.js +162 -39
- package/dist/core/adapter.d.ts +45 -1
- package/dist/core/compile.js +11 -1
- package/dist/core/config-schema.d.ts +244 -0
- package/dist/core/config-schema.js +452 -0
- package/dist/core/hook-program.d.ts +43 -0
- package/dist/core/hook-program.js +32 -0
- package/dist/core/refs.js +10 -1
- package/dist/core/surface-discovery.d.ts +270 -0
- package/dist/core/surface-discovery.js +425 -0
- package/dist/core/surface-scopes.d.ts +38 -1
- package/dist/core/surface-scopes.js +73 -1
- package/dist/core/symbols.d.ts +24 -2
- package/dist/core/symbols.js +66 -18
- package/dist/core/types.d.ts +36 -107
- package/dist/core/validate.d.ts +46 -18
- package/dist/core/validate.js +98 -172
- package/dist/exclude.d.ts +20 -0
- package/dist/exclude.js +11 -1
- package/dist/harness-test.js +3 -3
- package/dist/hook-install.d.ts +53 -0
- package/dist/hook-install.js +60 -0
- package/dist/hook-runtime.d.ts +2 -2
- package/dist/hook-runtime.js +82 -63
- package/dist/layout-registry.d.ts +14 -0
- package/dist/layout-registry.js +40 -0
- package/dist/load-hook.d.ts +1 -1
- package/dist/load-hook.js +2 -2
- package/dist/plugin-loader.d.ts +49 -1
- package/dist/plugin-loader.js +120 -14
- package/dist/run-hook.js +17 -1
- package/dist/scan-core.d.ts +19 -0
- package/dist/scan-core.js +30 -0
- package/dist/scan-files.js +15 -5
- package/dist/scan.d.ts +63 -0
- package/dist/scan.js +68 -12
- package/dist/score-core.js +8 -0
- package/dist/setup-plan.d.ts +2 -1
- package/dist/setup-plan.js +7 -2
- package/dist/surface-discovery-fs.d.ts +12 -0
- package/dist/surface-discovery-fs.js +108 -0
- package/dist/vigilesrc.schema.json +1689 -0
- package/package.json +10 -6
package/dist/cli-main.js
CHANGED
|
@@ -34,6 +34,7 @@ const setup_plan_js_1 = require("./setup-plan.js");
|
|
|
34
34
|
const types_js_1 = require("./core/types.js");
|
|
35
35
|
const test_coverage_js_1 = require("./test-coverage.js");
|
|
36
36
|
const scan_js_1 = require("./scan.js");
|
|
37
|
+
const surface_discovery_js_1 = require("./core/surface-discovery.js");
|
|
37
38
|
const scan_trigger_suggest_js_1 = require("./scan-trigger-suggest.js");
|
|
38
39
|
const dialect_drift_js_1 = require("./dialect-drift.js");
|
|
39
40
|
const skill_reachability_js_1 = require("./skill-reachability.js");
|
|
@@ -553,7 +554,7 @@ async function compile(specPaths, config, excludes, opts = {}) {
|
|
|
553
554
|
// dialect pick and the mirror from it โ no re-parsing, no cwd-sniffing in the
|
|
554
555
|
// helpers. A loud notice (never a silent guess) on a multi-harness or
|
|
555
556
|
// ambiguous-detection pick.
|
|
556
|
-
const declaredHarnesses = (0, adapter_registry_js_1.
|
|
557
|
+
const declaredHarnesses = (0, adapter_registry_js_1.declaredHarnessNames)(config.harnesses);
|
|
557
558
|
const selection = (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
558
559
|
root: process.cwd(),
|
|
559
560
|
flag: opts.harnessFlag,
|
|
@@ -1466,7 +1467,7 @@ async function runLint(restArgs, flags, excludes, config) {
|
|
|
1466
1467
|
const lintSelection = (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
1467
1468
|
root: scanRoot,
|
|
1468
1469
|
flag: harnessFlag,
|
|
1469
|
-
configHarness: (0, adapter_registry_js_1.
|
|
1470
|
+
configHarness: (0, adapter_registry_js_1.declaredHarnessNames)(config?.harnesses),
|
|
1470
1471
|
});
|
|
1471
1472
|
const adapter = lintSelection.adapter;
|
|
1472
1473
|
// ๐ด WHICH ROOTS GET SCORED, and saying so either way (#185).
|
|
@@ -3363,7 +3364,7 @@ async function setup(args) {
|
|
|
3363
3364
|
// Detect project. An existing `.vigilesrc.json` `harness` (from a prior init /
|
|
3364
3365
|
// a hand-authored config) wins over auto-detection (dogfood I3).
|
|
3365
3366
|
const detected = detectProject();
|
|
3366
|
-
const harnesses = resolveHarnesses(parsed, detected, (0, validate_js_1.loadConfig)().
|
|
3367
|
+
const harnesses = resolveHarnesses(parsed, detected, (0, adapter_registry_js_1.declaredHarnessNames)((0, validate_js_1.loadConfig)().harnesses));
|
|
3367
3368
|
printDetection(detected, harnesses);
|
|
3368
3369
|
// Files actually written, accumulated for an honest commit hint.
|
|
3369
3370
|
const written = [];
|
|
@@ -3436,15 +3437,15 @@ async function setup(args) {
|
|
|
3436
3437
|
console.log("\nโน Ran the standard setup. Already have a harness, or not a JS/Python repo, and want only the CI integrity gate (nothing installed)? Re-run `npx vigiles init --ci-only`.");
|
|
3437
3438
|
}
|
|
3438
3439
|
}
|
|
3439
|
-
/** Canonical, de-duplicated harness
|
|
3440
|
-
function
|
|
3441
|
-
|
|
3442
|
-
return canon.length === 1 ? canon[0] : canon;
|
|
3440
|
+
/** Canonical, de-duplicated harness names โ the keys `harnesses` gets. */
|
|
3441
|
+
function harnessConfigKeys(harnesses) {
|
|
3442
|
+
return [...new Set(harnesses.map(adapter_registry_js_1.normalizeHarnessName))];
|
|
3443
3443
|
}
|
|
3444
3444
|
/**
|
|
3445
3445
|
* Merge the resolved harness(es) (and strict rule severities) into
|
|
3446
|
-
* `.vigilesrc.json` without clobbering existing keys โ an existing
|
|
3447
|
-
* stays, a missing one is added, a malformed file is left
|
|
3446
|
+
* `.vigilesrc.json` without clobbering existing keys โ an existing
|
|
3447
|
+
* `harnesses` block stays, a missing one is added, a malformed file is left
|
|
3448
|
+
* untouched.
|
|
3448
3449
|
*/
|
|
3449
3450
|
function writeProjectConfig(opts) {
|
|
3450
3451
|
const configPath = (0, node_path_1.resolve)(process.cwd(), ".vigilesrc.json");
|
|
@@ -3459,7 +3460,7 @@ function writeProjectConfig(opts) {
|
|
|
3459
3460
|
}
|
|
3460
3461
|
}
|
|
3461
3462
|
const merged = (0, setup_plan_js_1.mergeProjectConfig)(existing, {
|
|
3462
|
-
|
|
3463
|
+
harnesses: harnessConfigKeys(opts.harnesses),
|
|
3463
3464
|
strict: opts.strict,
|
|
3464
3465
|
reportOnly: opts.reportOnly,
|
|
3465
3466
|
lint: opts.lint,
|
|
@@ -3524,7 +3525,7 @@ function harnessLayoutFor(root, config, flag) {
|
|
|
3524
3525
|
return (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
3525
3526
|
root,
|
|
3526
3527
|
flag,
|
|
3527
|
-
configHarness: (0, adapter_registry_js_1.
|
|
3528
|
+
configHarness: (0, adapter_registry_js_1.declaredHarnessNames)(config?.harnesses),
|
|
3528
3529
|
}).adapter.layout;
|
|
3529
3530
|
}
|
|
3530
3531
|
catch {
|
|
@@ -4391,7 +4392,7 @@ function flagValue(args, name) {
|
|
|
4391
4392
|
* Resolve the adapter for a COMMAND, honouring the full precedence: `--harness=`
|
|
4392
4393
|
* flag โ `.vigilesrc.json` `harness` โ auto-detect (dogfood A/I3). This is the
|
|
4393
4394
|
* ONE resolution path a command may use โ resolving via the raw auto-detect
|
|
4394
|
-
* alone silently ignores
|
|
4395
|
+
* alone silently ignores `.vigilesrc.json#harnesses`, which is the exact bug this closes.
|
|
4395
4396
|
* A dogfood test (src/cli-harness-resolution.test.ts) asserts cli.ts routes all
|
|
4396
4397
|
* command harness resolution through here, so a future command can't regress.
|
|
4397
4398
|
*/
|
|
@@ -4399,7 +4400,7 @@ function resolveCommandHarness(dir, harnessFlag) {
|
|
|
4399
4400
|
return (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
4400
4401
|
root: dir,
|
|
4401
4402
|
flag: harnessFlag,
|
|
4402
|
-
configHarness: (0, adapter_registry_js_1.
|
|
4403
|
+
configHarness: (0, adapter_registry_js_1.declaredHarnessNames)((0, validate_js_1.loadConfig)().harnesses),
|
|
4403
4404
|
});
|
|
4404
4405
|
}
|
|
4405
4406
|
async function handleMeasure(restArgs, args) {
|
|
@@ -5252,6 +5253,45 @@ function annotateLintForGitHub(report, flags) {
|
|
|
5252
5253
|
ghAnnotate("warning", `${String(report.duplicatePairs)} near-duplicate rule pair(s) detected โ consider merging`);
|
|
5253
5254
|
}
|
|
5254
5255
|
}
|
|
5256
|
+
/**
|
|
5257
|
+
* The project root a `hook-runtime` rail acts on โ NEVER `process.cwd()` first.
|
|
5258
|
+
*
|
|
5259
|
+
* These rails are a SECOND execution path, wired by hand into a hooks config
|
|
5260
|
+
* (`npx vigiles hook-runtime <kind>`), and every one of them used to ask
|
|
5261
|
+
* `process.cwd()` where the project was. The hook process has no stable cwd: a
|
|
5262
|
+
* git worktree, or a session that has `cd`-ed into a subdirectory, stands
|
|
5263
|
+
* somewhere the project's files are not, and then the state store writes its
|
|
5264
|
+
* marker beside the wrong repo and `.vigiles/*.json` is simply not found. The
|
|
5265
|
+
* failure is SILENT in the permissive direction โ no gates loaded reads exactly
|
|
5266
|
+
* like a project that declared none.
|
|
5267
|
+
*
|
|
5268
|
+
* Same order the compiled runtime uses (`projectRootOf`, core/hook-program.ts):
|
|
5269
|
+
* `$CLAUDE_PROJECT_DIR` first โ the harness resolved the hook's own path against
|
|
5270
|
+
* it โ then the event's own `cwd`, which Claude Code puts in every hook payload.
|
|
5271
|
+
* `process.cwd()` stays as the documented LAST resort, for the rails a human or
|
|
5272
|
+
* the model invokes as a plain command with no event and no env to go on.
|
|
5273
|
+
*
|
|
5274
|
+
* Pass the parsed event wherever stdin was already read; the handlers that take
|
|
5275
|
+
* only an argument pass nothing and get the env answer.
|
|
5276
|
+
*/
|
|
5277
|
+
function runtimeRoot(event = {}) {
|
|
5278
|
+
return (0, hook_program_js_1.projectRootOf)(event, process.env) ?? process.cwd();
|
|
5279
|
+
}
|
|
5280
|
+
/**
|
|
5281
|
+
* The hook payload as a root SOURCE โ `{}` when stdin was absent or malformed,
|
|
5282
|
+
* which {@link runtimeRoot} reads as "this event offers no root" and falls
|
|
5283
|
+
* through. Deliberately separate from each handler's own parse: a rail that
|
|
5284
|
+
* cannot understand its event still knows where the project is, and a rail that
|
|
5285
|
+
* only wants `tool_name` should not have to widen its own type to say so.
|
|
5286
|
+
*/
|
|
5287
|
+
function eventRoot(raw) {
|
|
5288
|
+
try {
|
|
5289
|
+
return JSON.parse(raw);
|
|
5290
|
+
}
|
|
5291
|
+
catch {
|
|
5292
|
+
return {};
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5255
5295
|
/**
|
|
5256
5296
|
* Run a compiled skill's deterministic gate ladder: execute each step gate in
|
|
5257
5297
|
* order (short-circuiting on the first failure), then the result gate. This is
|
|
@@ -5264,7 +5304,8 @@ function runSkillCommand(target) {
|
|
|
5264
5304
|
console.error("Usage: vigiles hook-runtime run-skill <SKILL.md>");
|
|
5265
5305
|
process.exit(2);
|
|
5266
5306
|
}
|
|
5267
|
-
const
|
|
5307
|
+
const root = runtimeRoot();
|
|
5308
|
+
const path = (0, node_path_1.resolve)(root, target);
|
|
5268
5309
|
if (!(0, node_fs_1.existsSync)(path)) {
|
|
5269
5310
|
console.error(`Not found: ${target}`);
|
|
5270
5311
|
process.exit(2);
|
|
@@ -5275,7 +5316,7 @@ function runSkillCommand(target) {
|
|
|
5275
5316
|
return;
|
|
5276
5317
|
}
|
|
5277
5318
|
console.log(`Running gate ladder for ${target}:\n`);
|
|
5278
|
-
const report = (0, skill_runtime_js_1.runSkillGates)(gates,
|
|
5319
|
+
const report = (0, skill_runtime_js_1.runSkillGates)(gates, root);
|
|
5279
5320
|
for (const r of report.results) {
|
|
5280
5321
|
const label = r.at === "result" ? "result" : `step ${String(r.at)}`;
|
|
5281
5322
|
console.log(` ${r.ok ? "โ" : "โ"} ${label} โ ${(0, skill_runtime_js_1.gateLabel)(r.gate)}`);
|
|
@@ -5303,11 +5344,12 @@ function runSkillCommand(target) {
|
|
|
5303
5344
|
* feeds the message back to the model; exit 0 allows it and clears the marker.
|
|
5304
5345
|
*/
|
|
5305
5346
|
function skillHookCommand() {
|
|
5306
|
-
const
|
|
5347
|
+
const root = runtimeRoot();
|
|
5348
|
+
const decision = (0, skill_runtime_js_1.evaluateStopHook)(root);
|
|
5307
5349
|
if (decision.allow) {
|
|
5308
5350
|
if (decision.message)
|
|
5309
5351
|
console.log(decision.message);
|
|
5310
|
-
(0, skill_runtime_js_1.clearActiveSkill)(
|
|
5352
|
+
(0, skill_runtime_js_1.clearActiveSkill)(root);
|
|
5311
5353
|
return;
|
|
5312
5354
|
}
|
|
5313
5355
|
console.error(decision.message);
|
|
@@ -5319,12 +5361,20 @@ function skillStartCommand(target) {
|
|
|
5319
5361
|
console.error("Usage: vigiles hook-runtime skill-start <SKILL.md>");
|
|
5320
5362
|
process.exit(2);
|
|
5321
5363
|
}
|
|
5322
|
-
|
|
5364
|
+
const root = runtimeRoot();
|
|
5365
|
+
(0, skill_runtime_js_1.setActiveSkill)(root, target);
|
|
5323
5366
|
// Record the fire in the flight recorder: the skill NAME is the parent dir of
|
|
5324
5367
|
// its SKILL.md (skills/<name>/SKILL.md), falling back to the raw target.
|
|
5325
5368
|
const parts = target.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
5326
5369
|
const name = parts.length >= 2 ? parts[parts.length - 2] : (parts[0] ?? target);
|
|
5327
|
-
|
|
5370
|
+
// The SAME root the decision above used. `appendObservation` defaults to
|
|
5371
|
+
// `process.cwd()`, which is correct as a library default and wrong here: a
|
|
5372
|
+
// hook does not run with a stable cwd, so the decision would land in the
|
|
5373
|
+
// project while its record landed beside whatever directory the process
|
|
5374
|
+
// happened to stand in. A ledger split across two directories is not untidy,
|
|
5375
|
+
// it is wrong in a way that reads as normal โ the file in the project looks
|
|
5376
|
+
// complete, and nobody notices a flight recorder that is short.
|
|
5377
|
+
(0, observe_js_1.appendObservation)({ kind: "skill", name, fired: true }, root);
|
|
5328
5378
|
console.log(`Active skill: ${target}`);
|
|
5329
5379
|
}
|
|
5330
5380
|
/**
|
|
@@ -5357,7 +5407,7 @@ function skillToolHookCommand() {
|
|
|
5357
5407
|
}
|
|
5358
5408
|
if (!tool)
|
|
5359
5409
|
return;
|
|
5360
|
-
const decision = (0, skill_runtime_js_1.evaluateSkillPreToolUse)(
|
|
5410
|
+
const decision = (0, skill_runtime_js_1.evaluateSkillPreToolUse)(runtimeRoot(eventRoot(raw)), tool, command);
|
|
5361
5411
|
if (!decision.allow) {
|
|
5362
5412
|
console.error(decision.message);
|
|
5363
5413
|
process.exit(2);
|
|
@@ -5393,7 +5443,7 @@ function agentHookCommand() {
|
|
|
5393
5443
|
catch {
|
|
5394
5444
|
/* malformed input โ no tool, allow */
|
|
5395
5445
|
}
|
|
5396
|
-
const cwd =
|
|
5446
|
+
const cwd = runtimeRoot(eventRoot(raw));
|
|
5397
5447
|
// EXPERIMENTAL (parked P3 โ do NOT auto-wire). The spawn/SubagentStop bracketing
|
|
5398
5448
|
// is now nesting-safe: a depth-aware STACK (push on dispatch, POP on SubagentStop)
|
|
5399
5449
|
// closes the contract-escape the flat single-slot model allowed under CC v2.1.172
|
|
@@ -5425,13 +5475,15 @@ function agentHookCommand() {
|
|
|
5425
5475
|
return;
|
|
5426
5476
|
const decision = (0, agent_runtime_js_1.evaluatePreToolUse)(cwd, tool, command);
|
|
5427
5477
|
if (!decision.allow) {
|
|
5478
|
+
// Same root as the decision โ see `skillStartCommand` for why the default
|
|
5479
|
+
// is wrong on a hook rail.
|
|
5428
5480
|
(0, observe_js_1.appendObservation)({
|
|
5429
5481
|
kind: "agent",
|
|
5430
5482
|
name: (0, agent_runtime_js_1.readActiveAgent)(cwd) ?? "unknown",
|
|
5431
5483
|
tool,
|
|
5432
5484
|
allowed: false,
|
|
5433
5485
|
reason: decision.message,
|
|
5434
|
-
});
|
|
5486
|
+
}, cwd);
|
|
5435
5487
|
console.error(decision.message);
|
|
5436
5488
|
process.exit(2);
|
|
5437
5489
|
}
|
|
@@ -5476,7 +5528,7 @@ function guardHookCommand() {
|
|
|
5476
5528
|
catch {
|
|
5477
5529
|
/* no stdin */
|
|
5478
5530
|
}
|
|
5479
|
-
const { decision } = (0, guards_js_1.runGuardHook)(
|
|
5531
|
+
const { decision } = (0, guards_js_1.runGuardHook)(runtimeRoot(eventRoot(raw)), raw);
|
|
5480
5532
|
if (!decision.allow) {
|
|
5481
5533
|
console.error(decision.reason ?? "Blocked by a vigiles guard.");
|
|
5482
5534
|
process.exit(2);
|
|
@@ -5488,7 +5540,7 @@ function agentStartCommand(target) {
|
|
|
5488
5540
|
console.error("Usage: vigiles hook-runtime agent-start <agents/<name>.md>");
|
|
5489
5541
|
process.exit(2);
|
|
5490
5542
|
}
|
|
5491
|
-
(0, agent_runtime_js_1.pushActiveAgent)(
|
|
5543
|
+
(0, agent_runtime_js_1.pushActiveAgent)(runtimeRoot(), target);
|
|
5492
5544
|
console.log(`Active agent: ${target}`);
|
|
5493
5545
|
}
|
|
5494
5546
|
/** Dispatch the skill-runtime subcommands. Returns false if unrecognized. */
|
|
@@ -5512,7 +5564,7 @@ async function handleHookRuntime(kind, restArgs) {
|
|
|
5512
5564
|
agentStartCommand(restArgs[0]);
|
|
5513
5565
|
return;
|
|
5514
5566
|
case "agent-done":
|
|
5515
|
-
(0, agent_runtime_js_1.popActiveAgent)(
|
|
5567
|
+
(0, agent_runtime_js_1.popActiveAgent)(runtimeRoot());
|
|
5516
5568
|
return;
|
|
5517
5569
|
case "skill":
|
|
5518
5570
|
skillHookCommand();
|
|
@@ -5524,7 +5576,7 @@ async function handleHookRuntime(kind, restArgs) {
|
|
|
5524
5576
|
skillStartCommand(restArgs[0]);
|
|
5525
5577
|
return;
|
|
5526
5578
|
case "skill-done":
|
|
5527
|
-
(0, skill_runtime_js_1.clearActiveSkill)(
|
|
5579
|
+
(0, skill_runtime_js_1.clearActiveSkill)(runtimeRoot());
|
|
5528
5580
|
return;
|
|
5529
5581
|
case "run-skill":
|
|
5530
5582
|
runSkillCommand(restArgs[0]);
|
|
@@ -5545,11 +5597,11 @@ async function handleHookRuntime(kind, restArgs) {
|
|
|
5545
5597
|
evalLockNudgeHookCommand();
|
|
5546
5598
|
return;
|
|
5547
5599
|
case "effect-enter":
|
|
5548
|
-
(0, effect_region_js_1.setEffectActive)(
|
|
5600
|
+
(0, effect_region_js_1.setEffectActive)(runtimeRoot());
|
|
5549
5601
|
console.log("Effect boundary entered.");
|
|
5550
5602
|
return;
|
|
5551
5603
|
case "effect-exit":
|
|
5552
|
-
(0, effect_region_js_1.clearEffectActive)(
|
|
5604
|
+
(0, effect_region_js_1.clearEffectActive)(runtimeRoot());
|
|
5553
5605
|
return;
|
|
5554
5606
|
default:
|
|
5555
5607
|
console.error(`vigiles hook-runtime: unknown runtime entrypoint "${kind ?? ""}". ` +
|
|
@@ -5580,7 +5632,8 @@ function actionHookCommand() {
|
|
|
5580
5632
|
catch {
|
|
5581
5633
|
/* malformed input โ no event, allow */
|
|
5582
5634
|
}
|
|
5583
|
-
const
|
|
5635
|
+
const root = runtimeRoot(eventRoot(raw));
|
|
5636
|
+
const decision = (0, action_gate_js_1.evaluateAction)(event, (0, action_gate_js_1.loadActionGates)(root), root);
|
|
5584
5637
|
if (!decision.allow) {
|
|
5585
5638
|
console.error(decision.message);
|
|
5586
5639
|
process.exit(2);
|
|
@@ -5624,7 +5677,7 @@ function evalLockNudgeHookCommand() {
|
|
|
5624
5677
|
}
|
|
5625
5678
|
if (!file)
|
|
5626
5679
|
return;
|
|
5627
|
-
const cwd =
|
|
5680
|
+
const cwd = runtimeRoot(eventRoot(raw));
|
|
5628
5681
|
const target = (0, node_path_1.relative)(cwd, (0, node_path_1.resolve)(cwd, file)) || file;
|
|
5629
5682
|
// ๐ด THE SAME CONFIG `vigiles lint` READS. This used to pass `basePath` alone,
|
|
5630
5683
|
// so a repo that had switched `untested-skill` off, or pointed `include` at
|
|
@@ -5638,7 +5691,11 @@ function evalLockNudgeHookCommand() {
|
|
|
5638
5691
|
// second gate saying the same thing is a branch no test can distinguish from
|
|
5639
5692
|
// its absence (measured โ the mutation passed), i.e. the dead-fragment class
|
|
5640
5693
|
// this same change removed from the runner table.
|
|
5641
|
-
|
|
5694
|
+
// `onInvalid: "warn"` โ a HOOK RAIL. This is a fresh process inside somebody's
|
|
5695
|
+
// editing session, and a malformed `.vigilesrc.json` must not turn a JSON typo
|
|
5696
|
+
// into a failed edit: the nudge not firing is the cheaper failure. The verbs
|
|
5697
|
+
// throw on the same config; what is CHECKED is identical.
|
|
5698
|
+
const config = (0, validate_js_1.loadConfig)(cwd, { onInvalid: "warn" });
|
|
5642
5699
|
const { options } = untestedRules(config);
|
|
5643
5700
|
// ๐ด THE SAME LAYOUT `vigiles lint` RESOLVES, for the same reason as the config
|
|
5644
5701
|
// above. This used to pass `basePath` alone, so the detector fell back to the
|
|
@@ -5701,10 +5758,13 @@ function refsHookCommand() {
|
|
|
5701
5758
|
}
|
|
5702
5759
|
if (!file || !isInstructionFile(file))
|
|
5703
5760
|
return;
|
|
5704
|
-
|
|
5761
|
+
// Root first: the config read below is anchored on it, and reading the config
|
|
5762
|
+
// from the process's directory is how a disabled rule comes back to life.
|
|
5763
|
+
const cwd = runtimeRoot(eventRoot(raw));
|
|
5764
|
+
// A hook rail โ see `evalLockNudgeHookCommand` for why it warns, not throws.
|
|
5765
|
+
const severity = (0, types_js_1.ruleSeverity)((0, validate_js_1.loadConfig)(cwd, { onInvalid: "warn" }).rules["unmarked-refs"]);
|
|
5705
5766
|
if (severity === false)
|
|
5706
5767
|
return;
|
|
5707
|
-
const cwd = process.cwd();
|
|
5708
5768
|
const target = (0, node_path_1.relative)(cwd, (0, node_path_1.resolve)(cwd, file)) || file;
|
|
5709
5769
|
let markdown;
|
|
5710
5770
|
try {
|
|
@@ -5787,7 +5847,14 @@ async function installHookFile(file, adapter, registeredProviders = []) {
|
|
|
5787
5847
|
// emitter never produced it. Measured 2026-09-10 in a consumer repo: after a compile,
|
|
5788
5848
|
// one `cd` into a subdirectory made a PreToolUse gate fail to load, and a gate that
|
|
5789
5849
|
// cannot load must block โ the repo seized, every command refused including the repair.
|
|
5790
|
-
|
|
5850
|
+
//
|
|
5851
|
+
// ๐ด AND LAUNCHED LOCALLY, NOT THROUGH `npx` โ 193 ms against 2545 ms on a warm
|
|
5852
|
+
// cache, thirteen times, on every tool call. The trailing `|| exit N` is what the
|
|
5853
|
+
// shell does when that binary cannot start at all, and N is decided by the hook's
|
|
5854
|
+
// ROLE: see `hookRuntimeRef` and `hookRuntimeMissingExit` for both measurements
|
|
5855
|
+
// and for why a gate and a nudge must answer differently.
|
|
5856
|
+
gateCommand: `${(0, hook_install_js_1.hookRuntimeRef)(adapter.layout.projectRootTokens)} hook-runtime run-program ` +
|
|
5857
|
+
`${(0, hook_install_js_1.hookGateRef)(ref, adapter.layout.projectRootTokens)} || exit ${(0, hook_install_js_1.hookRuntimeMissingExit)((0, hook_program_js_1.dispatchKind)(program))}`,
|
|
5791
5858
|
dialect: adapter.dialect,
|
|
5792
5859
|
hookProtocol: adapter.hookProtocol,
|
|
5793
5860
|
settingsFormat: adapter.layout.settingsFormat,
|
|
@@ -5869,7 +5936,7 @@ async function installHookFile(file, adapter, registeredProviders = []) {
|
|
|
5869
5936
|
* harness-neutral, so when a repo targets both harnesses the SAME hook is merged
|
|
5870
5937
|
* into `.claude/settings.json` AND `.codex/config.toml` (each in its native
|
|
5871
5938
|
* format, with per-harness warnings) โ never just the first. The harness set is
|
|
5872
|
-
* resolved from the `--harness=` flag, else
|
|
5939
|
+
* resolved from the `--harness=` flag, else `.vigilesrc.json#harnesses`, else auto-detect.
|
|
5873
5940
|
* Returns false if any hook failed to compile for any harness.
|
|
5874
5941
|
*/
|
|
5875
5942
|
async function installHooks(hookFiles, harnessFlag, configHarness) {
|
|
@@ -6389,7 +6456,8 @@ async function main() {
|
|
|
6389
6456
|
if (specs.length > 0)
|
|
6390
6457
|
valid =
|
|
6391
6458
|
(await compile(specs, config, excludes, { harnessFlag })) && valid;
|
|
6392
|
-
valid =
|
|
6459
|
+
valid =
|
|
6460
|
+
(await installHooks(hooks, harnessFlag, (0, adapter_registry_js_1.declaredHarnessNames)(config.harnesses))) && valid;
|
|
6393
6461
|
// Keep an existing whole-harness registry in sync (cheap, opt-in) so the
|
|
6394
6462
|
// user never hand-runs `generate-harness`. Skipped when no harness.gen.ts.
|
|
6395
6463
|
if (specs.length > 0)
|
|
@@ -6517,19 +6585,74 @@ async function main() {
|
|
|
6517
6585
|
const harnessFlag = harnessFlagFrom(args);
|
|
6518
6586
|
// Honor the SAME precedence as lint/compile (dogfood A): --harness= flag,
|
|
6519
6587
|
// else the `.vigilesrc.json` `harness` key, else auto-detect. Previously
|
|
6520
|
-
// audit auto-detected and IGNORED
|
|
6521
|
-
// config-declares `"
|
|
6588
|
+
// audit auto-detected and IGNORED the declared harnesses, so a repo
|
|
6589
|
+
// that config-declares `"harnesses": {"codex": {}}` but carries a CLAUDE.md was still
|
|
6522
6590
|
// scanned as Claude Code. `resolveHarnessSelection` also carries the
|
|
6523
6591
|
// ambiguity/multi-target `notice` so the warning stays consistent.
|
|
6524
6592
|
const selection = (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
6525
6593
|
root,
|
|
6526
6594
|
flag: harnessFlag,
|
|
6527
|
-
configHarness: (0, adapter_registry_js_1.
|
|
6595
|
+
configHarness: (0, adapter_registry_js_1.declaredHarnessNames)(config.harnesses),
|
|
6528
6596
|
});
|
|
6529
6597
|
const adapter = selection.adapter;
|
|
6598
|
+
// EVERY declared harness, each with the roots declared under it โ the
|
|
6599
|
+
// list the scan reads WHOLE. The flag still wins (an explicit override
|
|
6600
|
+
// is singular, and the user typed it), and a repo with no declaration
|
|
6601
|
+
// scans under the one detected adapter exactly as before.
|
|
6602
|
+
//
|
|
6603
|
+
// The PRIMARY is `adapter`, so the report is still labelled and
|
|
6604
|
+
// dialect-checked by one harness; the list is what stops the OTHER
|
|
6605
|
+
// declared harness's skills and instruction file from being invisible.
|
|
6606
|
+
const scanHarnesses = harnessFlag === undefined || harnessFlag === ""
|
|
6607
|
+
? (0, adapter_registry_js_1.resolveDeclaredHarnesses)(root, config.harnesses)
|
|
6608
|
+
.map((d) => ({
|
|
6609
|
+
layout: d.adapter.layout,
|
|
6610
|
+
dialect: d.adapter.dialect,
|
|
6611
|
+
roots: d.roots,
|
|
6612
|
+
}))
|
|
6613
|
+
// The primary first, whatever order the object was written in:
|
|
6614
|
+
// `resolveHarnessSelection` already decided which harness this
|
|
6615
|
+
// report is FOR, and the scan's first entry is the one that
|
|
6616
|
+
// supplies the dialect. Two different answers to "which is
|
|
6617
|
+
// primary" is the bug this change exists to remove.
|
|
6618
|
+
.sort((a, b) => a.layout.name === adapter.layout.name
|
|
6619
|
+
? -1
|
|
6620
|
+
: b.layout.name === adapter.layout.name
|
|
6621
|
+
? 1
|
|
6622
|
+
: 0)
|
|
6623
|
+
: [];
|
|
6624
|
+
// A declared root under a harness that reads no surface there is
|
|
6625
|
+
// REFUSED, not ignored. It is the one silent state the nested shape
|
|
6626
|
+
// would otherwise keep: the line changes nothing, and saying nothing is
|
|
6627
|
+
// the tool agreeing with a belief that is false.
|
|
6628
|
+
const badRoots = (0, surface_discovery_js_1.unresolvedDeclaredRoots)(scanHarnesses.map((h) => ({
|
|
6629
|
+
harness: h.layout.name,
|
|
6630
|
+
layout: h.layout,
|
|
6631
|
+
roots: h.roots,
|
|
6632
|
+
})), (rel) => {
|
|
6633
|
+
const abs = (0, node_path_1.resolve)(root, rel);
|
|
6634
|
+
return ((0, node_fs_1.lstatSync)(abs, { throwIfNoEntry: false })?.isDirectory() === true);
|
|
6635
|
+
});
|
|
6636
|
+
if (badRoots.length > 0) {
|
|
6637
|
+
for (const msg of badRoots)
|
|
6638
|
+
console.error(`โ ${msg}`);
|
|
6639
|
+
process.exitCode = 2;
|
|
6640
|
+
return;
|
|
6641
|
+
}
|
|
6530
6642
|
const report = (0, scan_js_1.scanPlugin)(targets[0], adapter.layout, adapter.dialect, {
|
|
6531
6643
|
sharedDirs: config.sharedDirs,
|
|
6532
6644
|
sharedDirsRoot: sharedDirsRootFor(targets[0]),
|
|
6645
|
+
// `.vigilesrc.json#exclude` reaches surface DISCOVERY, not just the
|
|
6646
|
+
// instruction file. Measured 2026-09-21 before this line existed: a repo
|
|
6647
|
+
// with `{"exclude": [".claude"]}` and one skill at `.claude/skills/demo`
|
|
6648
|
+
// still printed `Skills (1): โ demo` and docked Safety to 90 for it โ the
|
|
6649
|
+
// grade was computed over a tree the user had told the tool to ignore.
|
|
6650
|
+
excludes,
|
|
6651
|
+
// The repo owner's answer to "N skills no harness reads": these are
|
|
6652
|
+
// mine, grade them โ each root under the harness whose layout reads
|
|
6653
|
+
// it, so no array order decides which half of the repo is seen.
|
|
6654
|
+
// `exclude` still wins over it; the walk drops an excluded path first.
|
|
6655
|
+
harnesses: scanHarnesses,
|
|
6533
6656
|
});
|
|
6534
6657
|
if (!json) {
|
|
6535
6658
|
console.log(`Detected harness: ${adapter.name}`);
|
package/dist/core/adapter.d.ts
CHANGED
|
@@ -84,7 +84,28 @@ export interface HarnessAdapter {
|
|
|
84
84
|
* one seam the runner dispatches through). Carried on the bundle so the runner
|
|
85
85
|
* never imports a sibling adapter to find it.
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
/**
|
|
88
|
+
* ๐ด A THUNK, NOT THE DRIVER, and the indirection is the whole point. A driver
|
|
89
|
+
* lives in `harness-test.ts`, which imports the conformance suite, which
|
|
90
|
+
* imports the compiler, which imports the cross-language symbol index, which
|
|
91
|
+
* loads a NATIVE binary. Holding the driver eagerly meant every consumer of an
|
|
92
|
+
* adapter paid for all of it โ including the hook runtime, which reads only
|
|
93
|
+
* `dialect` and `hookProtocol` and never runs a harness test at all.
|
|
94
|
+
*
|
|
95
|
+
* Measured 2026-09-19, `require("./adapter-registry.js")`:
|
|
96
|
+
*
|
|
97
|
+
* eager: 107 modules, 7 ast-grep, 1 native .node
|
|
98
|
+
*
|
|
99
|
+
* ...on a path whose actual work takes about a millisecond. Calling the thunk
|
|
100
|
+
* is what loads the driver, so the test tier pays and the runtime does not.
|
|
101
|
+
*
|
|
102
|
+
* ASYNC because a dynamic `import()` is the only form that defers in BOTH
|
|
103
|
+
* environments this code runs in: the CJS `dist/` build (where TypeScript
|
|
104
|
+
* lowers it to a deferred `require`) and vitest loading the TS sources
|
|
105
|
+
* directly, where a synchronous `require` of a sibling `.ts` does not resolve
|
|
106
|
+
* at all โ measured, not assumed.
|
|
107
|
+
*/
|
|
108
|
+
readonly harnessTestDriver?: () => Promise<HarnessTestDriver>;
|
|
88
109
|
/**
|
|
89
110
|
* How strongly a repo at `root` looks like it targets this harness โ the CLI
|
|
90
111
|
* uses it to auto-detect which adapter to use (the library selects by import).
|
|
@@ -94,5 +115,28 @@ export interface HarnessAdapter {
|
|
|
94
115
|
* `AGENTS.md` that many harnesses share) regardless of registration order.
|
|
95
116
|
*/
|
|
96
117
|
detect(root: string): number;
|
|
118
|
+
/**
|
|
119
|
+
* Is this repo-relative path one THIS harness reads โ "is it mine?"
|
|
120
|
+
*
|
|
121
|
+
* ๐ด THE POINT OF THIS METHOD IS WHAT IT CANNOT DO. It takes a path and
|
|
122
|
+
* returns a boolean: no root, no filesystem, no enumeration. An adapter can
|
|
123
|
+
* therefore LABEL a surface the domain already found and nothing else โ
|
|
124
|
+
* **registering a new adapter cannot make vigiles read more in anyone's
|
|
125
|
+
* repository.** The rejected alternative was each adapter DECLARING roots for
|
|
126
|
+
* the audit to walk, which inverts that: shipping a Cursor adapter would start
|
|
127
|
+
* reading `.cursor/rules` in every user's repo, and a surface no adapter
|
|
128
|
+
* declared would stay invisible. Discovery is the domain's job
|
|
129
|
+
* (`core/surface-discovery.ts`); a claim is an adapter's. See
|
|
130
|
+
* `research/audit-harness-dx.md` ยง9.
|
|
131
|
+
*
|
|
132
|
+
* The consequence the audit reports: a surface NO registered adapter claims is
|
|
133
|
+
* a FINDING, not silence โ measured on a real repo whose 37 skills under
|
|
134
|
+
* `.ai/` graded A (100/100) precisely because nothing read them (#240).
|
|
135
|
+
*
|
|
136
|
+
* Every shipped adapter implements this as `layoutClaims(<its layout>, path)`,
|
|
137
|
+
* so a layout that moves takes its claim with it and the two cannot drift.
|
|
138
|
+
* Override it only for a location the `PluginLayout` fields cannot express.
|
|
139
|
+
*/
|
|
140
|
+
claims(path: string): boolean;
|
|
97
141
|
}
|
|
98
142
|
//# sourceMappingURL=adapter.d.ts.map
|
package/dist/core/compile.js
CHANGED
|
@@ -172,13 +172,23 @@ function validateSymbolRef(file, name, basePath) {
|
|
|
172
172
|
path: file,
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
const support = (0, symbols_js_1.langForFile)(file);
|
|
176
|
+
if (support.kind === "unsupported") {
|
|
176
177
|
return {
|
|
177
178
|
type: "stale-ref",
|
|
178
179
|
message: `Unsupported language for symbol check: "${file}"`,
|
|
179
180
|
path: file,
|
|
180
181
|
};
|
|
181
182
|
}
|
|
183
|
+
if (support.kind === "grammar-missing") {
|
|
184
|
+
// The language is parseable by this tool; the optional grammar is absent in THIS install.
|
|
185
|
+
// Distinct wording on purpose โ see the union's docblock in core/symbols.ts.
|
|
186
|
+
return {
|
|
187
|
+
type: "stale-ref",
|
|
188
|
+
message: `Symbol not checked: the ${support.id} grammar is not installed (npm i -D ${support.pkg})`,
|
|
189
|
+
path: file,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
182
192
|
if (!(0, symbols_js_1.fileDefinesSymbol)(full, name)) {
|
|
183
193
|
return {
|
|
184
194
|
type: "stale-ref",
|