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.
Files changed (50) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/claude-code/adapter.js +1 -0
  3. package/dist/adapters/claude-code/agent-runtime.d.ts +20 -6
  4. package/dist/adapters/claude-code/agent-runtime.js +51 -8
  5. package/dist/adapters/claude-code/dialect.js +19 -0
  6. package/dist/adapters/claude-code/effect-region.d.ts +9 -0
  7. package/dist/adapters/claude-code/effect-region.js +45 -0
  8. package/dist/adapters/claude-code/layout.js +3 -0
  9. package/dist/adapters/claude-code/skill-runtime.d.ts +25 -0
  10. package/dist/adapters/claude-code/skill-runtime.js +48 -0
  11. package/dist/adapters/codex/adapter.js +3 -0
  12. package/dist/adapters/codex/layout.js +3 -0
  13. package/dist/adapters/opencode/adapter.js +1 -0
  14. package/dist/adapters/opencode/layout.js +3 -0
  15. package/dist/check.d.ts +8 -0
  16. package/dist/check.js +27 -3
  17. package/dist/cli.js +323 -88
  18. package/dist/core/adapter.d.ts +10 -0
  19. package/dist/core/bash-effects.d.ts +41 -0
  20. package/dist/core/bash-effects.js +405 -0
  21. package/dist/core/compile.d.ts +3 -1
  22. package/dist/core/compile.js +162 -39
  23. package/dist/core/dialect.d.ts +10 -0
  24. package/dist/core/effects.d.ts +172 -0
  25. package/dist/core/effects.js +245 -0
  26. package/dist/core/layout.d.ts +6 -0
  27. package/dist/core/mcp-tool.d.ts +1 -1
  28. package/dist/core/orphans.js +21 -0
  29. package/dist/core/spec.d.ts +142 -3
  30. package/dist/core/spec.js +48 -0
  31. package/dist/core/tool-contract.d.ts +1 -1
  32. package/dist/core/types.d.ts +6 -6
  33. package/dist/core/validate.js +4 -4
  34. package/dist/harness-test.d.ts +7 -0
  35. package/dist/harness-test.js +19 -7
  36. package/dist/leaderboard.d.ts +2 -0
  37. package/dist/leaderboard.js +2 -0
  38. package/dist/optimize.d.ts +74 -0
  39. package/dist/optimize.js +94 -0
  40. package/dist/scaffold-test.d.ts +30 -0
  41. package/dist/scaffold-test.js +158 -0
  42. package/dist/scan.d.ts +40 -0
  43. package/dist/scan.js +91 -43
  44. package/dist/score-explainer.d.ts +69 -0
  45. package/dist/score-explainer.js +169 -0
  46. package/dist/test-coverage.d.ts +7 -0
  47. package/dist/test-coverage.js +39 -24
  48. package/package.json +2 -1
  49. package/skills/{migrate-to-spec → adopt-spec}/SKILL.md +4 -4
  50. package/skills/edit-spec/SKILL.md +1 -1
@@ -29,6 +29,20 @@ exports.formatUntestedReport = formatUntestedReport;
29
29
  const node_fs_1 = require("node:fs");
30
30
  const node_path_1 = require("node:path");
31
31
  const glob_1 = require("glob");
32
+ const layout_js_1 = require("./adapters/claude-code/layout.js");
33
+ /**
34
+ * The two on-disk locations a surface dir can occupy: the plugin-root form
35
+ * (`skills/…`) and the materialized form (`.claude/skills/…`) — derived from the
36
+ * layout so a non-Claude-Code harness (its own `materializeRoot` / surface dir)
37
+ * is discovered without hard-coding `.claude`. An empty `dir` (a harness lacking
38
+ * the surface, e.g. Codex subagents) yields no globs.
39
+ */
40
+ function surfaceGlobs(dir, leaf, materializeRoot) {
41
+ if (!dir)
42
+ return [];
43
+ const matForm = materializeRoot ? `${materializeRoot}/${dir}` : dir;
44
+ return [...new Set([`${dir}/${leaf}`, `${matForm}/${leaf}`])];
45
+ }
32
46
  // ---------------------------------------------------------------------------
33
47
  // Internals
34
48
  // ---------------------------------------------------------------------------
@@ -58,12 +72,9 @@ function read(path) {
58
72
  return "";
59
73
  }
60
74
  }
