vigiles 2.6.0 → 3.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/.claude-plugin/plugin.json +9 -0
- package/README.md +74 -129
- package/action.yml +143 -7
- package/dist/action-gate.d.ts +1 -1
- package/dist/action-gate.js +1 -1
- package/dist/adapter-conformance.d.ts +30 -0
- package/dist/adapter-conformance.js +153 -0
- package/dist/adapter-registry.d.ts +42 -0
- package/dist/adapter-registry.js +55 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +16 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.js +46 -0
- package/dist/{agent-result.d.ts → adapters/claude-code/agent-result.d.ts} +1 -1
- package/dist/adapters/claude-code/dialect.d.ts +13 -0
- package/dist/adapters/claude-code/dialect.js +51 -0
- package/dist/adapters/claude-code/egress-entry.d.ts +2 -0
- package/dist/adapters/claude-code/egress-entry.js +115 -0
- package/dist/adapters/claude-code/egress.d.ts +114 -0
- package/dist/adapters/claude-code/egress.js +276 -0
- package/dist/{eval-cache.d.ts → adapters/claude-code/eval-cache.d.ts} +1 -1
- package/dist/{eval-cache.js → adapters/claude-code/eval-cache.js} +1 -1
- package/dist/{eval.d.ts → adapters/claude-code/eval.d.ts} +28 -4
- package/dist/{eval.js → adapters/claude-code/eval.js} +61 -25
- package/dist/{harness-test.d.ts → adapters/claude-code/harness-test.d.ts} +33 -37
- package/dist/{harness-test.js → adapters/claude-code/harness-test.js} +124 -43
- package/dist/adapters/claude-code/hook-protocol.d.ts +10 -0
- package/dist/adapters/claude-code/hook-protocol.js +10 -0
- package/dist/adapters/claude-code/layout.d.ts +8 -0
- package/dist/adapters/claude-code/layout.js +18 -0
- package/dist/{mock-model.d.ts → adapters/claude-code/mock-model.d.ts} +2 -24
- package/dist/adapters/claude-code/model-mock.d.ts +11 -0
- package/dist/adapters/claude-code/model-mock.js +10 -0
- package/dist/adapters/claude-code/plugin-loader.d.ts +25 -0
- package/dist/adapters/claude-code/plugin-loader.js +19 -0
- package/dist/{run-hook.d.ts → adapters/claude-code/run-hook.d.ts} +50 -3
- package/dist/{run-hook.js → adapters/claude-code/run-hook.js} +178 -18
- package/dist/adapters/claude-code/run-scripts.d.ts +52 -0
- package/dist/adapters/claude-code/run-scripts.js +150 -0
- package/dist/adapters/claude-code/runtime.d.ts +16 -0
- package/dist/adapters/claude-code/runtime.js +39 -0
- package/dist/{sandbox.d.ts → adapters/claude-code/sandbox.d.ts} +10 -0
- package/dist/{sandbox.js → adapters/claude-code/sandbox.js} +5 -3
- package/dist/{skill-driver.d.ts → adapters/claude-code/skill-driver.d.ts} +1 -1
- package/dist/adapters/codex/adapter.d.ts +3 -0
- package/dist/adapters/codex/adapter.js +49 -0
- package/dist/adapters/codex/dialect.d.ts +10 -0
- package/dist/adapters/codex/dialect.js +30 -0
- package/dist/adapters/codex/driver.d.ts +20 -0
- package/dist/adapters/codex/driver.js +89 -0
- package/dist/adapters/codex/hook-protocol.d.ts +10 -0
- package/dist/adapters/codex/hook-protocol.js +18 -0
- package/dist/adapters/codex/layout.d.ts +16 -0
- package/dist/adapters/codex/layout.js +18 -0
- package/dist/adapters/codex/mock-model.d.ts +52 -0
- package/dist/adapters/codex/mock-model.js +210 -0
- package/dist/adapters/codex/model-mock.d.ts +11 -0
- package/dist/adapters/codex/model-mock.js +10 -0
- package/dist/adapters/codex/runtime.d.ts +38 -0
- package/dist/adapters/codex/runtime.js +62 -0
- package/dist/adapters/opencode/adapter.d.ts +3 -0
- package/dist/adapters/opencode/adapter.js +48 -0
- package/dist/adapters/opencode/dialect.d.ts +10 -0
- package/dist/adapters/opencode/dialect.js +36 -0
- package/dist/adapters/opencode/layout.d.ts +9 -0
- package/dist/adapters/opencode/layout.js +25 -0
- package/dist/adapters/opencode/model-mock.d.ts +10 -0
- package/dist/adapters/opencode/model-mock.js +10 -0
- package/dist/adapters/opencode/runtime.d.ts +9 -0
- package/dist/adapters/opencode/runtime.js +21 -0
- package/dist/claude-code.d.ts +8 -2
- package/dist/claude-code.js +8 -2
- package/dist/cli-flags.d.ts +22 -0
- package/dist/cli-flags.js +38 -0
- package/dist/cli.js +406 -206
- package/dist/codex.d.ts +20 -0
- package/dist/codex.js +36 -0
- package/dist/community-skills.d.ts +1 -1
- package/dist/community-skills.js +2 -2
- package/dist/core/adapter.d.ts +88 -0
- package/dist/core/adapter.js +3 -0
- package/dist/{compile.d.ts → core/compile.d.ts} +11 -2
- package/dist/{compile.js → core/compile.js} +68 -53
- package/dist/core/compose.d.ts +79 -0
- package/dist/core/compose.js +145 -0
- package/dist/core/dialect.d.ts +51 -0
- package/dist/core/dialect.js +3 -0
- package/dist/core/harness-driver.d.ts +134 -0
- package/dist/core/harness-driver.js +3 -0
- package/dist/core/hook-protocol.d.ts +28 -0
- package/dist/core/hook-protocol.js +3 -0
- package/dist/core/layout.d.ts +43 -0
- package/dist/core/layout.js +3 -0
- package/dist/core/model-mock.d.ts +27 -0
- package/dist/core/model-mock.js +3 -0
- package/dist/core/refs.d.ts +62 -0
- package/dist/{refs.js → core/refs.js} +49 -23
- package/dist/core/runtime.d.ts +38 -0
- package/dist/core/runtime.js +3 -0
- package/dist/{types.d.ts → core/types.d.ts} +29 -0
- package/dist/{validate.d.ts → core/validate.d.ts} +1 -1
- package/dist/{validate.js → core/validate.js} +41 -4
- package/dist/e2e.d.ts +19 -0
- package/dist/e2e.js +39 -0
- package/dist/harness-assert.d.ts +29 -14
- package/dist/harness-assert.js +37 -10
- package/dist/integration.d.ts +16 -0
- package/dist/integration.js +32 -0
- package/dist/leaderboard.d.ts +33 -0
- package/dist/leaderboard.js +107 -0
- package/dist/linting.d.ts +2 -2
- package/dist/linting.js +2 -2
- package/dist/plugin-loader.d.ts +6 -3
- package/dist/plugin-loader.js +101 -49
- package/dist/scan.d.ts +49 -0
- package/dist/scan.js +177 -0
- package/dist/setup-plan.d.ts +51 -0
- package/dist/setup-plan.js +85 -0
- package/dist/skill-test.d.ts +1 -1
- package/dist/skill-test.js +1 -1
- package/dist/test-coverage.d.ts +71 -0
- package/dist/test-coverage.js +228 -0
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +3 -3
- package/dist/unit.d.ts +17 -0
- package/dist/unit.js +36 -0
- package/hooks/refs-nudge.sh +24 -0
- package/package.json +30 -15
- package/skills/audit-feedback-loop/SKILL.md +76 -0
- package/skills/edit-spec/SKILL.md +131 -0
- package/skills/enforce-rules-format/SKILL.md +71 -0
- package/skills/generate-logo/SKILL.md +103 -0
- package/skills/generate-rule/SKILL.md +64 -0
- package/skills/linter-docs/clippy.md +241 -0
- package/skills/linter-docs/eslint.md +384 -0
- package/skills/linter-docs/pylint.md +288 -0
- package/skills/linter-docs/rubocop.md +277 -0
- package/skills/linter-docs/ruff.md +187 -0
- package/skills/linter-docs/stylelint.md +247 -0
- package/skills/migrate-to-spec/SKILL.md +126 -0
- package/skills/pr-to-lint-rule/SKILL.md +97 -0
- package/skills/strengthen/SKILL.md +168 -0
- package/skills/test-harness/SKILL.md +157 -0
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -180
- package/dist/refs.d.ts +0 -44
- package/dist/run-scripts.d.ts +0 -20
- package/dist/run-scripts.js +0 -70
- /package/dist/{agent-result.js → adapters/claude-code/agent-result.js} +0 -0
- /package/dist/{agent-runtime.d.ts → adapters/claude-code/agent-runtime.d.ts} +0 -0
- /package/dist/{agent-runtime.js → adapters/claude-code/agent-runtime.js} +0 -0
- /package/dist/{egress-proxy.d.ts → adapters/claude-code/egress-proxy.d.ts} +0 -0
- /package/dist/{egress-proxy.js → adapters/claude-code/egress-proxy.js} +0 -0
- /package/dist/{eval-baseline.d.ts → adapters/claude-code/eval-baseline.d.ts} +0 -0
- /package/dist/{eval-baseline.js → adapters/claude-code/eval-baseline.js} +0 -0
- /package/dist/{judge.d.ts → adapters/claude-code/judge.d.ts} +0 -0
- /package/dist/{judge.js → adapters/claude-code/judge.js} +0 -0
- /package/dist/{mock-entry.d.ts → adapters/claude-code/mock-entry.d.ts} +0 -0
- /package/dist/{mock-entry.js → adapters/claude-code/mock-entry.js} +0 -0
- /package/dist/{mock-model.js → adapters/claude-code/mock-model.js} +0 -0
- /package/dist/{skill-driver.js → adapters/claude-code/skill-driver.js} +0 -0
- /package/dist/{skill-runtime.d.ts → adapters/claude-code/skill-runtime.d.ts} +0 -0
- /package/dist/{skill-runtime.js → adapters/claude-code/skill-runtime.js} +0 -0
- /package/dist/{stats.d.ts → adapters/claude-code/stats.d.ts} +0 -0
- /package/dist/{stats.js → adapters/claude-code/stats.js} +0 -0
- /package/dist/{compile-generator.d.ts → core/compile-generator.d.ts} +0 -0
- /package/dist/{compile-generator.js → core/compile-generator.js} +0 -0
- /package/dist/{coverage.d.ts → core/coverage.d.ts} +0 -0
- /package/dist/{coverage.js → core/coverage.js} +0 -0
- /package/dist/{doc-refs.d.ts → core/doc-refs.d.ts} +0 -0
- /package/dist/{doc-refs.js → core/doc-refs.js} +0 -0
- /package/dist/{evolve.d.ts → core/evolve.d.ts} +0 -0
- /package/dist/{evolve.js → core/evolve.js} +0 -0
- /package/dist/{frontmatter.d.ts → core/frontmatter.d.ts} +0 -0
- /package/dist/{frontmatter.js → core/frontmatter.js} +0 -0
- /package/dist/{generate-schema.d.ts → core/generate-schema.d.ts} +0 -0
- /package/dist/{generate-schema.js → core/generate-schema.js} +0 -0
- /package/dist/{generate-types.d.ts → core/generate-types.d.ts} +0 -0
- /package/dist/{generate-types.js → core/generate-types.js} +0 -0
- /package/dist/{hash.d.ts → core/hash.d.ts} +0 -0
- /package/dist/{hash.js → core/hash.js} +0 -0
- /package/dist/{inline.d.ts → core/inline.d.ts} +0 -0
- /package/dist/{inline.js → core/inline.js} +0 -0
- /package/dist/{integrity.d.ts → core/integrity.d.ts} +0 -0
- /package/dist/{integrity.js → core/integrity.js} +0 -0
- /package/dist/{linters.d.ts → core/linters.d.ts} +0 -0
- /package/dist/{linters.js → core/linters.js} +0 -0
- /package/dist/{mcp.d.ts → core/mcp.d.ts} +0 -0
- /package/dist/{mcp.js → core/mcp.js} +0 -0
- /package/dist/{orphans.d.ts → core/orphans.d.ts} +0 -0
- /package/dist/{orphans.js → core/orphans.js} +0 -0
- /package/dist/{proofs.d.ts → core/proofs.d.ts} +0 -0
- /package/dist/{proofs.js → core/proofs.js} +0 -0
- /package/dist/{session.d.ts → core/session.d.ts} +0 -0
- /package/dist/{session.js → core/session.js} +0 -0
- /package/dist/{sidecar.d.ts → core/sidecar.d.ts} +0 -0
- /package/dist/{sidecar.js → core/sidecar.js} +0 -0
- /package/dist/{spec.d.ts → core/spec.d.ts} +0 -0
- /package/dist/{spec.js → core/spec.js} +0 -0
- /package/dist/{symbols.d.ts → core/symbols.d.ts} +0 -0
- /package/dist/{symbols.js → core/symbols.js} +0 -0
- /package/dist/{test-utils.d.ts → core/test-utils.d.ts} +0 -0
- /package/dist/{test-utils.js → core/test-utils.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/{.claude-plugin/hooks → hooks}/post-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/pre-edit.sh +0 -0
- /package/{.claude-plugin/hooks → hooks}/session-start.sh +0 -0
package/dist/plugin-loader.js
CHANGED
|
@@ -3,28 +3,38 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.loadPlugin = loadPlugin;
|
|
4
4
|
exports.resolveHarness = resolveHarness;
|
|
5
5
|
/**
|
|
6
|
-
* vigiles —
|
|
6
|
+
* vigiles — harness-agnostic plugin/repo harness loader (composition root).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Lives at the app/composition root, NOT in any harness adapter, so every
|
|
9
|
+
* adapter (Claude Code, Codex, OpenCode, …) loads its plugin through the SAME
|
|
10
|
+
* loader by injecting its own `PluginLayout` — no adapter imports a sibling
|
|
11
|
+
* adapter. The unit that matters is not a single hook but the *assembled
|
|
12
|
+
* machine*: the hooks, settings, instruction file, skills, subagents, and
|
|
13
|
+
* commands a plugin/repo actually ships, working together. `loadPlugin` reads
|
|
14
|
+
* that real harness so a `runHarnessTest` / `runEval` runs against what ships —
|
|
15
|
+
* not a hand-retyped subset that can drift. Hooks, instruction file and skills
|
|
16
|
+
* are exercisable at the deterministic tier; subagents/commands/MCP are
|
|
17
|
+
* materialized but only run under a real model, so `LoadedPlugin.warnings` flags
|
|
18
|
+
* them (no silent empty machine).
|
|
15
19
|
*
|
|
16
20
|
* runHarnessTest({ plugin: "./", model: scriptModel([...]) });
|
|
17
21
|
*
|
|
18
|
-
* Resolution order for hooks: inline `hooks` in
|
|
19
|
-
*
|
|
20
|
-
* obra/superpowers), then a plain repo's
|
|
21
|
-
*
|
|
22
|
-
* so the real hook scripts run from where they live (no copying
|
|
23
|
-
* plugin's
|
|
24
|
-
* assembled context is present too.
|
|
22
|
+
* Resolution order for hooks: inline `hooks` in the layout's manifest, a `hooks`
|
|
23
|
+
* string path in the manifest, the layout's `hooks` convention path (e.g.
|
|
24
|
+
* obra/superpowers' `hooks/hooks.json`), then a plain repo's settings file.
|
|
25
|
+
* The layout's plugin-root token in any hook command is expanded to the plugin's
|
|
26
|
+
* absolute path, so the real hook scripts run from where they live (no copying
|
|
27
|
+
* needed). The plugin's instruction file and skills/ are materialized into the
|
|
28
|
+
* sandbox so the assembled context is present too.
|
|
29
|
+
*
|
|
30
|
+
* `layout` is REQUIRED here — there is no harness default at the composition
|
|
31
|
+
* root. Each adapter supplies its own (the Claude Code wrapper at
|
|
32
|
+
* `src/adapters/claude-code/plugin-loader.ts` defaults it to `claudeCodeLayout`
|
|
33
|
+
* to preserve `loadPlugin(dir)` ergonomics and the public `vigiles/*` exports).
|
|
25
34
|
*/
|
|
26
35
|
const node_fs_1 = require("node:fs");
|
|
27
36
|
const node_path_1 = require("node:path");
|
|
37
|
+
const toml_1 = require("@iarna/toml");
|
|
28
38
|
const MAX_SKILL_FILE_BYTES = 256 * 1024;
|
|
29
39
|
/** Parse a JSON file, or null on any error (missing / malformed). */
|
|
30
40
|
function safeReadJson(path) {
|
|
@@ -39,6 +49,40 @@ function safeReadJson(path) {
|
|
|
39
49
|
function readHooksFile(path) {
|
|
40
50
|
return safeReadJson(path)?.hooks;
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Parse the layout's manifest in its declared `settingsFormat` — JSON (Claude
|
|
54
|
+
* Code's plugin.json) or TOML (Codex's `config.toml`). A TOML harness's manifest
|
|
55
|
+
* (hooks, `[mcp_servers]`) would otherwise read as empty through the JSON path.
|
|
56
|
+
* Behaviour-identical to `safeReadJson` when the format is JSON.
|
|
57
|
+
*/
|
|
58
|
+
function safeReadManifest(root, layout) {
|
|
59
|
+
const path = (0, node_path_1.join)(root, layout.manifestPath);
|
|
60
|
+
if (layout.settingsFormat === "toml") {
|
|
61
|
+
try {
|
|
62
|
+
return (0, toml_1.parse)((0, node_fs_1.readFileSync)(path, "utf-8"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return safeReadJson(path);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the `.hooks` field of a settings file in the layout's format — JSON
|
|
72
|
+
* (Claude Code's settings.json) or TOML (Codex's `config.toml` `[hooks]`). A
|
|
73
|
+
* TOML harness's hooks would otherwise be read as zero by the JSON path.
|
|
74
|
+
*/
|
|
75
|
+
function readSettingsHooks(path, format) {
|
|
76
|
+
if (format === "json")
|
|
77
|
+
return readHooksFile(path);
|
|
78
|
+
try {
|
|
79
|
+
return (0, toml_1.parse)((0, node_fs_1.readFileSync)(path, "utf-8"))
|
|
80
|
+
.hooks;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
42
86
|
/**
|
|
43
87
|
* Read the hooks block, handling the real-world plugin layouts:
|
|
44
88
|
* 1. inline `hooks` object in .claude-plugin/plugin.json,
|
|
@@ -46,22 +90,23 @@ function readHooksFile(path) {
|
|
|
46
90
|
* 3. the `hooks/hooks.json` convention (e.g. obra/superpowers) — auto-discovered,
|
|
47
91
|
* 4. a plain repo's `.claude/settings.json`.
|
|
48
92
|
*/
|
|
49
|
-
function readHooks(root) {
|
|
50
|
-
// A malformed
|
|
51
|
-
// other layouts (
|
|
52
|
-
|
|
93
|
+
function readHooks(root, layout) {
|
|
94
|
+
// A malformed manifest must not crash the loader — fall through to the
|
|
95
|
+
// other layouts (safeReadManifest returns null on a parse error). Format-aware
|
|
96
|
+
// so a Codex `config.toml` manifest's `[hooks]` is read, not skipped.
|
|
97
|
+
const m = safeReadManifest(root, layout);
|
|
53
98
|
if (m) {
|
|
54
99
|
if (typeof m.hooks === "string")
|
|
55
100
|
return readHooksFile((0, node_path_1.join)(root, m.hooks));
|
|
56
101
|
if (m.hooks !== undefined)
|
|
57
102
|
return m.hooks;
|
|
58
103
|
}
|
|
59
|
-
const conventionPath = (0, node_path_1.join)(root,
|
|
104
|
+
const conventionPath = (0, node_path_1.join)(root, layout.hooksConventionPath);
|
|
60
105
|
if ((0, node_fs_1.existsSync)(conventionPath))
|
|
61
106
|
return readHooksFile(conventionPath);
|
|
62
|
-
const settingsPath = (0, node_path_1.join)(root,
|
|
107
|
+
const settingsPath = (0, node_path_1.join)(root, layout.settingsPath);
|
|
63
108
|
if ((0, node_fs_1.existsSync)(settingsPath))
|
|
64
|
-
return
|
|
109
|
+
return readSettingsHooks(settingsPath, layout.settingsFormat);
|
|
65
110
|
return undefined;
|
|
66
111
|
}
|
|
67
112
|
/** Recursively collect text files under `dir` as `relativePath → contents`. */
|
|
@@ -85,39 +130,39 @@ function readTree(dir, base) {
|
|
|
85
130
|
* sandbox, and `warnings` for surfaces the deterministic tier can't drive. Merge
|
|
86
131
|
* `settings` with any inline settings and spread `files` into the fixture.
|
|
87
132
|
*/
|
|
88
|
-
function loadPlugin(pluginPath) {
|
|
133
|
+
function loadPlugin(pluginPath, layout) {
|
|
89
134
|
const root = (0, node_path_1.resolve)(pluginPath);
|
|
90
|
-
const hooks = readHooks(root);
|
|
91
|
-
// Expand
|
|
135
|
+
const hooks = readHooks(root, layout);
|
|
136
|
+
// Expand the plugin-root token to the real absolute path so the actual hook
|
|
92
137
|
// scripts execute — we test the shipped wiring, not a reimplementation.
|
|
93
138
|
const resolvedHooks = hooks
|
|
94
|
-
? JSON.parse(JSON.stringify(hooks).replaceAll(
|
|
139
|
+
? JSON.parse(JSON.stringify(hooks).replaceAll(layout.pluginRootToken, root))
|
|
95
140
|
: undefined;
|
|
96
141
|
const files = {};
|
|
97
|
-
const
|
|
98
|
-
if ((0, node_fs_1.existsSync)(
|
|
99
|
-
files[
|
|
142
|
+
const instructions = (0, node_path_1.join)(root, layout.instructionFile);
|
|
143
|
+
if ((0, node_fs_1.existsSync)(instructions)) {
|
|
144
|
+
files[layout.instructionFile] = (0, node_fs_1.readFileSync)(instructions, "utf-8");
|
|
100
145
|
}
|
|
101
|
-
// Materialize each project-level surface under
|
|
146
|
+
// Materialize each project-level surface under the materialize root so the
|
|
102
147
|
// assembled context is present in the sandbox (best-effort — headless
|
|
103
148
|
// activation of plugin skills/subagents/commands is not guaranteed; the body
|
|
104
149
|
// is present for the agent to read either way). Counting what we materialize
|
|
105
150
|
// also lets us warn about surfaces the deterministic tier can't drive.
|
|
106
151
|
const counts = {};
|
|
107
|
-
for (const surface of
|
|
152
|
+
for (const surface of layout.surfaceDirs) {
|
|
108
153
|
const dir = (0, node_path_1.join)(root, surface);
|
|
109
154
|
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory())
|
|
110
155
|
continue;
|
|
111
156
|
const tree = readTree(dir, root);
|
|
112
157
|
for (const [rel, content] of Object.entries(tree)) {
|
|
113
|
-
files[(0, node_path_1.join)(
|
|
158
|
+
files[(0, node_path_1.join)(layout.materializeRoot, rel)] = content;
|
|
114
159
|
}
|
|
115
160
|
counts[surface] = Object.keys(tree).length;
|
|
116
161
|
}
|
|
117
162
|
return {
|
|
118
163
|
settings: resolvedHooks ? { hooks: resolvedHooks } : {},
|
|
119
164
|
files,
|
|
120
|
-
warnings: pluginWarnings(root, counts, resolvedHooks, files),
|
|
165
|
+
warnings: pluginWarnings(root, counts, resolvedHooks, files, layout),
|
|
121
166
|
};
|
|
122
167
|
}
|
|
123
168
|
/**
|
|
@@ -127,7 +172,7 @@ function loadPlugin(pluginPath) {
|
|
|
127
172
|
* the eval tier. MCP servers aren't wired by the loader at all. And a plugin
|
|
128
173
|
* that yields neither hooks nor files would otherwise be a silent empty machine.
|
|
129
174
|
*/
|
|
130
|
-
function pluginWarnings(root, counts, hooks, files) {
|
|
175
|
+
function pluginWarnings(root, counts, hooks, files, layout) {
|
|
131
176
|
const warnings = [];
|
|
132
177
|
if (counts.agents) {
|
|
133
178
|
warnings.push(`plugin defines ${String(counts.agents)} subagent file(s) under agents/ — these run only under a real model; test them at the eval tier (runEval), not the deterministic mock.`);
|
|
@@ -135,10 +180,10 @@ function pluginWarnings(root, counts, hooks, files) {
|
|
|
135
180
|
if (counts.commands) {
|
|
136
181
|
warnings.push(`plugin defines ${String(counts.commands)} slash-command file(s) under commands/ — slash-command invocation needs a real model; test at the eval tier.`);
|
|
137
182
|
}
|
|
138
|
-
if (hasMcp(root)) {
|
|
139
|
-
warnings.push(`plugin declares MCP server(s) (
|
|
183
|
+
if (hasMcp(root, layout)) {
|
|
184
|
+
warnings.push(`plugin declares MCP server(s) (${layout.mcpManifestKey} / ${layout.mcpConfigFile}) — the loader does not wire MCP; bring the server up yourself if your test needs it.`);
|
|
140
185
|
}
|
|
141
|
-
const dangling = danglingRefs(root);
|
|
186
|
+
const dangling = danglingRefs(root, layout);
|
|
142
187
|
if (dangling.length) {
|
|
143
188
|
const shown = dangling.slice(0, 5).join(", ");
|
|
144
189
|
const more = dangling.length > 5 ? `, … (+${String(dangling.length - 5)})` : "";
|
|
@@ -149,16 +194,20 @@ function pluginWarnings(root, counts, hooks, files) {
|
|
|
149
194
|
}
|
|
150
195
|
return warnings;
|
|
151
196
|
}
|
|
152
|
-
/** Whether the plugin declares any MCP servers (manifest
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
/** Whether the plugin declares any MCP servers (manifest key or standalone file).
|
|
198
|
+
* Format-aware: reads the layout's `mcpManifestKey` from a JSON OR TOML manifest,
|
|
199
|
+
* so Codex's `[mcp_servers]` TOML table is detected, not silently missed. */
|
|
200
|
+
function hasMcp(root, layout) {
|
|
201
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, layout.mcpConfigFile)))
|
|
155
202
|
return true;
|
|
156
|
-
return (
|
|
157
|
-
undefined);
|
|
203
|
+
return safeReadManifest(root, layout)?.[layout.mcpManifestKey] !== undefined;
|
|
158
204
|
}
|
|
159
205
|
// A plugin-relative path reference to a file under a standard surface dir, with a
|
|
160
206
|
// known extension — e.g. a hook script that `cat`s `skills/using-superpowers/SKILL.md`.
|
|
161
|
-
const
|
|
207
|
+
const INTRA_REF_EXTS = "md|sh|cmd|mjs|cjs|js|ts|py|rb|txt|json";
|
|
208
|
+
function intraRefRe(layout) {
|
|
209
|
+
return new RegExp(`(?:${layout.intraRefDirs.join("|")})/[A-Za-z0-9._/-]+\\.(?:${INTRA_REF_EXTS})`, "g");
|
|
210
|
+
}
|
|
162
211
|
/**
|
|
163
212
|
* Intra-plugin file references that don't resolve — the partial-vendor / broken-
|
|
164
213
|
* path class (e.g. obra/superpowers' `SessionStart` reads
|
|
@@ -168,15 +217,16 @@ const INTRA_REF_RE = /(?:skills|hooks|commands|agents)\/[A-Za-z0-9._/-]+\.(?:md|
|
|
|
168
217
|
* and report the ones missing on disk. A static check that would have caught a
|
|
169
218
|
* bug the dogfood hit twice. Best-effort: a warning, not an error.
|
|
170
219
|
*/
|
|
171
|
-
function danglingRefs(root) {
|
|
220
|
+
function danglingRefs(root, layout) {
|
|
172
221
|
const missing = new Set();
|
|
173
222
|
const seen = new Set();
|
|
174
|
-
|
|
223
|
+
const re = intraRefRe(layout);
|
|
224
|
+
for (const surface of layout.intraRefDirs) {
|
|
175
225
|
const dir = (0, node_path_1.join)(root, surface);
|
|
176
226
|
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory())
|
|
177
227
|
continue;
|
|
178
228
|
for (const content of Object.values(readTree(dir, root))) {
|
|
179
|
-
for (const m of content.matchAll(
|
|
229
|
+
for (const m of content.matchAll(re)) {
|
|
180
230
|
const ref = m[0];
|
|
181
231
|
if (seen.has(ref))
|
|
182
232
|
continue;
|
|
@@ -215,11 +265,13 @@ function mergeSettings(base, override) {
|
|
|
215
265
|
/**
|
|
216
266
|
* Resolve the effective harness for a test/eval (arm): load the plugin if given,
|
|
217
267
|
* then layer inline settings + files on top. Shared by `runHarnessTest` and
|
|
218
|
-
* `runEval` so both test the assembled machine the same way.
|
|
268
|
+
* `runEval` so both test the assembled machine the same way. `layout` is
|
|
269
|
+
* REQUIRED (no harness default at the composition root) — the Claude Code
|
|
270
|
+
* wrapper supplies `claudeCodeLayout` to preserve `resolveHarness(opts)`.
|
|
219
271
|
*/
|
|
220
|
-
function resolveHarness(opts) {
|
|
272
|
+
function resolveHarness(opts, layout) {
|
|
221
273
|
const loaded = opts.plugin
|
|
222
|
-
? loadPlugin(opts.plugin)
|
|
274
|
+
? loadPlugin(opts.plugin, layout)
|
|
223
275
|
: { settings: {}, files: {} };
|
|
224
276
|
return {
|
|
225
277
|
files: { ...loaded.files, ...opts.files },
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vigiles scan <dir>` — point vigiles at any plugin/repo and see what it ships
|
|
3
|
+
* and what's broken, with **no model and no API key**.
|
|
4
|
+
*
|
|
5
|
+
* This is the deterministic substrate under the plugin/skill leaderboard
|
|
6
|
+
* (research/divergent-bets.md #9) and the harness-aware audit
|
|
7
|
+
* (research/agent-supply-chain-security.md #1): it re-aims the machinery that
|
|
8
|
+
* already exists — `loadPlugin` (surfaces + dangling-ref/MCP/empty-machine
|
|
9
|
+
* warnings), `parseAgentTools` (the declared tool contract), and
|
|
10
|
+
* `findUntestedSurfaces` — into one read-only report. Behavioral checks that
|
|
11
|
+
* need to RUN the plugin (observed egress under the sandbox, real trigger-rate)
|
|
12
|
+
* stack on top later; this core stays pure so it runs anywhere in CI for free.
|
|
13
|
+
*/
|
|
14
|
+
import type { PluginLayout } from "./core/layout.js";
|
|
15
|
+
export interface ScanSkill {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly hasDescription: boolean;
|
|
19
|
+
readonly userInvoked: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ScanAgent {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
/** Declared tool contract, or null when the agent ships no `tools:` (inherits all). */
|
|
25
|
+
readonly tools: readonly string[] | null;
|
|
26
|
+
}
|
|
27
|
+
/** ok = file present; missing = referenced but absent; unresolved = path still has an unexpanded var, can't check. */
|
|
28
|
+
export type HookStatus = "ok" | "missing" | "unresolved";
|
|
29
|
+
export interface ScanHook {
|
|
30
|
+
readonly script: string;
|
|
31
|
+
readonly status: HookStatus;
|
|
32
|
+
}
|
|
33
|
+
export interface ScanReport {
|
|
34
|
+
readonly dir: string;
|
|
35
|
+
readonly skills: readonly ScanSkill[];
|
|
36
|
+
readonly agents: readonly ScanAgent[];
|
|
37
|
+
readonly hooks: readonly ScanHook[];
|
|
38
|
+
/** Hook entries with no script file (inline shell one-liners) — can't be path-checked. */
|
|
39
|
+
readonly inlineHooks: number;
|
|
40
|
+
readonly commands: number;
|
|
41
|
+
readonly mcp: boolean;
|
|
42
|
+
readonly warnings: readonly string[];
|
|
43
|
+
readonly untested: number;
|
|
44
|
+
}
|
|
45
|
+
/** Scan a plugin/repo directory and report its surfaces + structural issues. */
|
|
46
|
+
export declare function scanPlugin(dir: string, layout?: PluginLayout): ScanReport;
|
|
47
|
+
/** Format a scan report as human-readable text. */
|
|
48
|
+
export declare function formatScanReport(r: ScanReport): string;
|
|
49
|
+
//# sourceMappingURL=scan.d.ts.map
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `vigiles scan <dir>` — point vigiles at any plugin/repo and see what it ships
|
|
4
|
+
* and what's broken, with **no model and no API key**.
|
|
5
|
+
*
|
|
6
|
+
* This is the deterministic substrate under the plugin/skill leaderboard
|
|
7
|
+
* (research/divergent-bets.md #9) and the harness-aware audit
|
|
8
|
+
* (research/agent-supply-chain-security.md #1): it re-aims the machinery that
|
|
9
|
+
* already exists — `loadPlugin` (surfaces + dangling-ref/MCP/empty-machine
|
|
10
|
+
* warnings), `parseAgentTools` (the declared tool contract), and
|
|
11
|
+
* `findUntestedSurfaces` — into one read-only report. Behavioral checks that
|
|
12
|
+
* need to RUN the plugin (observed egress under the sandbox, real trigger-rate)
|
|
13
|
+
* stack on top later; this core stays pure so it runs anywhere in CI for free.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.scanPlugin = scanPlugin;
|
|
17
|
+
exports.formatScanReport = formatScanReport;
|
|
18
|
+
const node_fs_1 = require("node:fs");
|
|
19
|
+
const node_path_1 = require("node:path");
|
|
20
|
+
const plugin_loader_js_1 = require("./adapters/claude-code/plugin-loader.js");
|
|
21
|
+
const agent_runtime_js_1 = require("./adapters/claude-code/agent-runtime.js");
|
|
22
|
+
const test_coverage_js_1 = require("./test-coverage.js");
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Internals
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
const SCRIPT_RE = /\S+\.(?:sh|mjs|cjs|js|ts|py|rb)\b/g;
|
|
27
|
+
function frontmatter(md) {
|
|
28
|
+
const m = /(?:^|\n)---\r?\n([\s\S]*?)\r?\n---/.exec(md);
|
|
29
|
+
if (!m)
|
|
30
|
+
return {};
|
|
31
|
+
const name = /^name:\s*(.+)$/m.exec(m[1])?.[1]?.trim();
|
|
32
|
+
const description = /^description:\s*(.+)$/m.exec(m[1])?.[1]?.trim();
|
|
33
|
+
return { name, description };
|
|
34
|
+
}
|
|
35
|
+
const isSkill = (f) => /skills\/[^/]+\/SKILL\.md$/.test(f);
|
|
36
|
+
const isAgent = (f) => /agents\/[^/]+\.md$/.test(f) && !f.endsWith(".spec.ts");
|
|
37
|
+
const isCommand = (f) => /commands\/.+\.md$/.test(f);
|
|
38
|
+
function skillName(path) {
|
|
39
|
+
return (path
|
|
40
|
+
.replace(/\/SKILL\.md$/, "")
|
|
41
|
+
.split("/")
|
|
42
|
+
.pop() ?? path);
|
|
43
|
+
}
|
|
44
|
+
function scanSkills(files) {
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const [path, md] of Object.entries(files)) {
|
|
47
|
+
if (!isSkill(path))
|
|
48
|
+
continue;
|
|
49
|
+
const fm = frontmatter(md);
|
|
50
|
+
out.push({
|
|
51
|
+
name: fm.name ?? skillName(path),
|
|
52
|
+
path,
|
|
53
|
+
hasDescription: Boolean(fm.description && fm.description.length >= 20),
|
|
54
|
+
userInvoked: /^\s*disable-model-invocation:\s*true\s*$/m.test(md),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
58
|
+
}
|
|
59
|
+
function scanAgents(files) {
|
|
60
|
+
const out = [];
|
|
61
|
+
for (const [path, md] of Object.entries(files)) {
|
|
62
|
+
if (!isAgent(path))
|
|
63
|
+
continue;
|
|
64
|
+
out.push({ name: (0, node_path_1.basename)(path, ".md"), path, tools: (0, agent_runtime_js_1.parseAgentTools)(md) });
|
|
65
|
+
}
|
|
66
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a hook script token to a checkable path. `loadPlugin` expands the
|
|
70
|
+
* braced `${CLAUDE_PLUGIN_ROOT}`; the unbraced shell form `$CLAUDE_PLUGIN_ROOT`
|
|
71
|
+
* survives, so resolve it against the plugin root here and strip shell quotes.
|
|
72
|
+
* A token that still carries any `$VAR` after that is genuinely uncheckable.
|
|
73
|
+
*/
|
|
74
|
+
function resolveScript(token, root) {
|
|
75
|
+
const path = token
|
|
76
|
+
.replace(/["']/g, "")
|
|
77
|
+
.replaceAll("${CLAUDE_PLUGIN_ROOT}", root)
|
|
78
|
+
.replaceAll("$CLAUDE_PLUGIN_ROOT", root);
|
|
79
|
+
if (path.includes("$"))
|
|
80
|
+
return { script: token, status: "unresolved" };
|
|
81
|
+
return { script: path, status: (0, node_fs_1.existsSync)(path) ? "ok" : "missing" };
|
|
82
|
+
}
|
|
83
|
+
/** Pull script-file hook commands out of the resolved settings; count inline ones. */
|
|
84
|
+
function scanHooks(settings, root) {
|
|
85
|
+
const text = JSON.stringify(settings.hooks ?? {});
|
|
86
|
+
const commands = [...text.matchAll(/"command":\s*"((?:[^"\\]|\\.)*)"/g)].map((m) => m[1]);
|
|
87
|
+
const byScript = new Map();
|
|
88
|
+
let inline = 0;
|
|
89
|
+
for (const cmd of commands) {
|
|
90
|
+
const unescaped = cmd.replace(/\\(.)/g, "$1");
|
|
91
|
+
const found = unescaped.match(SCRIPT_RE);
|
|
92
|
+
if (!found || found.length === 0) {
|
|
93
|
+
inline++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
for (const tok of found) {
|
|
97
|
+
const hook = resolveScript(tok, root);
|
|
98
|
+
byScript.set(hook.script, hook);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const hooks = [...byScript.values()].sort((a, b) => a.script.localeCompare(b.script));
|
|
102
|
+
return { hooks, inline };
|
|
103
|
+
}
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Public API
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
/** Scan a plugin/repo directory and report its surfaces + structural issues. */
|
|
108
|
+
function scanPlugin(dir, layout) {
|
|
109
|
+
const loaded = (0, plugin_loader_js_1.loadPlugin)(dir, layout);
|
|
110
|
+
const { hooks, inline } = scanHooks(loaded.settings, (0, node_path_1.resolve)(dir));
|
|
111
|
+
return {
|
|
112
|
+
dir,
|
|
113
|
+
skills: scanSkills(loaded.files),
|
|
114
|
+
agents: scanAgents(loaded.files),
|
|
115
|
+
hooks,
|
|
116
|
+
inlineHooks: inline,
|
|
117
|
+
commands: Object.keys(loaded.files).filter(isCommand).length,
|
|
118
|
+
mcp: loaded.warnings.some((w) => w.includes("MCP server")),
|
|
119
|
+
warnings: loaded.warnings,
|
|
120
|
+
untested: (0, test_coverage_js_1.findUntestedSurfaces)({ basePath: dir }).untested.length,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function section(title, lines) {
|
|
124
|
+
if (lines.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
return [`${title} (${String(lines.length)}):`, ...lines, ""];
|
|
127
|
+
}
|
|
128
|
+
/** Format a scan report as human-readable text. */
|
|
129
|
+
function formatScanReport(r) {
|
|
130
|
+
const out = [`Scan: ${r.dir}`, ""];
|
|
131
|
+
out.push(...section("Skills", r.skills.map((s) => {
|
|
132
|
+
const mark = s.hasDescription ? "✓" : "⚠";
|
|
133
|
+
const note = s.hasDescription
|
|
134
|
+
? s.userInvoked
|
|
135
|
+
? "(user-invoked)"
|
|
136
|
+
: ""
|
|
137
|
+
: "(missing/short description — can't trigger)";
|
|
138
|
+
return ` ${mark} ${s.name} ${note}`.trimEnd();
|
|
139
|
+
})));
|
|
140
|
+
out.push(...section("Agents", r.agents.map((a) => {
|
|
141
|
+
const tools = a.tools === null
|
|
142
|
+
? "tools: (inherits all — no contract)"
|
|
143
|
+
: `tools: ${a.tools.join(", ") || "(none)"}`;
|
|
144
|
+
return ` ${a.tools === null ? "⚠" : "✓"} ${a.name} — ${tools}`;
|
|
145
|
+
})));
|
|
146
|
+
const hookMark = {
|
|
147
|
+
ok: "✓",
|
|
148
|
+
missing: "✗",
|
|
149
|
+
unresolved: "?",
|
|
150
|
+
};
|
|
151
|
+
const hookNote = {
|
|
152
|
+
ok: "",
|
|
153
|
+
missing: " (referenced but MISSING)",
|
|
154
|
+
unresolved: " (unresolved var — can't check)",
|
|
155
|
+
};
|
|
156
|
+
const hookLines = r.hooks.map((h) => ` ${hookMark[h.status]} ${h.script}${hookNote[h.status]}`);
|
|
157
|
+
if (r.inlineHooks > 0) {
|
|
158
|
+
hookLines.push(` · ${String(r.inlineHooks)} inline hook(s) (no script file)`);
|
|
159
|
+
}
|
|
160
|
+
out.push(...section("Hooks", hookLines));
|
|
161
|
+
const facts = [];
|
|
162
|
+
if (r.commands > 0)
|
|
163
|
+
facts.push(`Commands: ${String(r.commands)}`);
|
|
164
|
+
facts.push(`MCP servers: ${r.mcp ? "yes" : "no"}`);
|
|
165
|
+
facts.push(`Untested surfaces: ${String(r.untested)}`);
|
|
166
|
+
out.push(...facts, "");
|
|
167
|
+
if (r.warnings.length > 0) {
|
|
168
|
+
out.push("Warnings:", ...r.warnings.map((w) => ` - ${w}`), "");
|
|
169
|
+
}
|
|
170
|
+
const broken = r.hooks.filter((h) => h.status === "missing").length +
|
|
171
|
+
r.skills.filter((s) => !s.hasDescription).length;
|
|
172
|
+
out.push(broken === 0
|
|
173
|
+
? "✓ no structural issues found"
|
|
174
|
+
: `⚠ ${String(broken)} structural issue(s) — see ✗/⚠ above`);
|
|
175
|
+
return out.join("\n");
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision logic for `vigiles init`.
|
|
3
|
+
*
|
|
4
|
+
* Turns CLI args + whether a human's at a TTY into a concrete setup PLAN, so the
|
|
5
|
+
* IO-heavy `setup()` in cli.ts stays a thin shell and every choice is unit-tested.
|
|
6
|
+
* Best-practice onboarding: **interactive** when a human runs it in a terminal,
|
|
7
|
+
* **non-interactive** (sensible defaults) for agents / CI / piped input — so
|
|
8
|
+
* "set up vigiles" from a Claude Code or Codex prompt Just Works without hanging
|
|
9
|
+
* on a prompt. See docs/agent-setup.md.
|
|
10
|
+
*/
|
|
11
|
+
/** What `vigiles init` will set up. */
|
|
12
|
+
export interface SetupPlan {
|
|
13
|
+
/** Pillar 1 — verify instruction files (specs, types, compile, audit, hooks). */
|
|
14
|
+
verify: boolean;
|
|
15
|
+
/** Pillar 2 — test the harness (scaffold a starter harness test + CI job). */
|
|
16
|
+
test: boolean;
|
|
17
|
+
/** Wire CI (the `zernie/vigiles@v1` Action; creates a workflow if none). */
|
|
18
|
+
gha: boolean;
|
|
19
|
+
/** Install the Claude Code plugin (hooks + skills). */
|
|
20
|
+
plugin: boolean;
|
|
21
|
+
/** Strict rule severities in `.vigilesrc.json`. */
|
|
22
|
+
strict: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The explicit choices a user pinned via flags (undefined = "not specified"). */
|
|
25
|
+
export interface ParsedSetupArgs {
|
|
26
|
+
target?: string;
|
|
27
|
+
strict: boolean;
|
|
28
|
+
yes: boolean;
|
|
29
|
+
pillars?: "verify" | "test" | "both";
|
|
30
|
+
gha?: boolean;
|
|
31
|
+
plugin?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Parse `init` args into the choices the user pinned. */
|
|
34
|
+
export declare function parseSetupArgs(args: readonly string[]): ParsedSetupArgs;
|
|
35
|
+
/** The non-interactive defaults: both pillars, CI, and the plugin. */
|
|
36
|
+
export declare function defaultPlan(strict?: boolean): SetupPlan;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
39
|
+
* `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
|
|
40
|
+
* via flags. Agents / CI / piped input (no TTY) never prompt.
|
|
41
|
+
*/
|
|
42
|
+
export declare function shouldPrompt(parsed: ParsedSetupArgs, isTTY: boolean): boolean;
|
|
43
|
+
/** Interactive answers (only the fields the prompts cover). */
|
|
44
|
+
export type SetupAnswers = Partial<Pick<SetupPlan, "verify" | "test" | "gha" | "plugin">>;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the final plan: defaults, then flags, then interactive answers (each
|
|
47
|
+
* layer overrides the previous only where it has an opinion). `--target` pins a
|
|
48
|
+
* bare Pillar-1 spec (no harness scaffold).
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolvePlan(parsed: ParsedSetupArgs, answers?: SetupAnswers): SetupPlan;
|
|
51
|
+
//# sourceMappingURL=setup-plan.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure decision logic for `vigiles init`.
|
|
4
|
+
*
|
|
5
|
+
* Turns CLI args + whether a human's at a TTY into a concrete setup PLAN, so the
|
|
6
|
+
* IO-heavy `setup()` in cli.ts stays a thin shell and every choice is unit-tested.
|
|
7
|
+
* Best-practice onboarding: **interactive** when a human runs it in a terminal,
|
|
8
|
+
* **non-interactive** (sensible defaults) for agents / CI / piped input — so
|
|
9
|
+
* "set up vigiles" from a Claude Code or Codex prompt Just Works without hanging
|
|
10
|
+
* on a prompt. See docs/agent-setup.md.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.parseSetupArgs = parseSetupArgs;
|
|
14
|
+
exports.defaultPlan = defaultPlan;
|
|
15
|
+
exports.shouldPrompt = shouldPrompt;
|
|
16
|
+
exports.resolvePlan = resolvePlan;
|
|
17
|
+
function flagValue(args, prefix) {
|
|
18
|
+
return args.find((a) => a.startsWith(prefix))?.slice(prefix.length);
|
|
19
|
+
}
|
|
20
|
+
/** Parse `init` args into the choices the user pinned. */
|
|
21
|
+
function parseSetupArgs(args) {
|
|
22
|
+
const pillarsRaw = flagValue(args, "--pillars=");
|
|
23
|
+
const pillars = pillarsRaw === "verify" || pillarsRaw === "test" || pillarsRaw === "both"
|
|
24
|
+
? pillarsRaw
|
|
25
|
+
: undefined;
|
|
26
|
+
return {
|
|
27
|
+
target: flagValue(args, "--target="),
|
|
28
|
+
strict: args.includes("--strict"),
|
|
29
|
+
yes: args.includes("--yes") || args.includes("-y"),
|
|
30
|
+
pillars,
|
|
31
|
+
gha: args.includes("--no-gha") ? false : undefined,
|
|
32
|
+
plugin: args.includes("--no-plugin") ? false : undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The non-interactive defaults: both pillars, CI, and the plugin. */
|
|
36
|
+
function defaultPlan(strict = false) {
|
|
37
|
+
return { verify: true, test: true, gha: true, plugin: true, strict };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
41
|
+
* `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
|
|
42
|
+
* via flags. Agents / CI / piped input (no TTY) never prompt.
|
|
43
|
+
*/
|
|
44
|
+
function shouldPrompt(parsed, isTTY) {
|
|
45
|
+
if (!isTTY || parsed.yes || parsed.target)
|
|
46
|
+
return false;
|
|
47
|
+
const allPinned = parsed.pillars !== undefined &&
|
|
48
|
+
parsed.gha !== undefined &&
|
|
49
|
+
parsed.plugin !== undefined;
|
|
50
|
+
return !allPinned;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the final plan: defaults, then flags, then interactive answers (each
|
|
54
|
+
* layer overrides the previous only where it has an opinion). `--target` pins a
|
|
55
|
+
* bare Pillar-1 spec (no harness scaffold).
|
|
56
|
+
*/
|
|
57
|
+
function resolvePlan(parsed, answers) {
|
|
58
|
+
const plan = defaultPlan(parsed.strict);
|
|
59
|
+
if (parsed.pillars === "verify") {
|
|
60
|
+
plan.verify = true;
|
|
61
|
+
plan.test = false;
|
|
62
|
+
}
|
|
63
|
+
else if (parsed.pillars === "test") {
|
|
64
|
+
plan.verify = false;
|
|
65
|
+
plan.test = true;
|
|
66
|
+
}
|
|
67
|
+
if (parsed.gha === false)
|
|
68
|
+
plan.gha = false;
|
|
69
|
+
if (parsed.plugin === false)
|
|
70
|
+
plan.plugin = false;
|
|
71
|
+
if (parsed.target)
|
|
72
|
+
plan.test = false;
|
|
73
|
+
if (answers) {
|
|
74
|
+
if (answers.verify !== undefined)
|
|
75
|
+
plan.verify = answers.verify;
|
|
76
|
+
if (answers.test !== undefined)
|
|
77
|
+
plan.test = answers.test;
|
|
78
|
+
if (answers.gha !== undefined)
|
|
79
|
+
plan.gha = answers.gha;
|
|
80
|
+
if (answers.plugin !== undefined)
|
|
81
|
+
plan.plugin = answers.plugin;
|
|
82
|
+
}
|
|
83
|
+
return plan;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=setup-plan.js.map
|
package/dist/skill-test.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* gate blocked. The model's prose quality is never asserted — that's the
|
|
9
9
|
* probabilistic boundary; everything else is deterministic and checkable.
|
|
10
10
|
*/
|
|
11
|
-
import { type SkillProgram, type GeneratorSkill } from "./skill-driver.js";
|
|
11
|
+
import { type SkillProgram, type GeneratorSkill } from "./adapters/claude-code/skill-driver.js";
|
|
12
12
|
export type ModelFn = (prose: string) => string;
|
|
13
13
|
/**
|
|
14
14
|
* Script the mocked model. Pass:
|
package/dist/skill-test.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.scriptModel = scriptModel;
|
|
14
14
|
exports.runSkill = runSkill;
|
|
15
|
-
const skill_driver_js_1 = require("./skill-driver.js");
|
|
15
|
+
const skill_driver_js_1 = require("./adapters/claude-code/skill-driver.js");
|
|
16
16
|
function gateLabel(g) {
|
|
17
17
|
if (g._ref === "cmd")
|
|
18
18
|
return g.command;
|