vigiles 33.2.0 → 34.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 +3 -2
- package/action.yml +45 -8
- package/dist/adapter.d.ts +1 -1
- package/dist/adapters/claude-code/runtime.js +70 -0
- package/dist/adapters/claude-code/skill-reachability.d.ts +42 -21
- package/dist/adapters/claude-code/skill-reachability.js +91 -28
- package/dist/cli-flag-check.js +1 -0
- package/dist/cli-main.js +401 -21
- package/dist/core/adapter.d.ts +7 -0
- package/dist/core/config-schema.d.ts +53 -34
- package/dist/core/config-schema.js +42 -0
- package/dist/core/install-reader.js +15 -0
- package/dist/core/layout.d.ts +10 -0
- package/dist/core/layout.js +17 -0
- package/dist/core/presets.d.ts +44 -0
- package/dist/core/presets.js +44 -0
- package/dist/core/run-env.d.ts +50 -0
- package/dist/core/run-env.js +51 -0
- package/dist/core/runtime.d.ts +44 -0
- package/dist/core/skill-link-target.d.ts +20 -0
- package/dist/core/skill-link-target.js +33 -0
- package/dist/core/validate.js +1 -1
- package/dist/eval.d.ts +39 -39
- package/dist/eval.js +62 -115
- package/dist/harness-test.d.ts +35 -0
- package/dist/harness-test.js +49 -7
- package/dist/output-style-arms.js +7 -2
- package/dist/plugin-declaration.d.ts +6 -6
- package/dist/plugin-declaration.js +6 -6
- package/dist/setup-plan.d.ts +142 -34
- package/dist/setup-plan.js +235 -135
- package/dist/skill-links.d.ts +183 -0
- package/dist/skill-links.js +255 -0
- package/dist/test-coverage.js +37 -1
- package/dist/vigilesrc.schema.json +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,8 +235,9 @@ The **hooks** keep it honest in-loop — nudging the agent to tag a linter-rule
|
|
|
235
235
|
|
|
236
236
|
- **Both lint and test** by default; scope with `--lint` / `--test`.
|
|
237
237
|
- **Already have a CLAUDE.md / AGENTS.md, skills, or subagents? `audit` and `lint` read them as-is** — nothing is moved or rewritten. For the structural rules that want a typed spec, `init` sets one up **non-destructively** (`eject` undoes it).
|
|
238
|
-
- Adds `vigiles` to `devDependencies
|
|
239
|
-
-
|
|
238
|
+
- Adds `vigiles` to `devDependencies` and **links its skills into your repo**: one relative symlink per skill, `.claude/skills/<name>` → `node_modules/vigiles/skills/<name>` (`.agents/skills/` for Codex). Commit them — every clone and every fresh container then has the skills as soon as `npm install` has run. A skill of yours with the same name is never replaced; `init` names the one you didn't get.
|
|
239
|
+
- Installs the Claude Code plugin via the marketplace — globally, never vendored — because it carries the **hooks**. That install is per machine; its copy of the skills is listed as `vigiles:<name>` next to the linked `<name>` (same files).
|
|
240
|
+
- Declares that plugin in your repo's `.claude/settings.json` (`extraKnownMarketplaces` + `enabledPlugins`, merged into whatever is already there). This is a **reference, not content**, and it does **not** install the plugin for a teammate: an external-source plugin declared project-level [doesn't load until each person installs it](https://code.claude.com/docs/en/discover-plugins#configure-team-marketplaces). What it buys is that Claude Code **prompts** them with the install command for the hooks.
|
|
240
241
|
- Wires CI as a `zernie/vigiles@v1` workflow (needs only read + PR-comment permissions) that posts a sticky PR comment + a `valid` output.
|
|
241
242
|
|
|
242
243
|
Targets Claude Code and Codex out of the box, or [your own harness](docs/authoring-an-adapter.md). Prefer to write tests yourself? JS **or** TS (`*.harness.{mjs,ts}`) — run with `npx vigiles test`.
|
package/action.yml
CHANGED
|
@@ -19,9 +19,15 @@ inputs:
|
|
|
19
19
|
required: false
|
|
20
20
|
default: ""
|
|
21
21
|
version:
|
|
22
|
-
description:
|
|
22
|
+
description: >
|
|
23
|
+
Version of the vigiles npm package to run (e.g. '34', '34.1.2', 'latest').
|
|
24
|
+
Empty (the default) runs the vigiles THIS repo declares in package.json:
|
|
25
|
+
the installed copy when node_modules has it, else the version in
|
|
26
|
+
package-lock.json, else the declared range — and 'latest' only when the
|
|
27
|
+
repo does not declare vigiles. Use 'local' to run the action's own
|
|
28
|
+
checkout — for dogfooding via `uses: ./` in this repo's CI.
|
|
23
29
|
required: false
|
|
24
|
-
default: "
|
|
30
|
+
default: ""
|
|
25
31
|
max-rules:
|
|
26
32
|
description: "Maximum number of rules per spec file (maps to the CLI's --max-rules)."
|
|
27
33
|
required: false
|
|
@@ -109,17 +115,48 @@ runs:
|
|
|
109
115
|
fi
|
|
110
116
|
|
|
111
117
|
# Resolve the runner. 'local' runs the action's own build (CI
|
|
112
|
-
# dogfood via `uses: ./`);
|
|
113
|
-
# package at that version
|
|
114
|
-
#
|
|
115
|
-
|
|
118
|
+
# dogfood via `uses: ./`); an explicit version runs the published npm
|
|
119
|
+
# package at that version. EMPTY (the default) runs the vigiles this
|
|
120
|
+
# repo declares, so CI and a local `npx vigiles` are the same version
|
|
121
|
+
# rather than two pins that drift: the installed copy, else the
|
|
122
|
+
# lockfile's version, else the declared range; `latest` only when the
|
|
123
|
+
# repo does not declare vigiles at all.
|
|
124
|
+
version="${VIGILES_VERSION:-}"
|
|
116
125
|
if [[ "$version" == "local" ]]; then
|
|
117
126
|
runner=(node "${VIGILES_ACTION_PATH}/dist/cli.js")
|
|
118
|
-
|
|
127
|
+
from="the action's own checkout"
|
|
128
|
+
elif [[ -n "$version" ]]; then
|
|
119
129
|
runner=(npx --yes "vigiles@${version}")
|
|
130
|
+
from="the version input"
|
|
131
|
+
else
|
|
132
|
+
declared="$(node -e '
|
|
133
|
+
const fs = require("fs");
|
|
134
|
+
const read = (f) => { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } };
|
|
135
|
+
const pkg = read("package.json") ?? {};
|
|
136
|
+
const spec = pkg.devDependencies?.vigiles ?? pkg.dependencies?.vigiles;
|
|
137
|
+
if (typeof spec !== "string") { console.log("none"); process.exit(0); }
|
|
138
|
+
if (fs.existsSync("node_modules/.bin/vigiles")) { console.log("installed"); process.exit(0); }
|
|
139
|
+
const lock = read("package-lock.json") ?? {};
|
|
140
|
+
const locked = lock.packages?.["node_modules/vigiles"]?.version ?? lock.dependencies?.vigiles?.version;
|
|
141
|
+
console.log(typeof locked === "string" ? "lock " + locked : "range " + spec);
|
|
142
|
+
' 2>/dev/null || echo none)"
|
|
143
|
+
case "$declared" in
|
|
144
|
+
installed)
|
|
145
|
+
runner=(npx --no-install vigiles)
|
|
146
|
+
from="this repo's installed vigiles" ;;
|
|
147
|
+
"lock "*)
|
|
148
|
+
runner=(npx --yes "vigiles@${declared#lock }")
|
|
149
|
+
from="package-lock.json" ;;
|
|
150
|
+
"range "*)
|
|
151
|
+
runner=(npx --yes "vigiles@${declared#range }")
|
|
152
|
+
from="the range package.json declares" ;;
|
|
153
|
+
*)
|
|
154
|
+
runner=(npx --yes "vigiles@latest")
|
|
155
|
+
from="latest (this repo does not declare vigiles)" ;;
|
|
156
|
+
esac
|
|
120
157
|
fi
|
|
121
158
|
|
|
122
|
-
echo "vigiles: ${runner[*]} ${args[*]}"
|
|
159
|
+
echo "vigiles: ${runner[*]} ${args[*]} (version from ${from})"
|
|
123
160
|
|
|
124
161
|
# Run, streaming to the log AND capturing for the summary/comment.
|
|
125
162
|
out="$(mktemp)"
|
package/dist/adapter.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export { EMPTY_CHAIN } from "./core/instruction-chain.js";
|
|
|
41
41
|
*/
|
|
42
42
|
export type { SettingsCodec } from "./core/settings-codec.js";
|
|
43
43
|
export { jsonSettingsCodec, tomlSettingsCodec } from "./core/settings-codec.js";
|
|
44
|
-
export type { HarnessRuntime } from "./core/runtime.js";
|
|
44
|
+
export type { HarnessRuntime, RunEnvPolicy } from "./core/runtime.js";
|
|
45
45
|
export type { HookProtocol } from "./core/hook-protocol.js";
|
|
46
46
|
export type { ModelMock } from "./core/model-mock.js";
|
|
47
47
|
/**
|
|
@@ -2,6 +2,75 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.claudeCodeRuntime = void 0;
|
|
4
4
|
exports.mockModelEnv = mockModelEnv;
|
|
5
|
+
/**
|
|
6
|
+
* What a child `claude` run may inherit from its caller. The runners apply it
|
|
7
|
+
* through `src/core/run-env.ts`; this object is the only place the names live.
|
|
8
|
+
*
|
|
9
|
+
* `keep` — auth and backend selection only, as an allowlist. It replaced a
|
|
10
|
+
* `CLAUDE_*` prefix pass-through, which carried the parent session's identity
|
|
11
|
+
* into every scrubbed run. Measured 2026-10-07 on 2.1.292: a child started with
|
|
12
|
+
* the parent's session variables ran as the parent session, and a host-brokered
|
|
13
|
+
* child (`ANTHROPIC_BASE_URL`) authenticated with NO `CLAUDE_*` variable at all.
|
|
14
|
+
* Every `CLAUDE_*` name below is one the prefix used to carry and that selects
|
|
15
|
+
* or supplies credentials (each is referenced by the 2.1.292 binary).
|
|
16
|
+
* `CLAUDE_CONFIG_DIR` is left out on purpose: it points the child at the real
|
|
17
|
+
* config directory (settings, hooks, sessions, tasks) that the throwaway HOME
|
|
18
|
+
* exists to hide.
|
|
19
|
+
*
|
|
20
|
+
* `sessionIdentity` — the parent session. The first six were observed in a live
|
|
21
|
+
* session's env on 2026-10-07; the rest are session-identifying names present
|
|
22
|
+
* in the 2.1.292 binary, removed by name rather than by a measured effect.
|
|
23
|
+
*/
|
|
24
|
+
const claudeCodeRunEnv = {
|
|
25
|
+
keep: [
|
|
26
|
+
// Anthropic API auth + endpoint.
|
|
27
|
+
"ANTHROPIC_API_KEY",
|
|
28
|
+
"ANTHROPIC_BASE_URL",
|
|
29
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
30
|
+
"ANTHROPIC_MODEL",
|
|
31
|
+
// Carried over from the earlier list; not referenced by the 2.1.292 binary.
|
|
32
|
+
"ANTHROPIC_API_URL",
|
|
33
|
+
"ANTHROPIC_DEFAULT_HEADERS",
|
|
34
|
+
// Subscription token (`claude setup-token`) and client certificates.
|
|
35
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
36
|
+
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS",
|
|
37
|
+
"CLAUDE_CODE_CLIENT_CERT",
|
|
38
|
+
"CLAUDE_CODE_CLIENT_KEY",
|
|
39
|
+
"CLAUDE_CODE_CLIENT_KEY_PASSPHRASE",
|
|
40
|
+
// Cloud backends: the switch, the auth skip, and region/profile — never
|
|
41
|
+
// the secret-shaped AWS_* access keys.
|
|
42
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
43
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
44
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
45
|
+
"CLAUDE_CODE_SKIP_BEDROCK_AUTH",
|
|
46
|
+
"CLAUDE_CODE_SKIP_VERTEX_AUTH",
|
|
47
|
+
"CLAUDE_CODE_SKIP_FOUNDRY_AUTH",
|
|
48
|
+
"AWS_REGION",
|
|
49
|
+
"AWS_DEFAULT_REGION",
|
|
50
|
+
"AWS_PROFILE",
|
|
51
|
+
"CLOUD_ML_REGION",
|
|
52
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
53
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
54
|
+
],
|
|
55
|
+
keepHomeFiles: [".claude/.credentials.json"],
|
|
56
|
+
sessionIdentity: [
|
|
57
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
58
|
+
"CLAUDE_CODE_REMOTE_SESSION_ID",
|
|
59
|
+
"CLAUDE_CODE_CHILD_SESSION",
|
|
60
|
+
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
61
|
+
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
62
|
+
"CLAUDE_SESSION_INGRESS_TOKEN_FILE",
|
|
63
|
+
"CLAUDE_SESSION_ID",
|
|
64
|
+
"CLAUDE_CODE_SESSION_ACCESS_TOKEN",
|
|
65
|
+
"CLAUDE_CODE_REMOTE_SESSION_UUID",
|
|
66
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
67
|
+
"CLAUDE_CODE_CLOUD_SESSION_ID",
|
|
68
|
+
"CLAUDE_CODE_BRIDGE_SESSION_ID",
|
|
69
|
+
"CLAUDE_RUNNER_SESSION_ID",
|
|
70
|
+
"CLAUDE_RUNNER_SESSION_UUID",
|
|
71
|
+
"CLAUDE_PID",
|
|
72
|
+
],
|
|
73
|
+
};
|
|
5
74
|
exports.claudeCodeRuntime = {
|
|
6
75
|
name: "claude-code",
|
|
7
76
|
agentBinary: "claude",
|
|
@@ -34,6 +103,7 @@ exports.claudeCodeRuntime = {
|
|
|
34
103
|
const m = /(\d+)\.(\d+)\.\d+/.exec(raw);
|
|
35
104
|
return m ? `${m[1]}.${m[2]}` : raw.trim();
|
|
36
105
|
},
|
|
106
|
+
runEnv: claudeCodeRunEnv,
|
|
37
107
|
};
|
|
38
108
|
/**
|
|
39
109
|
* Build the spawn env that points the agent CLI at the mock model: the caller's
|
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
* vigiles publishes six user-facing skills (`SHIPPED_SKILLS`) — the teaching
|
|
5
5
|
* surface. `test-harness` alone answers "which testing tier do I want?", the
|
|
6
6
|
* question this project's docs are otherwise organized around. They reach the
|
|
7
|
-
* agent
|
|
8
|
-
*
|
|
9
|
-
* vigiles
|
|
10
|
-
*
|
|
7
|
+
* agent two ways (`docs/agent-setup.md`):
|
|
8
|
+
*
|
|
9
|
+
* - the LINKS `vigiles init` commits into `.claude/skills/` (one relative
|
|
10
|
+
* symlink per skill into `node_modules/vigiles/skills/`, `src/skill-links.ts`)
|
|
11
|
+
* — a fact about the REPOSITORY, so every clone and container has them once
|
|
12
|
+
* `npm install` has run;
|
|
13
|
+
* - the GLOBAL plugin install (`claude plugin install vigiles@vigiles`, into
|
|
14
|
+
* `~/.claude/plugins/`) — a fact about ONE machine, namespaced
|
|
15
|
+
* `vigiles:<name>`.
|
|
11
16
|
*
|
|
12
17
|
* The failure this module makes loud: `npm install vigiles` ALSO puts those
|
|
13
18
|
* skills on disk, at `node_modules/vigiles/skills/` (they are in package.json's
|
|
@@ -46,9 +51,10 @@
|
|
|
46
51
|
* Shape follows `./dialect-drift.ts`: pure parsers + a best-effort local read
|
|
47
52
|
* that NEVER throws + a formatter that returns null when there is nothing to
|
|
48
53
|
* say, so `vigiles audit` can print it without a new verb, flag, or failure mode.
|
|
49
|
-
* It is ADVISORY — it never touches the audit score, because reachability is
|
|
50
|
-
* property of the machine (is the plugin installed?
|
|
51
|
-
* score that moved between a laptop and CI for identical source
|
|
54
|
+
* It is ADVISORY — it never touches the audit score, because reachability is
|
|
55
|
+
* partly a property of the machine (is the plugin installed? has `npm install`
|
|
56
|
+
* run?), and a score that moved between a laptop and CI for identical source
|
|
57
|
+
* would be a lie.
|
|
52
58
|
*/
|
|
53
59
|
import type { InstallReader } from "../../core/adapter.js";
|
|
54
60
|
/** The plugin id `claude plugin install` records — `<plugin>@<marketplace>`. */
|
|
@@ -61,7 +67,7 @@ export declare const VIGILES_PLUGIN_ID = "vigiles@vigiles";
|
|
|
61
67
|
export type ReachabilitySource =
|
|
62
68
|
/** `~/.claude/plugins/installed_plugins.json` — what `claude plugin install` writes. */
|
|
63
69
|
"global-plugin"
|
|
64
|
-
/** The
|
|
70
|
+
/** The repo's own `.claude/skills/<name>/SKILL.md` resolves — a link `init` made, or a copy. */
|
|
65
71
|
| "repo-skills";
|
|
66
72
|
/** The advisory: can the agent see vigiles's skills from this repo? */
|
|
67
73
|
export interface SkillReachability {
|
|
@@ -82,6 +88,18 @@ export interface SkillReachability {
|
|
|
82
88
|
* nothing stranded if they are wired) or when the package isn't installed yet.
|
|
83
89
|
*/
|
|
84
90
|
readonly strandedSkills: readonly string[];
|
|
91
|
+
/**
|
|
92
|
+
* Shipped skills whose `.claude/skills/<name>` entry is a LINK that does not
|
|
93
|
+
* resolve: the links came with `git clone`, the package they point into did
|
|
94
|
+
* not. The fix is `npm install`, not re-linking.
|
|
95
|
+
*/
|
|
96
|
+
readonly danglingLinks: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Skills the package ships that the repo has not linked while it HAS linked
|
|
99
|
+
* others — the state after an upgrade adds a skill. Empty unless at least one
|
|
100
|
+
* shipped skill resolves in the repo.
|
|
101
|
+
*/
|
|
102
|
+
readonly unlinkedSkills: readonly string[];
|
|
85
103
|
}
|
|
86
104
|
/**
|
|
87
105
|
* Does the global registry record a live install of the vigiles plugin? Pure over
|
|
@@ -101,25 +119,28 @@ export declare function hasEnabledPlugin(settingsJson: string): boolean;
|
|
|
101
119
|
* reader performs degrades to "not found".
|
|
102
120
|
*
|
|
103
121
|
* 🔴 IT READS THROUGH A REPO-BOUND {@link InstallReader}, NOT `node:fs`. Two of
|
|
104
|
-
* its
|
|
122
|
+
* its reads are of files NO adapter claims (`package.json`, vigiles's own
|
|
105
123
|
* package under `node_modules`), so the domain performs those and passes the
|
|
106
124
|
* ANSWER; the rest go through a reader that refuses any path this adapter does
|
|
107
|
-
* not claim. That is why this module
|
|
125
|
+
* not claim. That is why this module does not import `node:fs` at all.
|
|
108
126
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* a shipped skill but holding no `SKILL.md` used to count as reachable and no
|
|
115
|
-
* longer does. It was never loadable by the agent, which is the question asked.
|
|
127
|
+
* Each shipped skill is probed for its own `SKILL.md` rather than by listing
|
|
128
|
+
* `.claude/skills/` — enumeration is what the reader exists to prevent, and the
|
|
129
|
+
* names are known: the installed package's when it is installed, else the
|
|
130
|
+
* shipped list. A directory named after a shipped skill but holding no
|
|
131
|
+
* `SKILL.md` is not reachable; it was never loadable by the agent.
|
|
116
132
|
*/
|
|
117
133
|
export declare function checkSkillReachability(read: InstallReader): SkillReachability | null;
|
|
118
134
|
/**
|
|
119
|
-
* The
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
135
|
+
* The advisory, or null when there is nothing to say. Most specific state
|
|
136
|
+
* first, each ending on the command that fixes it:
|
|
137
|
+
*
|
|
138
|
+
* 1. links that dangle — `npm install`;
|
|
139
|
+
* 2. nothing reaches the agent — link them (per repo), or install the plugin
|
|
140
|
+
* (per machine);
|
|
141
|
+
* 3. only this machine's plugin reaches it — a fresh clone or container will
|
|
142
|
+
* not have them. Not a failure; said in one line;
|
|
143
|
+
* 4. some linked, some not (an upgrade added one) — link the rest.
|
|
123
144
|
*/
|
|
124
145
|
export declare function formatSkillReachability(r: SkillReachability | null): string | null;
|
|
125
146
|
//# sourceMappingURL=skill-reachability.d.ts.map
|
|
@@ -6,6 +6,9 @@ exports.hasEnabledPlugin = hasEnabledPlugin;
|
|
|
6
6
|
exports.checkSkillReachability = checkSkillReachability;
|
|
7
7
|
exports.formatSkillReachability = formatSkillReachability;
|
|
8
8
|
const setup_plan_js_1 = require("../../setup-plan.js");
|
|
9
|
+
const layout_js_1 = require("../../core/layout.js");
|
|
10
|
+
const skill_link_target_js_1 = require("../../core/skill-link-target.js");
|
|
11
|
+
const layout_js_2 = require("./layout.js");
|
|
9
12
|
/** The plugin id `claude plugin install` records — `<plugin>@<marketplace>`. */
|
|
10
13
|
exports.VIGILES_PLUGIN_ID = "vigiles@vigiles";
|
|
11
14
|
/**
|
|
@@ -36,6 +39,10 @@ function hasEnabledPlugin(settingsJson) {
|
|
|
36
39
|
return false;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
42
|
+
/** Where this harness keeps a repo's skills — the layout's answer, not a literal. */
|
|
43
|
+
const SKILLS_HOME = (0, layout_js_1.skillsHome)(layout_js_2.claudeCodeLayout) ?? ".claude/skills";
|
|
44
|
+
/** The per-repo fix, printed wherever the repo itself lacks the links. */
|
|
45
|
+
const LINK_FIX = "npx vigiles init — links each skill into .claude/skills/ (relative links into node_modules; commit them), and leaves any skill you already have alone";
|
|
39
46
|
/**
|
|
40
47
|
* Best-effort, read-local reachability check for `vigiles audit`. Returns null
|
|
41
48
|
* when the question does not apply — the repo does not depend on vigiles, or IS
|
|
@@ -43,54 +50,110 @@ function hasEnabledPlugin(settingsJson) {
|
|
|
43
50
|
* reader performs degrades to "not found".
|
|
44
51
|
*
|
|
45
52
|
* 🔴 IT READS THROUGH A REPO-BOUND {@link InstallReader}, NOT `node:fs`. Two of
|
|
46
|
-
* its
|
|
53
|
+
* its reads are of files NO adapter claims (`package.json`, vigiles's own
|
|
47
54
|
* package under `node_modules`), so the domain performs those and passes the
|
|
48
55
|
* ANSWER; the rest go through a reader that refuses any path this adapter does
|
|
49
|
-
* not claim. That is why this module
|
|
56
|
+
* not claim. That is why this module does not import `node:fs` at all.
|
|
50
57
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* a shipped skill but holding no `SKILL.md` used to count as reachable and no
|
|
57
|
-
* longer does. It was never loadable by the agent, which is the question asked.
|
|
58
|
+
* Each shipped skill is probed for its own `SKILL.md` rather than by listing
|
|
59
|
+
* `.claude/skills/` — enumeration is what the reader exists to prevent, and the
|
|
60
|
+
* names are known: the installed package's when it is installed, else the
|
|
61
|
+
* shipped list. A directory named after a shipped skill but holding no
|
|
62
|
+
* `SKILL.md` is not reachable; it was never loadable by the agent.
|
|
58
63
|
*/
|
|
59
64
|
function checkSkillReachability(read) {
|
|
60
65
|
if (!read.repoDependsOnVigiles)
|
|
61
66
|
return null;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// unrelated skills in `.claude/skills/` is still un-wired.
|
|
68
|
-
if (setup_plan_js_1.SHIPPED_SKILLS.some((s) => read.repo(`.claude/skills/${s}/SKILL.md`)))
|
|
69
|
-
sources.push("repo-skills");
|
|
67
|
+
const shipped = read.vendoredSkillNames.length > 0
|
|
68
|
+
? read.vendoredSkillNames
|
|
69
|
+
: setup_plan_js_1.SHIPPED_SKILLS;
|
|
70
|
+
const { inRepo, dangling } = repoSkillState(read, shipped);
|
|
71
|
+
const sources = reachabilitySources(read, inRepo);
|
|
70
72
|
const reachable = sources.length > 0;
|
|
71
73
|
// A project declaration is NOT a source — it makes Claude Code prompt for an
|
|
72
74
|
// install, it does not perform one. Only meaningful while unreachable.
|
|
73
75
|
const settings = read.repo(".claude/settings.json");
|
|
74
|
-
const declaredNotInstalled = !reachable && settings !== null && hasEnabledPlugin(settings);
|
|
75
76
|
const vendored = new Set(read.vendoredSkillNames);
|
|
76
77
|
return {
|
|
77
78
|
reachable,
|
|
78
79
|
sources,
|
|
79
|
-
declaredNotInstalled,
|
|
80
|
+
declaredNotInstalled: !reachable && settings !== null && hasEnabledPlugin(settings),
|
|
80
81
|
strandedSkills: reachable
|
|
81
82
|
? []
|
|
82
83
|
: setup_plan_js_1.SHIPPED_SKILLS.filter((s) => vendored.has(s)),
|
|
84
|
+
danglingLinks: dangling,
|
|
85
|
+
unlinkedSkills: inRepo.length > 0
|
|
86
|
+
? shipped.filter((s) => !inRepo.includes(s) && !dangling.includes(s))
|
|
87
|
+
: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Which shipped skills resolve in the repo, and which are links that dangle. */
|
|
91
|
+
function repoSkillState(read, shipped) {
|
|
92
|
+
const resolves = (s) => read.repo(`${SKILLS_HOME}/${s}/SKILL.md`) !== null;
|
|
93
|
+
return {
|
|
94
|
+
inRepo: shipped.filter(resolves),
|
|
95
|
+
dangling: shipped.filter((s) => !resolves(s) && isOurLink(read.repoLink(`${SKILLS_HOME}/${s}`), s)),
|
|
83
96
|
};
|
|
84
97
|
}
|
|
85
98
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
|
|
99
|
+
* A link `vigiles init` wrote (into `node_modules/vigiles/skills/<name>`). A
|
|
100
|
+
* dangling link that only shares a shipped skill's NAME is the user's own, and
|
|
101
|
+
* "run npm install" would be advice about the wrong thing.
|
|
102
|
+
*/
|
|
103
|
+
function isOurLink(target, name) {
|
|
104
|
+
return target !== null && (0, skill_link_target_js_1.isVigilesSkillTarget)(target, name);
|
|
105
|
+
}
|
|
106
|
+
/** The sources that resolved, in check order. */
|
|
107
|
+
function reachabilitySources(read, inRepo) {
|
|
108
|
+
const installed = read.home(".claude/plugins/installed_plugins.json");
|
|
109
|
+
return [
|
|
110
|
+
...(installed !== null && hasGlobalPluginInstall(installed)
|
|
111
|
+
? ["global-plugin"]
|
|
112
|
+
: []),
|
|
113
|
+
// Only vigiles's OWN skill names count. A repo with 38 unrelated skills in
|
|
114
|
+
// `.claude/skills/` is still un-wired.
|
|
115
|
+
...(inRepo.length > 0 ? ["repo-skills"] : []),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The advisory, or null when there is nothing to say. Most specific state
|
|
120
|
+
* first, each ending on the command that fixes it:
|
|
121
|
+
*
|
|
122
|
+
* 1. links that dangle — `npm install`;
|
|
123
|
+
* 2. nothing reaches the agent — link them (per repo), or install the plugin
|
|
124
|
+
* (per machine);
|
|
125
|
+
* 3. only this machine's plugin reaches it — a fresh clone or container will
|
|
126
|
+
* not have them. Not a failure; said in one line;
|
|
127
|
+
* 4. some linked, some not (an upgrade added one) — link the rest.
|
|
90
128
|
*/
|
|
91
129
|
function formatSkillReachability(r) {
|
|
92
|
-
if (!r
|
|
130
|
+
if (!r)
|
|
93
131
|
return null;
|
|
132
|
+
if (r.danglingLinks.length > 0)
|
|
133
|
+
return formatDangling(r);
|
|
134
|
+
if (!r.reachable)
|
|
135
|
+
return formatUnreachable(r);
|
|
136
|
+
if (!r.sources.includes("repo-skills"))
|
|
137
|
+
return (`ℹ vigiles's skills reach your agent only through this machine's plugin ` +
|
|
138
|
+
`install (~/.claude/plugins/), so a fresh clone or container won't have ` +
|
|
139
|
+
`them.\n Fix (per repo): ${LINK_FIX}`);
|
|
140
|
+
if (r.unlinkedSkills.length > 0)
|
|
141
|
+
return (`⚠ ${String(r.unlinkedSkills.length)} of vigiles's skills aren't linked ` +
|
|
142
|
+
`into ${SKILLS_HOME}/: ${r.unlinkedSkills.join(", ")}.\n Fix: ${LINK_FIX}`);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/** Links that came with the clone, into a package that did not. */
|
|
146
|
+
function formatDangling(r) {
|
|
147
|
+
const plugin = r.sources.includes("global-plugin")
|
|
148
|
+
? " This machine's plugin install still provides them as vigiles:<name>."
|
|
149
|
+
: "";
|
|
150
|
+
return (`⚠ ${String(r.danglingLinks.length)} of vigiles's skills are linked into ` +
|
|
151
|
+
`${SKILLS_HOME}/ but the links don't resolve here — node_modules/vigiles ` +
|
|
152
|
+
`isn't installed.${plugin}\n` +
|
|
153
|
+
` Fix: npm install (a session that started before it may need /reload-skills or a restart)`);
|
|
154
|
+
}
|
|
155
|
+
/** Nothing reaches the agent: the per-repo fix first, the per-machine one second. */
|
|
156
|
+
function formatUnreachable(r) {
|
|
94
157
|
const stranded = r.strandedSkills.length > 0
|
|
95
158
|
? ` ${String(r.strandedSkills.length)} of them are sitting in ` +
|
|
96
159
|
`node_modules/vigiles/skills/, which the agent never scans.`
|
|
@@ -100,12 +163,12 @@ function formatSkillReachability(r) {
|
|
|
100
163
|
`project declaration doesn't install it — Claude Code loads an ` +
|
|
101
164
|
`external-source plugin only once each collaborator installs it on their ` +
|
|
102
165
|
`own machine.`
|
|
103
|
-
: `This repo depends on vigiles, but
|
|
166
|
+
: `This repo depends on vigiles, but it does not link vigiles's skills and the plugin isn't installed here.`;
|
|
104
167
|
return (`⚠ vigiles's skills are NOT reachable by your agent here. ${why} So the ` +
|
|
105
168
|
`shipped skills (${setup_plan_js_1.SHIPPED_SKILLS.join(", ")}) can't be selected — ` +
|
|
106
169
|
`including test-harness, which picks the testing tier for you.${stranded}\n` +
|
|
107
|
-
` Fix (per
|
|
108
|
-
`claude plugin
|
|
109
|
-
`
|
|
170
|
+
` Fix (per repo, reaches every clone): ${LINK_FIX}\n` +
|
|
171
|
+
` Or per machine: claude plugin marketplace add zernie/vigiles && ` +
|
|
172
|
+
`claude plugin install ${exports.VIGILES_PLUGIN_ID}`);
|
|
110
173
|
}
|
|
111
174
|
//# sourceMappingURL=skill-reachability.js.map
|