61
- function discoverSkills(basePath, ignore) {
75
+ function discoverSkills(basePath, ignore, layout) {
62
76
  const out = [];
63
- const found = (0, glob_1.globSync)(["skills/*/SKILL.md", ".claude/skills/*/SKILL.md"], {
64
- cwd: basePath,
65
- ignore,
66
- });
77
+ const found = (0, glob_1.globSync)(surfaceGlobs(layout.skillDir, "*/SKILL.md", layout.materializeRoot), { cwd: basePath, ignore });
67
78
  for (const path of found.sort()) {
68
79
  const name = (0, node_path_1.basename)((0, node_path_1.dirname)(path));
69
80
  const content = read((0, node_path_1.join)(basePath, path));
@@ -71,18 +82,15 @@ function discoverSkills(basePath, ignore) {
71
82
  kind: "skill",
72
83
  path,
73
84
  name,
74
- tokens: [`skills/${name}`, `:${name}`],
85
+ tokens: [`${layout.skillDir}/${name}`, `:${name}`],
75
86
  ignored: content.includes(IGNORE_MARKER),
76
87
  });
77
88
  }
78
89
  return out;
79
90
  }
80
- function discoverAgents(basePath, ignore) {
91
+ function discoverAgents(basePath, ignore, layout) {
81
92
  const out = [];
82
- const found = (0, glob_1.globSync)(["agents/*.md", ".claude/agents/*.md"], {
83
- cwd: basePath,
84
- ignore,
85
- });
93
+ const found = (0, glob_1.globSync)(surfaceGlobs(layout.agentDir, "*.md", layout.materializeRoot), { cwd: basePath, ignore });
86
94
  for (const path of found.sort()) {
87
95
  if (path.endsWith(".spec.ts"))
88
96
  continue;
@@ -100,7 +108,7 @@ function discoverAgents(basePath, ignore) {
100
108
  return out;
101
109
  }
102
110
  /** Hook-script paths referenced from a manifest's `hooks` block (file hooks only). */
103
- function hookScripts(basePath, manifest) {
111
+ function hookScripts(basePath, manifest, pluginRootToken) {
104
112
  if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(basePath, manifest)))
105
113
  return [];
106
114
  let hooks;
@@ -114,25 +122,31 @@ function hookScripts(basePath, manifest) {
114
122
  if (hooks === undefined)
115
123
  return [];
116
124
  const text = JSON.stringify(hooks);
125
+ // "${CLAUDE_PLUGIN_ROOT}" → unbraced "$CLAUDE_PLUGIN_ROOT" — strip the harness's
126
+ // own token (both forms) so the path is checkable relative to the plugin root.
127
+ const unbraced = pluginRootToken.replace(/^\$\{(.+)\}$/, "$$$1");
117
128
  const scripts = new Set();
118
129
  for (const m of text.matchAll(SCRIPT_RE)) {
119
130
  const rel = m[0]
120
- .replace("${CLAUDE_PLUGIN_ROOT}/", "")
121
- .replace(/^\$\{CLAUDE_PLUGIN_ROOT\}/, "")
131
+ .replaceAll(pluginRootToken, "")
132
+ .replaceAll(unbraced, "")
133
+ .replace(/^\/+/, "")
122
134
  .replace(/^\.\//, "");
123
135
  if ((0, node_fs_1.existsSync)((0, node_path_1.join)(basePath, rel)))
124
136
  scripts.add(rel);
125
137
  }
126
138
  return [...scripts];
127
139
  }
128
- function discoverHooks(basePath) {
140
+ function discoverHooks(basePath, layout) {
129
141
  const scripts = new Set();
130
- for (const m of [
131
- ".claude-plugin/plugin.json",
132
- ".claude/settings.json",
133
- ".claude/settings.local.json",
134
- ]) {
135
- for (const s of hookScripts(basePath, m))
142
+ // The harness's manifest + settings (and a `.local` settings sibling, a CC
143
+ // convention that's harmless to probe elsewhere).
144
+ const localSettings = layout.settingsPath.replace(/(\.[^./]+)$/, ".local$1");
145
+ const manifests = [
146
+ ...new Set([layout.manifestPath, layout.settingsPath, localSettings]),
147
+ ];
148
+ for (const m of manifests) {
149
+ for (const s of hookScripts(basePath, m, layout.pluginRootToken))
136
150
  scripts.add(s);
137
151
  }
138
152
  return [...scripts].sort().map((path) => ({
@@ -185,15 +199,16 @@ function isCovered(surface, tests) {
185
199
  */
186
200
  function findUntestedSurfaces(options = {}) {
187
201
  const basePath = options.basePath ?? process.cwd();
202
+ const layout = options.layout ?? layout_js_1.claudeCodeLayout;
188
203
  const ignore = [...DEFAULT_IGNORE, ...(options.exclude ?? [])];
189
204
  const globs = options.testGlobs ?? DEFAULT_TEST_GLOBS;
190
205
  const surfaces = [];
191
206
  if (options.skills !== false)
192
- surfaces.push(...discoverSkills(basePath, ignore));
207
+ surfaces.push(...discoverSkills(basePath, ignore, layout));
193
208
  if (options.agents !== false)
194
- surfaces.push(...discoverAgents(basePath, ignore));
209
+ surfaces.push(...discoverAgents(basePath, ignore, layout));
195
210
  if (options.hooks !== false)
196
- surfaces.push(...discoverHooks(basePath));
211
+ surfaces.push(...discoverHooks(basePath, layout));
197
212
  // Every skill/agent/hook is held to the requirement — only an explicit
198
213
  // `vigiles:ignore-test` marker exempts a surface (a visible, deliberate skip).
199
214
  const considered = surfaces.filter((s) => !s.ignored);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigiles",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Lint & test the harness your AI agent runs on — verify the references in your CLAUDE.md / AGENTS.md and test that your hooks and skills actually work.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -128,6 +128,7 @@
128
128
  "glob": "^13.0.6",
129
129
  "js-yaml": "^4.1.0",
130
130
  "minimatch": "^10.0.1",
131
+ "mvdan-sh": "^0.10.1",
131
132
  "ts-essentials": "^10.2.1",
132
133
  "typescript": "^5.9.3"
133
134
  }
@@ -1,13 +1,13 @@
1
1
  ---
2
- name: migrate-to-spec
3
- description: Convert an existing hand-written CLAUDE.md into a typed .spec.ts file for incremental adoption
2
+ name: adopt-spec
3
+ description: Adopt a typed .spec.ts for an existing hand-written CLAUDE.md start from the file you already have, non-destructively
4
4
  disable-model-invocation: true
5
5
  argument-hint: <path to CLAUDE.md, defaults to CLAUDE.md>
6
6
  ---
7
7
 
8
- Convert an existing hand-written CLAUDE.md (or AGENTS.md) into a typed `CLAUDE.md.spec.ts` file. This is the incremental adoption path — you keep your existing instruction file as the starting point and get type safety going forward.
8
+ Start a typed `CLAUDE.md.spec.ts` from an existing hand-written CLAUDE.md (or AGENTS.md). This is the non-destructive adoption path — you keep your existing instruction file as the starting point and get type safety going forward.
9
9
 
10
- > **Don't need full TypeScript?** A typed spec is the deepest commitment level. If the user only wants verified rules without a build step, point them at markdown mode first: inline `<!-- vigiles:enforce ... -->` comments (Level 0) or a `vigiles:` YAML frontmatter block with `vigiles generate-schema` for editor autocomplete (Level 1). Both are verified by `vigiles lint` with the same engine as a spec. See `docs/markdown-mode.md`. Migrate to a spec only when they want compiler-grade guarantees.
10
+ > **Don't need full TypeScript?** A typed spec is the deepest commitment level. If the user only wants verified rules without a build step, point them at markdown mode first: inline `<!-- vigiles:enforce ... -->` comments (Level 0) or a `vigiles:` YAML frontmatter block with `vigiles generate-schema` for editor autocomplete (Level 1). Both are verified by `vigiles lint` with the same engine as a spec. See `docs/markdown-mode.md`. Adopt a spec only when they want compiler-grade guarantees.
11
11
 
12
12
  ## Instructions
13
13
 
@@ -27,7 +27,7 @@ Look for spec files in the repo root:
27
27
  - Any `*.spec.ts` matching instruction files
28
28
 
29
29
  If no spec exists: if there's a hand-written `CLAUDE.md`, suggest the
30
- `migrate-to-spec` skill; otherwise suggest `npx vigiles init` to scaffold one.
30
+ `adopt-spec` skill; otherwise suggest `npx vigiles init` to scaffold one.
31
31
 
32
32
  ### Step 2: Read and Understand the Spec
33
33