vigiles 5.1.0 → 5.2.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 +2 -2
- package/dist/adapters/claude-code/adapter.js +1 -0
- package/dist/adapters/claude-code/agent-runtime.d.ts +20 -6
- package/dist/adapters/claude-code/agent-runtime.js +51 -8
- package/dist/adapters/claude-code/dialect.js +19 -0
- package/dist/adapters/claude-code/effect-region.d.ts +9 -0
- package/dist/adapters/claude-code/effect-region.js +45 -0
- package/dist/adapters/claude-code/layout.js +3 -0
- package/dist/adapters/claude-code/skill-runtime.d.ts +25 -0
- package/dist/adapters/claude-code/skill-runtime.js +48 -0
- package/dist/adapters/codex/adapter.js +3 -0
- package/dist/adapters/codex/layout.js +3 -0
- package/dist/adapters/opencode/adapter.js +1 -0
- package/dist/adapters/opencode/layout.js +3 -0
- package/dist/check.d.ts +8 -0
- package/dist/check.js +27 -3
- package/dist/cli.js +323 -88
- package/dist/core/adapter.d.ts +10 -0
- package/dist/core/bash-effects.d.ts +41 -0
- package/dist/core/bash-effects.js +405 -0
- package/dist/core/compile.d.ts +3 -1
- package/dist/core/compile.js +162 -39
- package/dist/core/dialect.d.ts +10 -0
- package/dist/core/effects.d.ts +172 -0
- package/dist/core/effects.js +245 -0
- package/dist/core/layout.d.ts +6 -0
- package/dist/core/mcp-tool.d.ts +1 -1
- package/dist/core/orphans.js +21 -0
- package/dist/core/spec.d.ts +142 -3
- package/dist/core/spec.js +48 -0
- package/dist/core/tool-contract.d.ts +1 -1
- package/dist/core/types.d.ts +6 -6
- package/dist/core/validate.js +4 -4
- package/dist/harness-test.d.ts +7 -0
- package/dist/harness-test.js +19 -7
- package/dist/leaderboard.d.ts +2 -0
- package/dist/leaderboard.js +2 -0
- package/dist/optimize.d.ts +74 -0
- package/dist/optimize.js +94 -0
- package/dist/scaffold-test.d.ts +30 -0
- package/dist/scaffold-test.js +158 -0
- package/dist/scan.d.ts +40 -0
- package/dist/scan.js +91 -43
- package/dist/score-explainer.d.ts +69 -0
- package/dist/score-explainer.js +169 -0
- package/dist/test-coverage.d.ts +7 -0
- package/dist/test-coverage.js +39 -24
- package/package.json +2 -1
- package/skills/{migrate-to-spec → adopt-spec}/SKILL.md +4 -4
- package/skills/edit-spec/SKILL.md +1 -1
package/dist/cli.js
CHANGED
|
@@ -19,14 +19,14 @@ const cli_flags_js_1 = require("./cli-flags.js");
|
|
|
19
19
|
const setup_plan_js_1 = require("./setup-plan.js");
|
|
20
20
|
const types_js_1 = require("./core/types.js");
|
|
21
21
|
const test_coverage_js_1 = require("./test-coverage.js");
|
|
22
|
+
const scaffold_test_js_1 = require("./scaffold-test.js");
|
|
22
23
|
const scan_js_1 = require("./scan.js");
|
|
23
|
-
const
|
|
24
|
-
const agent_runtime_js_1 = require("./adapters/claude-code/agent-runtime.js");
|
|
25
|
-
const dialect_js_1 = require("./adapters/claude-code/dialect.js");
|
|
24
|
+
const score_explainer_js_1 = require("./score-explainer.js");
|
|
26
25
|
const scan_behavioral_js_1 = require("./scan-behavioral.js");
|
|
27
26
|
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
28
27
|
const skill_harness_js_1 = require("./skill-harness.js");
|
|
29
28
|
const leaderboard_js_1 = require("./leaderboard.js");
|
|
29
|
+
const optimize_js_1 = require("./optimize.js");
|
|
30
30
|
const compile_js_1 = require("./core/compile.js");
|
|
31
31
|
const proofs_js_1 = require("./core/proofs.js");
|
|
32
32
|
const inline_js_1 = require("./core/inline.js");
|
|
@@ -35,7 +35,8 @@ const generate_schema_js_1 = require("./core/generate-schema.js");
|
|
|
35
35
|
const compose_js_1 = require("./core/compose.js");
|
|
36
36
|
const compile_generator_js_1 = require("./core/compile-generator.js");
|
|
37
37
|
const action_gate_js_1 = require("./action-gate.js");
|
|
38
|
-
const
|
|
38
|
+
const agent_runtime_js_1 = require("./adapters/claude-code/agent-runtime.js");
|
|
39
|
+
const effect_region_js_1 = require("./adapters/claude-code/effect-region.js");
|
|
39
40
|
const tool_intercept_js_1 = require("./tool-intercept.js");
|
|
40
41
|
const refs_js_1 = require("./core/refs.js");
|
|
41
42
|
const mcp_js_1 = require("./core/mcp.js");
|
|
@@ -869,6 +870,17 @@ async function runLint(restArgs, flags, config) {
|
|
|
869
870
|
const json = flags.includes("--json");
|
|
870
871
|
const silent = summary || json;
|
|
871
872
|
const files = findInstructionFiles(restArgs);
|
|
873
|
+
// Resolve the active harness ONCE so the harness-specific checks below run
|
|
874
|
+
// against the right adapter's dialect (tool/event catalogs) and surfaces —
|
|
875
|
+
// not a hard-coded Claude Code default. A subagent-surface rule reports n/a
|
|
876
|
+
// on a harness without subagents (Codex) rather than scanning nothing.
|
|
877
|
+
const harnessFlag = harnessFlagFrom(flags);
|
|
878
|
+
const lintSelection = (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
879
|
+
root: process.cwd(),
|
|
880
|
+
flag: harnessFlag,
|
|
881
|
+
configHarness: (0, adapter_registry_js_1.normalizeHarnessList)(config?.harness),
|
|
882
|
+
});
|
|
883
|
+
const adapter = lintSelection.adapter;
|
|
872
884
|
// 1. Verify hashes and structure
|
|
873
885
|
if (!silent) {
|
|
874
886
|
if (files.length > 0) {
|
|
@@ -927,40 +939,41 @@ async function runLint(restArgs, flags, config) {
|
|
|
927
939
|
// 7b. Untested-surface check — skills/agents/hooks shipping without a test or
|
|
928
940
|
// eval. Warning by default (a nudge, exit 0); set rules.untested-{skill,agent,
|
|
929
941
|
// hook} to "error" to gate CI. See src/test-coverage.ts and docs/rules/.
|
|
930
|
-
const untested = checkUntestedSurfaces(config, silent);
|
|
931
|
-
// 7c.
|
|
932
|
-
// against the harness catalog (the moat).
|
|
933
|
-
//
|
|
934
|
-
|
|
942
|
+
const untested = checkUntestedSurfaces(config, silent, adapter);
|
|
943
|
+
// 7c. Subagent tool-contract check — cross-reference each subagent's `tools:`
|
|
944
|
+
// rail against the harness catalog (the moat). n/a on a harness with no
|
|
945
|
+
// subagents. Off by default unless a severity is configured; warning surfaces
|
|
946
|
+
// a typo/never-available tool, error gates CI.
|
|
947
|
+
const toolContract = checkSubagentToolContracts(config, silent, adapter);
|
|
935
948
|
// 7d. Hook-event check — a hook registered under an event the harness doesn't
|
|
936
949
|
// define never fires. High-precision (close typos only). Off unless configured.
|
|
937
|
-
const hookEvents = checkHookEvents(config, silent);
|
|
938
|
-
// 7e.
|
|
939
|
-
// (name
|
|
940
|
-
const frontmatter = checkFrontmatterSchema(config, silent);
|
|
950
|
+
const hookEvents = checkHookEvents(config, silent, adapter);
|
|
951
|
+
// 7e. Subagent-frontmatter check — a subagent missing required frontmatter
|
|
952
|
+
// (name + description) won't register. n/a on a harness with no subagents.
|
|
953
|
+
const frontmatter = checkFrontmatterSchema(config, silent, adapter);
|
|
941
954
|
// 7f. MCP-config check — a declared MCP server with no command/url can't start.
|
|
942
|
-
const mcpConfig = checkMcpConfig(config, silent);
|
|
955
|
+
const mcpConfig = checkMcpConfig(config, silent, adapter);
|
|
943
956
|
// 7g. Skill-frontmatter — RECOMMEND explicit name/description on skills (a
|
|
944
957
|
// reliable trigger surface). Best-practice nudge; skills load without it.
|
|
945
|
-
const skillFm = checkSkillFrontmatter(config, silent);
|
|
958
|
+
const skillFm = checkSkillFrontmatter(config, silent, adapter);
|
|
946
959
|
// 7h. MCP tool-resolution — an `mcp__server__tool` in a contract whose server
|
|
947
960
|
// the plugin doesn't declare can't resolve (the MCP half of the tool moat).
|
|
948
|
-
const mcpToolResolves = checkMcpToolResolves(config, silent);
|
|
961
|
+
const mcpToolResolves = checkMcpToolResolves(config, silent, adapter);
|
|
949
962
|
// 7i. Hook-script existence — a hook command referencing a missing script file
|
|
950
963
|
// never runs (matches Anthropic's own `claude plugin validate`).
|
|
951
|
-
const hookScripts = checkHookScriptExists(config, silent);
|
|
964
|
+
const hookScripts = checkHookScriptExists(config, silent, adapter);
|
|
952
965
|
// 7j. Disallowed-tools — a `disallowedTools:` block-list typo blocks nothing
|
|
953
|
-
// (the deny-side mirror of
|
|
954
|
-
const disallowedTools = checkDisallowedTools(config, silent);
|
|
966
|
+
// (the deny-side mirror of subagent-tool-contract; close-typo only).
|
|
967
|
+
const disallowedTools = checkDisallowedTools(config, silent, adapter);
|
|
955
968
|
// 7k. Description-overlap — two model-invocable skills with near-identical
|
|
956
969
|
// descriptions collide in the selector (deterministic NCD precision proxy).
|
|
957
|
-
const descriptionOverlap = checkDescriptionOverlap(config, silent);
|
|
970
|
+
const descriptionOverlap = checkDescriptionOverlap(config, silent, adapter);
|
|
958
971
|
// 7l. Frontmatter-valid — a `---` block that isn't valid YAML (warn; js-yaml is
|
|
959
972
|
// stricter than some loaders, so verify before enforcing).
|
|
960
|
-
const frontmatterValid = checkFrontmatterValid(config, silent);
|
|
973
|
+
const frontmatterValid = checkFrontmatterValid(config, silent, adapter);
|
|
961
974
|
// 7m. MCP hook-target — a `type: mcp_tool` hook action that's incomplete or
|
|
962
975
|
// targets an undeclared server (the moat applied to the hook surface).
|
|
963
|
-
const mcpHookTargets = checkMcpHookTargets(config, silent);
|
|
976
|
+
const mcpHookTargets = checkMcpHookTargets(config, silent, adapter);
|
|
964
977
|
// 8. Validate vigiles builder calls inside markdown code blocks. Default
|
|
965
978
|
// is to validate every ref; illustrative blocks opt out via
|
|
966
979
|
// `<!-- vigiles:ignore -->` (single block) or
|
|
@@ -1594,7 +1607,7 @@ function redirectSyncToolTargets(cwd, targets) {
|
|
|
1594
1607
|
}
|
|
1595
1608
|
/** Pillar 1 — specs + types + schema + compile. Scaffolds a spec for every
|
|
1596
1609
|
* instruction file (so `--lint` always delivers a spec), but never compiles
|
|
1597
|
-
* OVER a hand-written file — that is left to the
|
|
1610
|
+
* OVER a hand-written file — that is left to the adopt-spec skill. */
|
|
1598
1611
|
async function setupPillar1(detected, targetValue, harnesses) {
|
|
1599
1612
|
const cwd = process.cwd();
|
|
1600
1613
|
const written = [];
|
|
@@ -1619,7 +1632,7 @@ async function setupPillar1(detected, targetValue, harnesses) {
|
|
|
1619
1632
|
}
|
|
1620
1633
|
if (targetExists && !targetHasHash((0, node_path_1.resolve)(cwd, target))) {
|
|
1621
1634
|
needsMigration.push(target);
|
|
1622
|
-
console.log(` ${target} already has content —
|
|
1635
|
+
console.log(` ${target} already has content — adopt it into a spec with the adopt-spec skill, then \`vigiles compile\`.`);
|
|
1623
1636
|
}
|
|
1624
1637
|
}
|
|
1625
1638
|
// Generate types + schema.
|
|
@@ -1811,7 +1824,7 @@ function printSetupSummary(opts) {
|
|
|
1811
1824
|
console.log("\n---\nSetup complete.\n");
|
|
1812
1825
|
const nextSteps = [];
|
|
1813
1826
|
if (needsMigration.length > 0) {
|
|
1814
|
-
nextSteps.push(`
|
|
1827
|
+
nextSteps.push(`Adopt ${needsMigration.join(", ")} into a spec with the adopt-spec skill, then \`npx vigiles compile\``);
|
|
1815
1828
|
}
|
|
1816
1829
|
else if (specPathsList.length > 0) {
|
|
1817
1830
|
nextSteps.push(`Edit ${specPathsList.join(", ")} — add your conventions, then \`/strengthen\``);
|
|
@@ -1958,16 +1971,16 @@ function checkIntegrityForFiles(files, severity, silent) {
|
|
|
1958
1971
|
return severity === "error" ? errorCount : 0;
|
|
1959
1972
|
}
|
|
1960
1973
|
/**
|
|
1961
|
-
* Apply the per-kind `untested-skill` / `untested-
|
|
1974
|
+
* Apply the per-kind `untested-skill` / `untested-subagent` / `untested-hook` rules:
|
|
1962
1975
|
* find skills/agents/hooks with no test or eval (see src/test-coverage.ts). Each
|
|
1963
1976
|
* kind is gated by its OWN rule severity — a kind set to `false` is not scanned;
|
|
1964
1977
|
* "warn" prints but never fails CI; "error" fails (exit 2). Returns the raw
|
|
1965
1978
|
* untested count plus the severity-gated error count.
|
|
1966
1979
|
*/
|
|
1967
|
-
function checkUntestedSurfaces(config, silent) {
|
|
1980
|
+
function checkUntestedSurfaces(config, silent, adapter) {
|
|
1968
1981
|
const rules = config?.rules;
|
|
1969
1982
|
const skillSev = (0, types_js_1.ruleSeverity)(rules?.["untested-skill"]);
|
|
1970
|
-
const agentSev = (0, types_js_1.ruleSeverity)(rules?.["untested-
|
|
1983
|
+
const agentSev = (0, types_js_1.ruleSeverity)(rules?.["untested-subagent"]);
|
|
1971
1984
|
const hookSev = (0, types_js_1.ruleSeverity)(rules?.["untested-hook"]);
|
|
1972
1985
|
if (!skillSev && !agentSev && !hookSev)
|
|
1973
1986
|
return { untested: 0, errors: 0 };
|
|
@@ -1976,11 +1989,12 @@ function checkUntestedSurfaces(config, silent) {
|
|
|
1976
1989
|
// whichever of the three rules carries them.
|
|
1977
1990
|
const opts = {
|
|
1978
1991
|
...(0, types_js_1.ruleOptions)(rules?.["untested-skill"]),
|
|
1979
|
-
...(0, types_js_1.ruleOptions)(rules?.["untested-
|
|
1992
|
+
...(0, types_js_1.ruleOptions)(rules?.["untested-subagent"]),
|
|
1980
1993
|
...(0, types_js_1.ruleOptions)(rules?.["untested-hook"]),
|
|
1981
1994
|
};
|
|
1982
1995
|
const report = (0, test_coverage_js_1.findUntestedSurfaces)({
|
|
1983
1996
|
basePath: process.cwd(),
|
|
1997
|
+
layout: adapter.layout,
|
|
1984
1998
|
skills: skillSev !== false,
|
|
1985
1999
|
agents: agentSev !== false,
|
|
1986
2000
|
hooks: hookSev !== false,
|
|
@@ -2002,47 +2016,58 @@ function checkUntestedSurfaces(config, silent) {
|
|
|
2002
2016
|
};
|
|
2003
2017
|
}
|
|
2004
2018
|
/**
|
|
2005
|
-
*
|
|
2019
|
+
* A surface-scoped rule (subagent / shell-hook) is configured, but the active
|
|
2020
|
+
* harness doesn't have that surface. Report it as **n/a** — loud, not silent (the
|
|
2021
|
+
* no-silent-skips ethos): the rule isn't failing and isn't passing, it simply
|
|
2022
|
+
* doesn't apply to this harness. Never counts toward issues/errors.
|
|
2023
|
+
*/
|
|
2024
|
+
function reportNotApplicable(check, surface, adapter, silent) {
|
|
2025
|
+
if (silent)
|
|
2026
|
+
return;
|
|
2027
|
+
console.log(`\n${check}:\n`);
|
|
2028
|
+
console.log(` – n/a — ${adapter.name} has no ${surface}`);
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Apply the `subagent-tool-contract` rule: cross-reference every subagent's `tools:`
|
|
2006
2032
|
* rail against the harness tool catalog (the moat — "valid is not true"). Flags
|
|
2007
2033
|
* only the HIGH-CONFIDENCE issues (a never-available tool, or a close typo) via
|
|
2008
2034
|
* the shared `confidentToolIssues` detector — the same code `scan` and
|
|
2009
2035
|
* `compileAgent` use (one-detector-no-drift), so a bare unrecognized tool
|
|
2010
2036
|
* (plugin/MCP-provided) is never a false alarm. Warning by default; set
|
|
2011
|
-
* `
|
|
2037
|
+
* `subagent-tool-contract: "error"` to gate CI. Returns the issue + error counts.
|
|
2012
2038
|
*/
|
|
2013
|
-
function
|
|
2014
|
-
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["
|
|
2039
|
+
function checkSubagentToolContracts(config, silent, adapter) {
|
|
2040
|
+
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["subagent-tool-contract"]);
|
|
2015
2041
|
if (!sev)
|
|
2016
2042
|
return { issues: 0, errors: 0 };
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
}
|
|
2043
|
+
if (!adapter.capabilities.subagents) {
|
|
2044
|
+
reportNotApplicable("Subagent tool-contract check", "subagents", adapter, silent);
|
|
2045
|
+
return { issues: 0, errors: 0 };
|
|
2046
|
+
}
|
|
2047
|
+
// Reuse the loader's already-resolved, layout+dialect-driven agents (the same
|
|
2048
|
+
// `scan` detector — one-detector-no-drift) instead of re-globbing a hard-coded
|
|
2049
|
+
// `agents/` path, so a harness with a different subagent dir Just Works.
|
|
2050
|
+
let agents;
|
|
2051
|
+
try {
|
|
2052
|
+
agents = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).agents;
|
|
2053
|
+
}
|
|
2054
|
+
catch {
|
|
2055
|
+
return { issues: 0, errors: 0 };
|
|
2056
|
+
}
|
|
2021
2057
|
let issues = 0;
|
|
2022
2058
|
let printedHeader = false;
|
|
2023
|
-
for (const
|
|
2024
|
-
|
|
2025
|
-
try {
|
|
2026
|
-
md = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(process.cwd(), rel), "utf-8");
|
|
2027
|
-
}
|
|
2028
|
-
catch {
|
|
2029
|
-
continue;
|
|
2030
|
-
}
|
|
2031
|
-
const tools = (0, agent_runtime_js_1.parseAgentTools)(md);
|
|
2032
|
-
if (tools === null)
|
|
2033
|
-
continue; // no contract → inherits all (a different rule)
|
|
2034
|
-
const found = (0, tool_contract_js_1.confidentToolIssues)((0, tool_contract_js_1.verifyToolContract)(tools, dialect_js_1.claudeCodeDialect));
|
|
2035
|
-
if (found.length === 0)
|
|
2059
|
+
for (const agent of agents) {
|
|
2060
|
+
if (agent.toolIssues.length === 0)
|
|
2036
2061
|
continue;
|
|
2037
|
-
issues +=
|
|
2062
|
+
issues += agent.toolIssues.length;
|
|
2038
2063
|
if (!silent) {
|
|
2039
2064
|
if (!printedHeader) {
|
|
2040
|
-
console.log("\
|
|
2065
|
+
console.log("\nSubagent tool-contract check:\n");
|
|
2041
2066
|
printedHeader = true;
|
|
2042
2067
|
}
|
|
2043
|
-
for (const issue of
|
|
2044
|
-
console.log(` ${sev === "error" ? "✗" : "⚠"} ${
|
|
2045
|
-
ghAnnotate(sev === "error" ? "error" : "warning", issue.message,
|
|
2068
|
+
for (const issue of agent.toolIssues) {
|
|
2069
|
+
console.log(` ${sev === "error" ? "✗" : "⚠"} ${agent.path}: ${issue.message}`);
|
|
2070
|
+
ghAnnotate(sev === "error" ? "error" : "warning", issue.message, agent.path);
|
|
2046
2071
|
}
|
|
2047
2072
|
}
|
|
2048
2073
|
}
|
|
@@ -2054,13 +2079,17 @@ function checkAgentToolContracts(config, silent) {
|
|
|
2054
2079
|
* `hookEventIssues` (the shared detector, high-precision: close typos only, never
|
|
2055
2080
|
* a framework/custom event). Warning by default; "error" gates CI.
|
|
2056
2081
|
*/
|
|
2057
|
-
function checkHookEvents(config, silent) {
|
|
2082
|
+
function checkHookEvents(config, silent, adapter) {
|
|
2058
2083
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-events"]);
|
|
2059
2084
|
if (!sev)
|
|
2060
2085
|
return { issues: 0, errors: 0 };
|
|
2086
|
+
if (!adapter.capabilities.shellHooks) {
|
|
2087
|
+
reportNotApplicable("Hook-event check", "shell hooks", adapter, silent);
|
|
2088
|
+
return { issues: 0, errors: 0 };
|
|
2089
|
+
}
|
|
2061
2090
|
let found;
|
|
2062
2091
|
try {
|
|
2063
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).hookEventIssues;
|
|
2092
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).hookEventIssues;
|
|
2064
2093
|
}
|
|
2065
2094
|
catch {
|
|
2066
2095
|
return { issues: 0, errors: 0 };
|
|
@@ -2075,19 +2104,23 @@ function checkHookEvents(config, silent) {
|
|
|
2075
2104
|
return { issues: found.length, errors: sev === "error" ? found.length : 0 };
|
|
2076
2105
|
}
|
|
2077
2106
|
/**
|
|
2078
|
-
* Apply the `
|
|
2107
|
+
* Apply the `subagent-frontmatter` rule. Two kinds of subagent-frontmatter defect, one
|
|
2079
2108
|
* rule: (1) a subagent MISSING a required field (`name`/`description`) — it won't
|
|
2080
2109
|
* register; (2) a subagent with an INVALID `model:`/`color:` value (a close typo
|
|
2081
2110
|
* of a real one) — it silently falls back / is ignored. Reuses `scanPlugin`'s
|
|
2082
2111
|
* `frontmatterIssues` + `frontmatterValueIssues`. Warning by default; "error" gates CI.
|
|
2083
2112
|
*/
|
|
2084
|
-
function checkFrontmatterSchema(config, silent) {
|
|
2085
|
-
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["
|
|
2113
|
+
function checkFrontmatterSchema(config, silent, adapter) {
|
|
2114
|
+
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["subagent-frontmatter"]);
|
|
2086
2115
|
if (!sev)
|
|
2087
2116
|
return { issues: 0, errors: 0 };
|
|
2117
|
+
if (!adapter.capabilities.subagents) {
|
|
2118
|
+
reportNotApplicable("Subagent-frontmatter check", "subagents", adapter, silent);
|
|
2119
|
+
return { issues: 0, errors: 0 };
|
|
2120
|
+
}
|
|
2088
2121
|
let found;
|
|
2089
2122
|
try {
|
|
2090
|
-
const r = (0, scan_js_1.scanPlugin)(process.cwd());
|
|
2123
|
+
const r = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect);
|
|
2091
2124
|
found = [...r.frontmatterIssues, ...r.frontmatterValueIssues];
|
|
2092
2125
|
}
|
|
2093
2126
|
catch {
|
|
@@ -2110,13 +2143,13 @@ function checkFrontmatterSchema(config, silent) {
|
|
|
2110
2143
|
* default; set "error" to enforce it on your own skills. Reuses `scanPlugin`'s
|
|
2111
2144
|
* `skillMetaIssues`.
|
|
2112
2145
|
*/
|
|
2113
|
-
function checkSkillFrontmatter(config, silent) {
|
|
2146
|
+
function checkSkillFrontmatter(config, silent, adapter) {
|
|
2114
2147
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["skill-frontmatter"]);
|
|
2115
2148
|
if (!sev)
|
|
2116
2149
|
return { issues: 0, errors: 0 };
|
|
2117
2150
|
let found;
|
|
2118
2151
|
try {
|
|
2119
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).skillMetaIssues;
|
|
2152
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).skillMetaIssues;
|
|
2120
2153
|
}
|
|
2121
2154
|
catch {
|
|
2122
2155
|
return { issues: 0, errors: 0 };
|
|
@@ -2135,13 +2168,13 @@ function checkSkillFrontmatter(config, silent) {
|
|
|
2135
2168
|
* (stdio) nor a `url` (http/sse) can't start. Reuses `scanPlugin`'s `mcpIssues`.
|
|
2136
2169
|
* Warning by default; "error" gates CI.
|
|
2137
2170
|
*/
|
|
2138
|
-
function checkMcpConfig(config, silent) {
|
|
2171
|
+
function checkMcpConfig(config, silent, adapter) {
|
|
2139
2172
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["mcp-config"]);
|
|
2140
2173
|
if (!sev)
|
|
2141
2174
|
return { issues: 0, errors: 0 };
|
|
2142
2175
|
let found;
|
|
2143
2176
|
try {
|
|
2144
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).mcpIssues;
|
|
2177
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).mcpIssues;
|
|
2145
2178
|
}
|
|
2146
2179
|
catch {
|
|
2147
2180
|
return { issues: 0, errors: 0 };
|
|
@@ -2162,13 +2195,17 @@ function checkMcpConfig(config, silent) {
|
|
|
2162
2195
|
* `disallowedToolIssues` (close-typo only — high-precision). Warning by default;
|
|
2163
2196
|
* "error" gates CI.
|
|
2164
2197
|
*/
|
|
2165
|
-
function checkDisallowedTools(config, silent) {
|
|
2198
|
+
function checkDisallowedTools(config, silent, adapter) {
|
|
2166
2199
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["disallowed-tools-contract"]);
|
|
2167
2200
|
if (!sev)
|
|
2168
2201
|
return { issues: 0, errors: 0 };
|
|
2202
|
+
if (!adapter.capabilities.subagents) {
|
|
2203
|
+
reportNotApplicable("Disallowed-tools check", "subagents", adapter, silent);
|
|
2204
|
+
return { issues: 0, errors: 0 };
|
|
2205
|
+
}
|
|
2169
2206
|
let found;
|
|
2170
2207
|
try {
|
|
2171
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).agents.flatMap((a) => a.disallowedToolIssues.map((i) => ({ message: i.message, path: a.path })));
|
|
2208
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).agents.flatMap((a) => a.disallowedToolIssues.map((i) => ({ message: i.message, path: a.path })));
|
|
2172
2209
|
}
|
|
2173
2210
|
catch {
|
|
2174
2211
|
return { issues: 0, errors: 0 };
|
|
@@ -2190,13 +2227,13 @@ function checkDisallowedTools(config, silent) {
|
|
|
2190
2227
|
* colon / `<example>` is flagged though it may still load — hence WARN by default
|
|
2191
2228
|
* (verify before setting "error").
|
|
2192
2229
|
*/
|
|
2193
|
-
function checkFrontmatterValid(config, silent) {
|
|
2230
|
+
function checkFrontmatterValid(config, silent, adapter) {
|
|
2194
2231
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["frontmatter-valid"]);
|
|
2195
2232
|
if (!sev)
|
|
2196
2233
|
return { issues: 0, errors: 0 };
|
|
2197
2234
|
let found;
|
|
2198
2235
|
try {
|
|
2199
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).malformedFrontmatter;
|
|
2236
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).malformedFrontmatter;
|
|
2200
2237
|
}
|
|
2201
2238
|
catch {
|
|
2202
2239
|
return { issues: 0, errors: 0 };
|
|
@@ -2217,13 +2254,13 @@ function checkFrontmatterValid(config, silent) {
|
|
|
2217
2254
|
* `scanPlugin`'s `descriptionOverlaps` (calibrated FP-safe: only basically
|
|
2218
2255
|
* identical text). Warning by default; "error" gates CI.
|
|
2219
2256
|
*/
|
|
2220
|
-
function checkDescriptionOverlap(config, silent) {
|
|
2257
|
+
function checkDescriptionOverlap(config, silent, adapter) {
|
|
2221
2258
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["description-overlap"]);
|
|
2222
2259
|
if (!sev)
|
|
2223
2260
|
return { issues: 0, errors: 0 };
|
|
2224
2261
|
let found;
|
|
2225
2262
|
try {
|
|
2226
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).descriptionOverlaps;
|
|
2263
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).descriptionOverlaps;
|
|
2227
2264
|
}
|
|
2228
2265
|
catch {
|
|
2229
2266
|
return { issues: 0, errors: 0 };
|
|
@@ -2244,13 +2281,17 @@ function checkDescriptionOverlap(config, silent) {
|
|
|
2244
2281
|
* `mcpHookIssues` (high-precision: declared-set gated, built-ins allowlisted).
|
|
2245
2282
|
* Warning by default; "error" gates CI.
|
|
2246
2283
|
*/
|
|
2247
|
-
function checkMcpHookTargets(config, silent) {
|
|
2284
|
+
function checkMcpHookTargets(config, silent, adapter) {
|
|
2248
2285
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["mcp-hook-target-resolves"]);
|
|
2249
2286
|
if (!sev)
|
|
2250
2287
|
return { issues: 0, errors: 0 };
|
|
2288
|
+
if (!adapter.capabilities.shellHooks) {
|
|
2289
|
+
reportNotApplicable("MCP hook-target check", "shell hooks", adapter, silent);
|
|
2290
|
+
return { issues: 0, errors: 0 };
|
|
2291
|
+
}
|
|
2251
2292
|
let found;
|
|
2252
2293
|
try {
|
|
2253
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).mcpHookIssues;
|
|
2294
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).mcpHookIssues;
|
|
2254
2295
|
}
|
|
2255
2296
|
catch {
|
|
2256
2297
|
return { issues: 0, errors: 0 };
|
|
@@ -2272,13 +2313,17 @@ function checkMcpHookTargets(config, silent) {
|
|
|
2272
2313
|
* existence-guarded one-liners, inline commands). Matches Anthropic's own
|
|
2273
2314
|
* `claude plugin validate`. Warning by default; "error" gates CI.
|
|
2274
2315
|
*/
|
|
2275
|
-
function checkHookScriptExists(config, silent) {
|
|
2316
|
+
function checkHookScriptExists(config, silent, adapter) {
|
|
2276
2317
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["hook-script-exists"]);
|
|
2277
2318
|
if (!sev)
|
|
2278
2319
|
return { issues: 0, errors: 0 };
|
|
2320
|
+
if (!adapter.capabilities.shellHooks) {
|
|
2321
|
+
reportNotApplicable("Hook-script existence check", "shell hooks", adapter, silent);
|
|
2322
|
+
return { issues: 0, errors: 0 };
|
|
2323
|
+
}
|
|
2279
2324
|
let missing;
|
|
2280
2325
|
try {
|
|
2281
|
-
missing = (0, scan_js_1.scanPlugin)(process.cwd()).hooks.filter((h) => h.status === "missing");
|
|
2326
|
+
missing = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).hooks.filter((h) => h.status === "missing");
|
|
2282
2327
|
}
|
|
2283
2328
|
catch {
|
|
2284
2329
|
return { issues: 0, errors: 0 };
|
|
@@ -2303,13 +2348,17 @@ function checkHookScriptExists(config, silent) {
|
|
|
2303
2348
|
* — high-precision (gated on a declared set, built-ins allowlisted, the
|
|
2304
2349
|
* plugin-namespaced form skipped). Warning by default; "error" gates CI.
|
|
2305
2350
|
*/
|
|
2306
|
-
function checkMcpToolResolves(config, silent) {
|
|
2351
|
+
function checkMcpToolResolves(config, silent, adapter) {
|
|
2307
2352
|
const sev = (0, types_js_1.ruleSeverity)(config?.rules?.["mcp-tool-resolves"]);
|
|
2308
2353
|
if (!sev)
|
|
2309
2354
|
return { issues: 0, errors: 0 };
|
|
2355
|
+
if (!adapter.capabilities.subagents) {
|
|
2356
|
+
reportNotApplicable("MCP tool-resolution check", "subagents", adapter, silent);
|
|
2357
|
+
return { issues: 0, errors: 0 };
|
|
2358
|
+
}
|
|
2310
2359
|
let found;
|
|
2311
2360
|
try {
|
|
2312
|
-
found = (0, scan_js_1.scanPlugin)(process.cwd()).agents.flatMap((a) => a.mcpToolIssues.map((i) => ({ message: i.message, path: a.path })));
|
|
2361
|
+
found = (0, scan_js_1.scanPlugin)(process.cwd(), adapter.layout, adapter.dialect).agents.flatMap((a) => a.mcpToolIssues.map((i) => ({ message: i.message, path: a.path })));
|
|
2313
2362
|
}
|
|
2314
2363
|
catch {
|
|
2315
2364
|
return { issues: 0, errors: 0 };
|
|
@@ -2583,6 +2632,129 @@ function handleRunScripts(kind, args, restArgs) {
|
|
|
2583
2632
|
process.exit(1);
|
|
2584
2633
|
}
|
|
2585
2634
|
}
|
|
2635
|
+
/** Parse the `--harness=<name>` override out of an argv list (the one definition). */
|
|
2636
|
+
function harnessFlagFrom(argv) {
|
|
2637
|
+
return argv
|
|
2638
|
+
.find((a) => a.startsWith("--harness="))
|
|
2639
|
+
?.slice("--harness=".length);
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* `vigiles explain <dir> [name]` — the deterministic WHY behind a low score (C4):
|
|
2643
|
+
* scan a plugin and surface the structural CAUSE of a behavioral symptom + the
|
|
2644
|
+
* one-line fix. No model — it reads the same `ScanReport` `scan` computes. An
|
|
2645
|
+
* optional surface name narrows to one underperforming skill/agent (the
|
|
2646
|
+
* optimizer's call). `--json` for the agent-consumable shape, `--harness=` to
|
|
2647
|
+
* override detection.
|
|
2648
|
+
*/
|
|
2649
|
+
function handleExplain(restArgs, args) {
|
|
2650
|
+
const dir = (0, node_path_1.resolve)(restArgs[0] ?? ".");
|
|
2651
|
+
const surface = restArgs[1];
|
|
2652
|
+
const json = args.includes("--json");
|
|
2653
|
+
const harnessFlag = harnessFlagFrom(args);
|
|
2654
|
+
const adapter = harnessFlag
|
|
2655
|
+
? (0, adapter_registry_js_1.resolveAdapter)(dir, harnessFlag)
|
|
2656
|
+
: (0, adapter_registry_js_1.detectAdapterResult)(dir).adapter;
|
|
2657
|
+
const report = (0, scan_js_1.scanPlugin)(dir, adapter.layout, adapter.dialect);
|
|
2658
|
+
const exps = surface ? (0, score_explainer_js_1.explainSurface)(report, surface) : (0, score_explainer_js_1.explainScore)(report);
|
|
2659
|
+
if (json) {
|
|
2660
|
+
console.log(JSON.stringify(exps, null, 2));
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
if (surface)
|
|
2664
|
+
console.log(`Explaining "${surface}":\n`);
|
|
2665
|
+
console.log((0, score_explainer_js_1.formatExplanations)(exps));
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2668
|
+
* The plugin's declared name for the namespaced skill id, read from the layout's
|
|
2669
|
+
* manifest (adapter-aware path, not a hardcoded `.claude-plugin/`), falling back to
|
|
2670
|
+
* the dir basename. JSON manifests only for now (a TOML/Codex manifest → basename).
|
|
2671
|
+
*/
|
|
2672
|
+
function pluginNameFor(dir, manifestPath) {
|
|
2673
|
+
try {
|
|
2674
|
+
const manifest = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(dir, manifestPath), "utf-8"));
|
|
2675
|
+
if (typeof manifest.name === "string" && manifest.name)
|
|
2676
|
+
return manifest.name;
|
|
2677
|
+
}
|
|
2678
|
+
catch {
|
|
2679
|
+
/* missing / non-JSON manifest → fall back */
|
|
2680
|
+
}
|
|
2681
|
+
return (0, node_path_1.basename)(dir);
|
|
2682
|
+
}
|
|
2683
|
+
/** Enrich an untested Surface with the metadata the right template needs. */
|
|
2684
|
+
function scaffoldInputFor(s, report, pluginName) {
|
|
2685
|
+
const base = { kind: s.kind, name: s.name, path: s.path };
|
|
2686
|
+
switch (s.kind) {
|
|
2687
|
+
case "skill": {
|
|
2688
|
+
const sk = report.skills.find((x) => x.name === s.name);
|
|
2689
|
+
return { ...base, pluginName, userInvoked: sk?.userInvoked };
|
|
2690
|
+
}
|
|
2691
|
+
case "agent": {
|
|
2692
|
+
const ag = report.agents.find((x) => x.name === s.name);
|
|
2693
|
+
return { ...base, tools: ag?.tools ?? null };
|
|
2694
|
+
}
|
|
2695
|
+
case "hook":
|
|
2696
|
+
return { ...base, hookCommand: `bash ${s.path}` };
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* `vigiles scaffold-test [dir]` — generate a runnable STARTER test for each
|
|
2701
|
+
* untested skill/agent/hook (B1, test-gen from free-form). Reuses the
|
|
2702
|
+
* untested-surface detector for the list + `scan` for the metadata, then emits the
|
|
2703
|
+
* cheapest meaningful tier per kind (hook → `runHook`, skill → `measureTriggerRate`,
|
|
2704
|
+
* subagent → `runHarnessTest`) at the surface's suggested test path. Dry-run by
|
|
2705
|
+
* default (prints the scaffolds); `--write` creates the files (never clobbering an
|
|
2706
|
+
* existing one); `--json` for the agent-consumable `{ path, content }[]`.
|
|
2707
|
+
*/
|
|
2708
|
+
function handleScaffoldTest(restArgs, args) {
|
|
2709
|
+
const dir = (0, node_path_1.resolve)(restArgs[0] ?? ".");
|
|
2710
|
+
const write = args.includes("--write");
|
|
2711
|
+
const json = args.includes("--json");
|
|
2712
|
+
const harnessFlag = harnessFlagFrom(args);
|
|
2713
|
+
const adapter = harnessFlag
|
|
2714
|
+
? (0, adapter_registry_js_1.resolveAdapter)(dir, harnessFlag)
|
|
2715
|
+
: (0, adapter_registry_js_1.detectAdapterResult)(dir).adapter;
|
|
2716
|
+
const { untested } = (0, test_coverage_js_1.findUntestedSurfaces)({
|
|
2717
|
+
basePath: dir,
|
|
2718
|
+
layout: adapter.layout,
|
|
2719
|
+
});
|
|
2720
|
+
const report = (0, scan_js_1.scanPlugin)(dir, adapter.layout, adapter.dialect);
|
|
2721
|
+
const pluginName = pluginNameFor(dir, adapter.layout.manifestPath);
|
|
2722
|
+
const scaffolds = untested.map((s) => (0, scaffold_test_js_1.scaffoldTest)(scaffoldInputFor(s, report, pluginName)));
|
|
2723
|
+
if (json) {
|
|
2724
|
+
console.log(JSON.stringify(scaffolds, null, 2));
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
if (!write) {
|
|
2728
|
+
console.log((0, scaffold_test_js_1.formatScaffolds)(scaffolds));
|
|
2729
|
+
for (const s of scaffolds) {
|
|
2730
|
+
console.log(`\n# ${s.path}\n`);
|
|
2731
|
+
console.log(s.content);
|
|
2732
|
+
}
|
|
2733
|
+
if (scaffolds.length > 0) {
|
|
2734
|
+
console.log("Re-run with --write to create these files.");
|
|
2735
|
+
}
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const written = [];
|
|
2739
|
+
const skipped = [];
|
|
2740
|
+
for (const s of scaffolds) {
|
|
2741
|
+
const target = (0, node_path_1.resolve)(dir, s.path);
|
|
2742
|
+
if ((0, node_fs_1.existsSync)(target)) {
|
|
2743
|
+
skipped.push(s.path);
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true });
|
|
2747
|
+
(0, node_fs_1.writeFileSync)(target, s.content);
|
|
2748
|
+
written.push(s.path);
|
|
2749
|
+
}
|
|
2750
|
+
for (const p of written)
|
|
2751
|
+
console.log(`✓ wrote ${p}`);
|
|
2752
|
+
for (const p of skipped)
|
|
2753
|
+
console.log(`⊘ skipped ${p} (already exists)`);
|
|
2754
|
+
if (written.length === 0 && skipped.length === 0) {
|
|
2755
|
+
console.log("Nothing to scaffold — every surface already has a test.");
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2586
2758
|
function printUsage(command) {
|
|
2587
2759
|
console.log("vigiles — compile typed specs to instruction files");
|
|
2588
2760
|
console.log("");
|
|
@@ -2592,6 +2764,8 @@ function printUsage(command) {
|
|
|
2592
2764
|
console.log(" vigiles lint [files...] Verify references, find gaps in instruction files");
|
|
2593
2765
|
console.log(" vigiles test [files...] Run *.harness.mjs deterministic harness tests");
|
|
2594
2766
|
console.log(" vigiles eval [files...] Run *.eval.mjs real-model harness evals (--trials=N, --min=N, --no-skip)");
|
|
2767
|
+
console.log(" vigiles explain <dir> [name] Deterministic WHY a skill/agent underperforms + the fix (--json, --harness=)");
|
|
2768
|
+
console.log(" vigiles scaffold-test [dir] Generate a starter test for each untested skill/agent/hook (--write, --json)");
|
|
2595
2769
|
console.log("");
|
|
2596
2770
|
console.log("Examples:");
|
|
2597
2771
|
console.log(" vigiles init Auto-detect project, create specs, wire CI");
|
|
@@ -2701,6 +2875,42 @@ function skillStartCommand(target) {
|
|
|
2701
2875
|
(0, skill_runtime_js_1.setActiveSkill)(process.cwd(), target);
|
|
2702
2876
|
console.log(`Active skill: ${target}`);
|
|
2703
2877
|
}
|
|
2878
|
+
/**
|
|
2879
|
+
* PreToolUse-hook entrypoint: enforce the active skill's declared purity floor.
|
|
2880
|
+
* Reads the tool event on stdin, parses the `vigiles:purity:` marker from the
|
|
2881
|
+
* active skill's compiled SKILL.md, and blocks (exit 2 + reason on stderr) any
|
|
2882
|
+
* tool call that violates the declared floor — refining `Bash` by the live
|
|
2883
|
+
* command via `isReadOnlyBash`. Skills have no tools-allowlist rail; this gate
|
|
2884
|
+
* is purity-only. Mirrors `agentHookCommand` for skills.
|
|
2885
|
+
*/
|
|
2886
|
+
function skillToolHookCommand() {
|
|
2887
|
+
let raw = "";
|
|
2888
|
+
try {
|
|
2889
|
+
raw = (0, node_fs_1.readFileSync)(0, "utf-8");
|
|
2890
|
+
}
|
|
2891
|
+
catch {
|
|
2892
|
+
/* no stdin */
|
|
2893
|
+
}
|
|
2894
|
+
let tool = "";
|
|
2895
|
+
let command;
|
|
2896
|
+
try {
|
|
2897
|
+
const parsed = JSON.parse(raw);
|
|
2898
|
+
tool = parsed.tool_name ?? "";
|
|
2899
|
+
if (typeof parsed.tool_input?.command === "string") {
|
|
2900
|
+
command = parsed.tool_input.command;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
catch {
|
|
2904
|
+
/* malformed input → no tool, allow */
|
|
2905
|
+
}
|
|
2906
|
+
if (!tool)
|
|
2907
|
+
return;
|
|
2908
|
+
const decision = (0, skill_runtime_js_1.evaluateSkillPreToolUse)(process.cwd(), tool, command);
|
|
2909
|
+
if (!decision.allow) {
|
|
2910
|
+
console.error(decision.message);
|
|
2911
|
+
process.exit(2);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2704
2914
|
/**
|
|
2705
2915
|
* PreToolUse-hook entrypoint: enforce the active subagent's allowed-tools
|
|
2706
2916
|
* contract. Reads the tool event on stdin, parses the active agent's compiled
|
|
@@ -2716,15 +2926,20 @@ function agentHookCommand() {
|
|
|
2716
2926
|
/* no stdin */
|
|
2717
2927
|
}
|
|
2718
2928
|
let tool = "";
|
|
2929
|
+
let command;
|
|
2719
2930
|
try {
|
|
2720
|
-
|
|
2931
|
+
const parsed = JSON.parse(raw);
|
|
2932
|
+
tool = parsed.tool_name ?? "";
|
|
2933
|
+
if (typeof parsed.tool_input?.command === "string") {
|
|
2934
|
+
command = parsed.tool_input.command;
|
|
2935
|
+
}
|
|
2721
2936
|
}
|
|
2722
2937
|
catch {
|
|
2723
2938
|
/* malformed input → no tool, allow */
|
|
2724
2939
|
}
|
|
2725
2940
|
if (!tool)
|
|
2726
2941
|
return;
|
|
2727
|
-
const decision = (0,
|
|
2942
|
+
const decision = (0, agent_runtime_js_1.evaluatePreToolUse)(process.cwd(), tool, command);
|
|
2728
2943
|
if (!decision.allow) {
|
|
2729
2944
|
console.error(decision.message);
|
|
2730
2945
|
process.exit(2);
|
|
@@ -2760,7 +2975,7 @@ function agentStartCommand(target) {
|
|
|
2760
2975
|
console.error("Usage: vigiles agent-start <agents/<name>.md>");
|
|
2761
2976
|
process.exit(2);
|
|
2762
2977
|
}
|
|
2763
|
-
(0,
|
|
2978
|
+
(0, agent_runtime_js_1.setActiveAgent)(process.cwd(), target);
|
|
2764
2979
|
console.log(`Active agent: ${target}`);
|
|
2765
2980
|
}
|
|
2766
2981
|
/** Dispatch the skill-runtime subcommands. Returns false if unrecognized. */
|
|
@@ -2778,11 +2993,14 @@ function handleSkillCommand(command, restArgs) {
|
|
|
2778
2993
|
case "skill-hook":
|
|
2779
2994
|
skillHookCommand();
|
|
2780
2995
|
return true;
|
|
2996
|
+
case "skill-tool-hook":
|
|
2997
|
+
skillToolHookCommand();
|
|
2998
|
+
return true;
|
|
2781
2999
|
case "agent-start":
|
|
2782
3000
|
agentStartCommand(restArgs[0]);
|
|
2783
3001
|
return true;
|
|
2784
3002
|
case "agent-done":
|
|
2785
|
-
(0,
|
|
3003
|
+
(0, agent_runtime_js_1.clearActiveAgent)(process.cwd());
|
|
2786
3004
|
return true;
|
|
2787
3005
|
case "agent-hook":
|
|
2788
3006
|
agentHookCommand();
|
|
@@ -2799,6 +3017,13 @@ function handleSkillCommand(command, restArgs) {
|
|
|
2799
3017
|
case "refs-hook":
|
|
2800
3018
|
refsHookCommand();
|
|
2801
3019
|
return true;
|
|
3020
|
+
case "effect-enter":
|
|
3021
|
+
(0, effect_region_js_1.setEffectActive)(process.cwd());
|
|
3022
|
+
console.log("Effect boundary entered.");
|
|
3023
|
+
return true;
|
|
3024
|
+
case "effect-exit":
|
|
3025
|
+
(0, effect_region_js_1.clearEffectActive)(process.cwd());
|
|
3026
|
+
return true;
|
|
2802
3027
|
default:
|
|
2803
3028
|
return false;
|
|
2804
3029
|
}
|
|
@@ -2969,9 +3194,7 @@ async function main() {
|
|
|
2969
3194
|
console.log("Run `vigiles init` to create one.");
|
|
2970
3195
|
process.exit(0);
|
|
2971
3196
|
}
|
|
2972
|
-
const harnessFlag = args
|
|
2973
|
-
.find((a) => a.startsWith("--harness="))
|
|
2974
|
-
?.slice("--harness=".length);
|
|
3197
|
+
const harnessFlag = harnessFlagFrom(args);
|
|
2975
3198
|
const valid = await compile(specs, config, { harnessFlag });
|
|
2976
3199
|
console.log("");
|
|
2977
3200
|
if (valid) {
|
|
@@ -3033,9 +3256,7 @@ async function main() {
|
|
|
3033
3256
|
}
|
|
3034
3257
|
else {
|
|
3035
3258
|
const root = (0, node_path_1.resolve)(targets[0]);
|
|
3036
|
-
const harnessFlag = args
|
|
3037
|
-
.find((a) => a.startsWith("--harness="))
|
|
3038
|
-
?.slice("--harness=".length);
|
|
3259
|
+
const harnessFlag = harnessFlagFrom(args);
|
|
3039
3260
|
const det = (0, adapter_registry_js_1.detectAdapterResult)(root);
|
|
3040
3261
|
const adapter = harnessFlag
|
|
3041
3262
|
? (0, adapter_registry_js_1.resolveAdapter)(root, harnessFlag)
|
|
@@ -3048,7 +3269,15 @@ async function main() {
|
|
|
3048
3269
|
}
|
|
3049
3270
|
console.log("");
|
|
3050
3271
|
}
|
|
3051
|
-
|
|
3272
|
+
if (args.includes("--fix-plan")) {
|
|
3273
|
+
// The deterministic optimization lens on the SAME report: health score
|
|
3274
|
+
// + the ranked free fixes to clear before measuring (the A2 spine).
|
|
3275
|
+
const plan = (0, optimize_js_1.optimize)(report);
|
|
3276
|
+
console.log(json ? JSON.stringify(plan, null, 2) : (0, optimize_js_1.formatOptimize)(plan));
|
|
3277
|
+
}
|
|
3278
|
+
else {
|
|
3279
|
+
console.log(json ? JSON.stringify(report, null, 2) : (0, scan_js_1.formatScanReport)(report));
|
|
3280
|
+
}
|
|
3052
3281
|
if (wantTrigger) {
|
|
3053
3282
|
const harness = adapter.name === "codex" ? "codex" : "claude-code";
|
|
3054
3283
|
await handleScanTrigger(root, args, json, harness);
|
|
@@ -3056,6 +3285,12 @@ async function main() {
|
|
|
3056
3285
|
}
|
|
3057
3286
|
break;
|
|
3058
3287
|
}
|
|
3288
|
+
case "explain":
|
|
3289
|
+
handleExplain(restArgs, args);
|
|
3290
|
+
break;
|
|
3291
|
+
case "scaffold-test":
|
|
3292
|
+
handleScaffoldTest(restArgs, args);
|
|
3293
|
+
break;
|
|
3059
3294
|
// --- Plumbing ---
|
|
3060
3295
|
case "generate-types":
|
|
3061
3296
|
handleGenerateTypes(args, restArgs);
|