vigiles 5.2.0 → 7.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 +99 -48
- package/dist/action-gate.js +1 -1
- package/dist/adapters/claude-code/agent-runtime.d.ts +64 -4
- package/dist/adapters/claude-code/agent-runtime.js +131 -17
- package/dist/adapters/claude-code/dialect.d.ts +34 -0
- package/dist/adapters/claude-code/dialect.js +46 -33
- package/dist/adapters/claude-code/effect-region.js +1 -1
- package/dist/adapters/claude-code/skill-runtime.d.ts +1 -1
- package/dist/adapters/claude-code/skill-runtime.js +1 -9
- package/dist/adapters/claude-code/typed-spec.d.ts +58 -0
- package/dist/adapters/claude-code/typed-spec.js +55 -0
- package/dist/adapters/codex/hook-protocol.js +3 -0
- package/dist/adapters/codex/mock-model.js +1 -1
- package/dist/claude-code.d.ts +1 -0
- package/dist/claude-code.js +8 -1
- package/dist/cli-commands.d.ts +19 -0
- package/dist/cli-commands.js +51 -0
- package/dist/cli.js +735 -76
- package/dist/core/bash-effects.d.ts +12 -0
- package/dist/core/bash-effects.js +31 -0
- package/dist/core/capability-diff.d.ts +46 -0
- package/dist/core/capability-diff.js +97 -0
- package/dist/core/compile.d.ts +1 -1
- package/dist/core/compile.js +14 -0
- package/dist/core/generate-harness.d.ts +187 -0
- package/dist/core/generate-harness.js +337 -0
- package/dist/core/guards.d.ts +126 -0
- package/dist/core/guards.js +309 -0
- package/dist/core/harness-driver.d.ts +1 -1
- package/dist/core/hook-program.d.ts +459 -0
- package/dist/core/hook-program.js +468 -0
- package/dist/core/hook-protocol.d.ts +7 -0
- package/dist/core/hook-providers.d.ts +138 -0
- package/dist/core/hook-providers.js +155 -0
- package/dist/core/hook-spec.d.ts +74 -0
- package/dist/core/hook-spec.js +130 -0
- package/dist/core/inline.js +1 -1
- package/dist/core/mcp-tool.d.ts +12 -0
- package/dist/core/mcp-tool.js +20 -0
- package/dist/core/mcp.d.ts +13 -0
- package/dist/core/mcp.js +67 -0
- package/dist/core/spec.d.ts +290 -8
- package/dist/core/spec.js +118 -3
- package/dist/core/types.d.ts +8 -0
- package/dist/dialect-drift.d.ts +65 -0
- package/dist/dialect-drift.js +216 -0
- package/dist/eval.d.ts +40 -5
- package/dist/eval.js +59 -5
- package/dist/guardrail-check.d.ts +85 -0
- package/dist/guardrail-check.js +152 -0
- package/dist/harness-assert.d.ts +10 -0
- package/dist/harness-assert.js +30 -0
- package/dist/hook-install.d.ts +43 -0
- package/dist/hook-install.js +91 -0
- package/dist/hook.d.ts +52 -0
- package/dist/hook.js +98 -0
- package/dist/leaderboard.d.ts +6 -0
- package/dist/leaderboard.js +43 -1
- package/dist/linting.d.ts +9 -5
- package/dist/linting.js +17 -5
- package/dist/optimize.js +1 -1
- package/dist/scaffold-test.d.ts +28 -0
- package/dist/scaffold-test.js +134 -15
- package/dist/scan-behavioral.d.ts +60 -0
- package/dist/scan-behavioral.js +239 -1
- package/dist/scan.d.ts +14 -0
- package/dist/scan.js +33 -1
- package/dist/score-explainer.js +1 -1
- package/dist/self-command-refs.d.ts +21 -0
- package/dist/self-command-refs.js +125 -0
- package/dist/testing.d.ts +5 -3
- package/dist/testing.js +37 -23
- package/dist/tool-intercept.d.ts +4 -4
- package/dist/tool-intercept.js +5 -5
- package/dist/unit.d.ts +2 -0
- package/dist/unit.js +8 -1
- package/hooks/refs-nudge.sh +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
<h1 align="center">vigiles</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Make the harness your AI agent runs on reliable.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
Your CLAUDE.md, hooks, and skills steer the agent — but nothing checks they're <em>true</em>,
|
|
12
|
+
Your CLAUDE.md, hooks, and skills steer the agent — but nothing checks they're <em>true</em>, nothing stops them entering a <em>bad state</em>, nothing tests they <em>work</em>, and nothing measures whether they actually <em>help</em>. vigiles does all four — deterministically, mostly with no API key.
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
@@ -20,14 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
`Agent = Model + Harness`.
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
`Agent = Model + Harness`. The model gets the headlines; the **harness** — the
|
|
24
|
+
instruction files, hooks, and skills you actually control — is the half that
|
|
25
|
+
fails silently. vigiles[^name] turns that harness into a compilable, analyzable,
|
|
26
|
+
testable object instead of vibes. Four instruments, adopt any:
|
|
26
27
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
| **🔎 Lint**
|
|
30
|
-
|
|
|
28
|
+
| | |
|
|
29
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| **🔎 Lint** | Every file path, script, code symbol, and linter rule your CLAUDE.md cites is checked against reality — a lint that asks _is this **true**_, not just _well-formed_ — so a renamed file or a disabled rule can't silently mislead the agent. **[→](docs/verifying-instruction-files.md)** |
|
|
31
|
+
| **🛡 Guard** | Author a hook as a pure typed function and vigiles compiles it — making **whole classes of hook bugs unrepresentable** (the guard that silently doesn't block). **[→](docs/compiled-hooks.md)** |
|
|
32
|
+
| **🧪 Test** | Hooks, skills, and subagents are code. vigiles tests they _do their job_ — and almost all of it is **deterministic, no API key**; the real-model evals run on your **Claude subscription**, not metered tokens. **[→](docs/harness-testing.md)** |
|
|
33
|
+
| **📊 Eval** | Does that skill or plugin actually help — or just add cost? **A/B it on real tasks** and read the bill + correctness, not the vendor's headline — the eval you can afford. The same engine ranks the hyped ecosystem: **what works vs hype**. **[→](docs/measuring-skills.md)** |
|
|
31
34
|
|
|
32
35
|
Pick the one that hurts today. **Works with Claude Code and Codex**
|
|
33
36
|
([`vigiles/codex`](docs/harnesses.md)), and you can
|
|
@@ -50,24 +53,16 @@ Or do it yourself:
|
|
|
50
53
|
npx vigiles init # sets up lint + test: spec + harness test + CI + plugin
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
tell your agent _"test my skills"_ and it picks the tier and writes the test.
|
|
56
|
+
Interactive in a terminal, non-interactive for agents/CI (or `--yes`). It installs
|
|
57
|
+
a model-invocable **`test-harness` skill** too, so you can then tell your agent
|
|
58
|
+
_"test my skills"_ and it writes the test.
|
|
57
59
|
|
|
58
60
|
<details>
|
|
59
61
|
<summary>What <code>init</code> sets up</summary>
|
|
60
62
|
|
|
61
|
-
- **Both lint and test** by default; scope with `--lint` / `--test
|
|
62
|
-
- Adds `vigiles` to
|
|
63
|
-
-
|
|
64
|
-
globally, never vendored into your repo.
|
|
65
|
-
- Wires CI as a `zernie/vigiles@v1` workflow (a composite over the same CLI):
|
|
66
|
-
|
|
67
|
-
```yaml
|
|
68
|
-
- uses: actions/checkout@v4
|
|
69
|
-
- uses: zernie/vigiles@v1 # lints by default; posts a sticky PR comment + a `valid` output
|
|
70
|
-
```
|
|
63
|
+
- **Both lint and test** by default; scope with `--lint` / `--test`.
|
|
64
|
+
- Adds `vigiles` to `devDependencies`; installs the Claude Code plugin (skills + hooks) via the marketplace — globally, never vendored.
|
|
65
|
+
- Wires CI as a `zernie/vigiles@v1` workflow (a composite over the same CLI) that posts a sticky PR comment + a `valid` output.
|
|
71
66
|
|
|
72
67
|
Prefer to write tests yourself? They can be JS **or** TS
|
|
73
68
|
(`*.harness.{mjs,ts}`) — run them with `npx vigiles test`.
|
|
@@ -76,11 +71,10 @@ Prefer to write tests yourself? They can be JS **or** TS
|
|
|
76
71
|
|
|
77
72
|
## ① Lint — your CLAUDE.md lies to your agent
|
|
78
73
|
|
|
79
|
-
Your CLAUDE.md points the agent at `src/auth/login.ts` and
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`npx vigiles lint` resolves every reference against reality:
|
|
74
|
+
Your CLAUDE.md points the agent at `src/auth/login.ts` and says to run `npm run
|
|
75
|
+
check`. But the file moved six commits ago and the script was renamed — the agent
|
|
76
|
+
trusts the stale claim and acts on fiction. `npx vigiles lint` resolves every
|
|
77
|
+
reference against reality:
|
|
84
78
|
|
|
85
79
|
```text
|
|
86
80
|
CLAUDE.md:
|
|
@@ -91,19 +85,54 @@ CLAUDE.md:
|
|
|
91
85
|
|
|
92
86
|
File paths, scripts, and code symbols — plus linter rules across **7 catalogs**
|
|
93
87
|
(the rule exists **and is enabled**). Start with one inline comment, no new files;
|
|
94
|
-
step up to a typed `.spec.ts` (compiled to CLAUDE.md
|
|
95
|
-
|
|
88
|
+
step up to a typed `.spec.ts` (compiled to CLAUDE.md) when you want it.
|
|
89
|
+
**[Full guide →](docs/verifying-instruction-files.md)**
|
|
90
|
+
|
|
91
|
+
> **Markdown is prose; a typed spec is a _program_.** Opt in and an agent that
|
|
92
|
+
> leaks or hands off mismatched data is a **type error** — your multi-agent
|
|
93
|
+
> pipeline won't compile if the handoffs don't line up. Others lint prose; vigiles
|
|
94
|
+
> is a **compiler for harnesses**, graduated like `strict`. **[The moat →](research/typed-spec-moat.md)**
|
|
96
95
|
|
|
97
96
|
**Same cross-reference, any plugin.** `npx vigiles scan` checks a plugin's
|
|
98
97
|
_contracts_ — every subagent tool, `mcp__server__tool`, `mcp_tool` hook, hook
|
|
99
98
|
event, and script path actually **exists and resolves**, not just parses (valid
|
|
100
|
-
YAML ≠ a tool
|
|
101
|
-
|
|
99
|
+
YAML ≠ a real tool). A **superset of `claude plugin validate`**, no key. **[Audit any plugin →](docs/cli.md#scan-dir)**
|
|
100
|
+
|
|
101
|
+
## ② Guard — author a hook that can't be wrong
|
|
102
|
+
|
|
103
|
+
A safety hook is the one place that can _stop_ the agent before something
|
|
104
|
+
irreversible — yet hand-written hooks fail silently (`exit 1` not `2`, the wrong
|
|
105
|
+
JSON field, a `grep` that misses `cd x && git push -f`): they look like a guard
|
|
106
|
+
and block nothing. Write the hook as a pure typed function instead — vigiles
|
|
107
|
+
compiles the protocol for you:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { defineHook, tool, deny, allow } from "vigiles/hook";
|
|
111
|
+
|
|
112
|
+
export default defineHook({
|
|
113
|
+
on: "PreToolUse",
|
|
114
|
+
match: tool("Bash"),
|
|
115
|
+
decide: (e) =>
|
|
116
|
+
e.command.runs("git push", { force: true })
|
|
117
|
+
? deny("no force-push to a protected branch")
|
|
118
|
+
: allow(),
|
|
119
|
+
});
|
|
120
|
+
```
|
|
102
121
|
|
|
103
|
-
|
|
122
|
+
This **eliminates an entire class of bugs**: you never write the exit code / JSON
|
|
123
|
+
field (false confidence), the matcher is **AST-backed** (catches the compound
|
|
124
|
+
bypass a glob misses), the only import allowed is `vigiles/hook` (capability = API
|
|
125
|
+
surface), and the artifact is **stamped** (a later hand-edit is refused). A real,
|
|
126
|
+
widely-copied OSS safety hook blocks **2/7** of the disaster battery; the compiled
|
|
127
|
+
rewrite blocks **7/7**. Honest about the cons: this fixes a hook's _logic_, not
|
|
128
|
+
the harness's _delivery_ — a subagent's tool calls still bypass any PreToolUse
|
|
129
|
+
hook ([#34692](https://github.com/anthropics/claude-code/issues/34692)), so it's a
|
|
130
|
+
strong default, not an unbypassable wall. **[Compiled hooks — bug classes + trade-offs →](docs/compiled-hooks.md)**
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
## ③ Test — does your harness do its job?
|
|
133
|
+
|
|
134
|
+
A hook can be wired wrong; a skill's description can fail to trigger — or hijack
|
|
135
|
+
unrelated prompts; injected context can never reach the model. All of it passes a
|
|
107
136
|
naive "did it run?" check. vigiles tests the assembled harness for real:
|
|
108
137
|
|
|
109
138
|
```typescript
|
|
@@ -121,26 +150,48 @@ It goes well past _"did it fire?"_:
|
|
|
121
150
|
|
|
122
151
|
- **Hooks block** what they must — `runHook`, or the real agent CLI via `runHarnessTest`.
|
|
123
152
|
- **Skills trigger** on the right prompts and stay quiet on the wrong ones — recall _and_ precision (`measureTriggerRate`).
|
|
124
|
-
- **Behaviour is good** — score a skill's output
|
|
153
|
+
- **Behaviour is good** — score a skill's output, or A/B it on-vs-off for the real lift (`measure` / `runEval`, with significance testing).
|
|
125
154
|
- **Safety holds** — the agent _didn't_ push to the wrong branch or hit a paid API; `interceptTools` catches the attempt so the side effect never happens.
|
|
126
155
|
|
|
127
|
-
|
|
128
|
-
|
|
156
|
+
Almost every tier runs with **no model and no API key** — milliseconds, on every
|
|
157
|
+
commit; only the real-model evals need a model, on your own `claude` CLI.
|
|
158
|
+
**[How it works →](docs/harness-testing.md)**
|
|
159
|
+
|
|
160
|
+
## ④ Eval — does it actually help, or just cost more?
|
|
161
|
+
|
|
162
|
+
A skill claims "65% fewer tokens." A plugin promises "3× faster." Stars and
|
|
163
|
+
vibes — **zero measurement**. vigiles A/Bs the claim on real coding tasks, the
|
|
164
|
+
harness loaded exactly as it ships, and reports the **metric triple**:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { measureArms } from "vigiles/testing";
|
|
168
|
+
|
|
169
|
+
const r = await measureArms({
|
|
170
|
+
fixture: { "in.txt": "Implement a slug helper." },
|
|
171
|
+
task: "Read in.txt, write slugify() to slug.js, explain. Stop.",
|
|
172
|
+
arms: { baseline: {}, skill: { files: { "SKILL.md": THE_SKILL } } },
|
|
173
|
+
measure: (ctx) => ({ cost: ctx.usage.costUsd, correct: check(ctx) }),
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **The bill (`costUsd`)** — weights cache ~0.1× / output 1×, so a "saved tokens" headline can't hide behind cheap cache.
|
|
178
|
+
- **The target** — whatever the skill claims to move (output tokens, latency, tool calls), verified on its own terms.
|
|
179
|
+
- **The blast radius** — correctness, a deterministic 1/0. A token win that breaks the code is **not a win**.
|
|
129
180
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
| **vigiles** | your Claude Pro/Max sub | **$0 extra** — and most tiers need no model |
|
|
181
|
+
The kicker: every run is **your own `claude` CLI on your Pro/Max subscription**,
|
|
182
|
+
so you can measure on every change — and the same engine powers the **ecosystem
|
|
183
|
+
benchmark** ("what works vs hype"). **[Eval a skill →](docs/measuring-skills.md)** · **[Why it's affordable →](docs/eval-architecture.md)**
|
|
134
184
|
|
|
135
|
-
|
|
136
|
-
|
|
185
|
+
**The eval you can actually afford.** promptfoo / DeepEval / … hit a metered API
|
|
186
|
+
SDK and bill **per token, every run**; vigiles answers most questions with **no
|
|
187
|
+
model at all** and runs the rest on **your Claude Pro/Max subscription — $0 extra**.
|
|
137
188
|
|
|
138
189
|
## More
|
|
139
190
|
|
|
140
|
-
- **[Plugin health leaderboard →](docs/cli.md#scan-dir)** — point `scan` at a marketplace (e.g. `wshobson/agents`) and it ranks every plugin by structural health (0–100, A–F), worst issues first —
|
|
141
|
-
- **[CLI & GitHub Action →](docs/cli.md)** — every command
|
|
142
|
-
- **[Skills →](docs/skills.md)** — consumer skills installed as a Claude Code plugin
|
|
143
|
-
- **[Docs index →](docs/README.md)** · **[Research →](research/README.md)** · **[Related tools →](docs/related-tools.md)** (ast-grep, Dependency Cruiser, Ruler, rulesync).
|
|
191
|
+
- **[Plugin health leaderboard →](docs/cli.md#scan-dir)** — point `scan` at a marketplace (e.g. `wshobson/agents`) and it ranks every plugin by structural health (0–100, A–F), worst issues first — **no key**. Add `--trigger` for the model-gated column: do the skills actually fire?
|
|
192
|
+
- **[CLI & GitHub Action →](docs/cli.md)** — every command (incl. compiled hooks via `compile`), the Action, and the plugin. The full **[lint rules matrix →](docs/verifying-instruction-files.md#the-validation-rules--the-full-matrix)** lives with the linting guide.
|
|
193
|
+
- **[Skills →](docs/skills.md)** — consumer skills installed as a Claude Code plugin. The model-invocable ones (`test-harness`, `strengthen`, `edit-spec`) fire on their own — ask _"test my skills"_ and the agent reaches for them.
|
|
194
|
+
- **[Docs index →](docs/README.md)** · **[Research →](research/README.md)** · **[API reference →](https://zernie.github.io/vigiles/)** (generated) · **[Related tools →](docs/related-tools.md)** (ast-grep, Dependency Cruiser, Ruler, rulesync).
|
|
144
195
|
- Companion to [Feedback Loop Is All You Need](https://zernie.com/blog/feedback-loop-is-all-you-need).
|
|
145
196
|
|
|
146
197
|
## License
|
package/dist/action-gate.js
CHANGED
|
@@ -13,7 +13,7 @@ exports.loadActionGates = loadActionGates;
|
|
|
13
13
|
*
|
|
14
14
|
* It is the same deterministic gate primitive (reuses `runGate` + the
|
|
15
15
|
* author-time reference resolution), re-anchored from step → action. Delivered
|
|
16
|
-
* as a PostToolUse hook (`vigiles action
|
|
16
|
+
* as a PostToolUse hook (`vigiles hook-runtime action`): exit 2 blocks the action and
|
|
17
17
|
* feeds the reason back, exit 0 allows it.
|
|
18
18
|
*
|
|
19
19
|
* Config: `.vigiles/action-gates.json` → `{ "gates": [ { on, gate, when? } ] }`.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* doesn't list.
|
|
11
11
|
*
|
|
12
12
|
* This is the same emit-a-hook pattern the skill runtime already ships
|
|
13
|
-
* (`src/skill-runtime.ts`): there a `Stop` hook reads the active skill's
|
|
13
|
+
* (`src/adapters/claude-code/skill-runtime.ts`): there a `Stop` hook reads the active skill's
|
|
14
14
|
* compiled SKILL.md and runs its result gate; here a `PreToolUse` hook reads
|
|
15
15
|
* the active agent's compiled `.md`, parses its `tools:` allowlist, and
|
|
16
16
|
* allows/denies the tool call. The compiled markdown's frontmatter is the
|
|
@@ -66,12 +66,72 @@ export interface PreToolDecision {
|
|
|
66
66
|
* feeding the contract back to the model so it self-corrects.
|
|
67
67
|
*/
|
|
68
68
|
export declare function decidePreToolUse(allowed: readonly string[] | null, tool: string): PreToolDecision;
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Read the active-agent stack (oldest → newest; the dispatched subagent chain).
|
|
71
|
+
* Back-compat: a legacy single-slot `{ agent: string }` marker reads as a one-frame
|
|
72
|
+
* stack; a malformed file or non-string entries → an empty stack (fail-open).
|
|
73
|
+
*/
|
|
74
|
+
export declare function readActiveStack(cwd: string): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Push a dispatched subagent onto the active stack (a `PreToolUse` spawn). The
|
|
77
|
+
* PreToolUse gate then enforces this subagent's contract until it returns. Under
|
|
78
|
+
* nesting each dispatch pushes a frame, so the chain is tracked, not overwritten.
|
|
79
|
+
*/
|
|
80
|
+
export declare function pushActiveAgent(cwd: string, agentPath: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Pop the top frame — the subagent returned (`SubagentStop`), so control returns
|
|
83
|
+
* to its PARENT (the next frame down), whose contract the gate enforces again.
|
|
84
|
+
* This is the nesting-safe close, distinct from {@link clearActiveAgent} (which
|
|
85
|
+
* drops the whole stack). Popping an empty stack is a no-op.
|
|
86
|
+
*/
|
|
87
|
+
export declare function popActiveAgent(cwd: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Push a subagent frame (the manual `agent-start` fallback + the deterministic
|
|
90
|
+
* spawn open-signal both use this). A single call is equivalent to a one-frame
|
|
91
|
+
* stack, so the top — what the gate reads — is this agent. Alias of
|
|
92
|
+
* {@link pushActiveAgent} kept under the historical name.
|
|
93
|
+
*/
|
|
70
94
|
export declare function setActiveAgent(cwd: string, agentPath: string): void;
|
|
71
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Clear the WHOLE stack (a hard reset / session end). Distinct from
|
|
97
|
+
* {@link popActiveAgent}, which returns to the parent frame. Idempotent.
|
|
98
|
+
*/
|
|
72
99
|
export declare function clearActiveAgent(cwd: string): void;
|
|
73
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* The active agent's compiled `.md` — the STACK TOP — or null when none is active.
|
|
102
|
+
* The gate reads the top, so a returned nested subagent reveals its parent's
|
|
103
|
+
* contract again (the contract-escape fix).
|
|
104
|
+
*/
|
|
74
105
|
export declare function readActiveAgent(cwd: string): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* EXPERIMENTAL — parked (P3), do NOT auto-wire. The subagent-window tracking is
|
|
108
|
+
* now nesting-safe: a depth-aware STACK (push on dispatch, pop on SubagentStop —
|
|
109
|
+
* see {@link pushActiveAgent}/{@link popActiveAgent}) closes the contract-escape
|
|
110
|
+
* the flat single-slot model allowed under Claude Code v2.1.172 depth-5 nesting
|
|
111
|
+
* (certified in research/prototypes/.../AgentWindowStack.tla). The open signal
|
|
112
|
+
* recognizes BOTH spawn tool names (`Task` and the nested-spawn `Agent`), gated on
|
|
113
|
+
* a resolvable `subagent_type` so a non-spawn call never opens a frame. Still
|
|
114
|
+
* parked because the `effect()` sub-region goal it served was dropped (see
|
|
115
|
+
* research/effect-boundary-design.md, "Why dropped") — the stack is shipped for
|
|
116
|
+
* when active-agent contract enforcement under nesting is wanted on its own.
|
|
117
|
+
*
|
|
118
|
+
* Resolve a spawn tool's `subagent_type` to the compiled agent `.md` to
|
|
119
|
+
* activate, or null when none is found. The DETERMINISTIC open signal that
|
|
120
|
+
* replaces the model-invoked `agent-start`: Claude Code fires `PreToolUse` for
|
|
121
|
+
* the parent's `Task` dispatch (and `SubagentStop` when it returns), so the
|
|
122
|
+
* harness — not the model — brackets the subagent's active window. The name is
|
|
123
|
+
* the last ":"-segment (a `--plugin-dir` subagent_type is namespaced
|
|
124
|
+
* "plugin:name"); searched in `agents/` under the cwd then the plugin root. A
|
|
125
|
+
* path under the cwd is returned relative (readActiveAgent resolves vs cwd); a
|
|
126
|
+
* plugin-root hit is absolute. Returns null on an unknown agent (fail-open: an
|
|
127
|
+
* unresolved subagent is simply not gated, exactly as before agent-start ran).
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolveDispatchedAgent(subagentType: string, cwd: string, pluginRoot?: string): string | null;
|
|
130
|
+
/**
|
|
131
|
+
* The agent `.md` to activate for a `PreToolUse(Task)` event, or null. Pure
|
|
132
|
+
* (reads `tool_input.subagent_type`, resolves via {@link resolveDispatchedAgent}).
|
|
133
|
+
*/
|
|
134
|
+
export declare function decideTaskDispatch(toolInput: unknown, cwd: string, pluginRoot?: string): string | null;
|
|
75
135
|
/**
|
|
76
136
|
* PreToolUse-hook decision. If an agent is active, enforce BOTH deterministic
|
|
77
137
|
* rails its compiled `.md` declares, in order:
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* doesn't list.
|
|
12
12
|
*
|
|
13
13
|
* This is the same emit-a-hook pattern the skill runtime already ships
|
|
14
|
-
* (`src/skill-runtime.ts`): there a `Stop` hook reads the active skill's
|
|
14
|
+
* (`src/adapters/claude-code/skill-runtime.ts`): there a `Stop` hook reads the active skill's
|
|
15
15
|
* compiled SKILL.md and runs its result gate; here a `PreToolUse` hook reads
|
|
16
16
|
* the active agent's compiled `.md`, parses its `tools:` allowlist, and
|
|
17
17
|
* allows/denies the tool call. The compiled markdown's frontmatter is the
|
|
@@ -28,9 +28,14 @@ exports.parseAgentTools = parseAgentTools;
|
|
|
28
28
|
exports.parseAgentToolList = parseAgentToolList;
|
|
29
29
|
exports.parseAgentPurity = parseAgentPurity;
|
|
30
30
|
exports.decidePreToolUse = decidePreToolUse;
|
|
31
|
+
exports.readActiveStack = readActiveStack;
|
|
32
|
+
exports.pushActiveAgent = pushActiveAgent;
|
|
33
|
+
exports.popActiveAgent = popActiveAgent;
|
|
31
34
|
exports.setActiveAgent = setActiveAgent;
|
|
32
35
|
exports.clearActiveAgent = clearActiveAgent;
|
|
33
36
|
exports.readActiveAgent = readActiveAgent;
|
|
37
|
+
exports.resolveDispatchedAgent = resolveDispatchedAgent;
|
|
38
|
+
exports.decideTaskDispatch = decideTaskDispatch;
|
|
34
39
|
exports.evaluatePreToolUse = evaluatePreToolUse;
|
|
35
40
|
const node_fs_1 = require("node:fs");
|
|
36
41
|
const node_path_1 = require("node:path");
|
|
@@ -98,37 +103,146 @@ function decidePreToolUse(allowed, tool) {
|
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
// ---------------------------------------------------------------------------
|
|
101
|
-
// Active-agent tracking (mirrors .vigiles/active-skill.json)
|
|
106
|
+
// Active-agent tracking — a depth-aware STACK (mirrors .vigiles/active-skill.json)
|
|
102
107
|
// ---------------------------------------------------------------------------
|
|
108
|
+
//
|
|
109
|
+
// Claude Code v2.1.172 added nested subagents (a subagent with the spawn tool can
|
|
110
|
+
// dispatch its own, up to depth 5). A single active-agent slot is NOT nesting-safe:
|
|
111
|
+
// when an inner subagent returns, clearing the whole slot drops the OUTER agent's
|
|
112
|
+
// contract while it is still running, so the PreToolUse gate then allows a tool the
|
|
113
|
+
// outer subagent forbids — a CONTRACT ESCAPE. The fix (certified in TLC, see
|
|
114
|
+
// research/prototypes/typed-spec-formal-verification/AgentWindowStack.tla) is a
|
|
115
|
+
// STACK: push on dispatch, pop on SubagentStop (back to the parent), gate on the
|
|
116
|
+
// stack TOP. Counterexample the flat model fails and the stack model passes:
|
|
117
|
+
// Open(writer); Open(writer); Stop; Call(Bash).
|
|
103
118
|
const ACTIVE_PATH = ".vigiles/active-agent.json";
|
|
104
|
-
/**
|
|
105
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Read the active-agent stack (oldest → newest; the dispatched subagent chain).
|
|
121
|
+
* Back-compat: a legacy single-slot `{ agent: string }` marker reads as a one-frame
|
|
122
|
+
* stack; a malformed file or non-string entries → an empty stack (fail-open).
|
|
123
|
+
*/
|
|
124
|
+
function readActiveStack(cwd) {
|
|
125
|
+
const p = (0, node_path_1.resolve)(cwd, ACTIVE_PATH);
|
|
126
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
127
|
+
return [];
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
|
|
130
|
+
if (Array.isArray(parsed.stack)) {
|
|
131
|
+
return parsed.stack.filter((x) => typeof x === "string");
|
|
132
|
+
}
|
|
133
|
+
// legacy single-slot format
|
|
134
|
+
if (typeof parsed.agent === "string")
|
|
135
|
+
return [parsed.agent];
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function writeActiveStack(cwd, stack) {
|
|
106
143
|
const p = (0, node_path_1.resolve)(cwd, ACTIVE_PATH);
|
|
144
|
+
if (stack.length === 0) {
|
|
145
|
+
if ((0, node_fs_1.existsSync)(p))
|
|
146
|
+
(0, node_fs_1.rmSync)(p);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
107
149
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(p), { recursive: true });
|
|
108
|
-
(0, node_fs_1.writeFileSync)(p, JSON.stringify({
|
|
150
|
+
(0, node_fs_1.writeFileSync)(p, JSON.stringify({ stack }) + "\n");
|
|
109
151
|
}
|
|
110
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Push a dispatched subagent onto the active stack (a `PreToolUse` spawn). The
|
|
154
|
+
* PreToolUse gate then enforces this subagent's contract until it returns. Under
|
|
155
|
+
* nesting each dispatch pushes a frame, so the chain is tracked, not overwritten.
|
|
156
|
+
*/
|
|
157
|
+
function pushActiveAgent(cwd, agentPath) {
|
|
158
|
+
writeActiveStack(cwd, [...readActiveStack(cwd), agentPath]);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Pop the top frame — the subagent returned (`SubagentStop`), so control returns
|
|
162
|
+
* to its PARENT (the next frame down), whose contract the gate enforces again.
|
|
163
|
+
* This is the nesting-safe close, distinct from {@link clearActiveAgent} (which
|
|
164
|
+
* drops the whole stack). Popping an empty stack is a no-op.
|
|
165
|
+
*/
|
|
166
|
+
function popActiveAgent(cwd) {
|
|
167
|
+
const stack = readActiveStack(cwd);
|
|
168
|
+
stack.pop();
|
|
169
|
+
writeActiveStack(cwd, stack);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Push a subagent frame (the manual `agent-start` fallback + the deterministic
|
|
173
|
+
* spawn open-signal both use this). A single call is equivalent to a one-frame
|
|
174
|
+
* stack, so the top — what the gate reads — is this agent. Alias of
|
|
175
|
+
* {@link pushActiveAgent} kept under the historical name.
|
|
176
|
+
*/
|
|
177
|
+
function setActiveAgent(cwd, agentPath) {
|
|
178
|
+
pushActiveAgent(cwd, agentPath);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Clear the WHOLE stack (a hard reset / session end). Distinct from
|
|
182
|
+
* {@link popActiveAgent}, which returns to the parent frame. Idempotent.
|
|
183
|
+
*/
|
|
111
184
|
function clearActiveAgent(cwd) {
|
|
112
185
|
const p = (0, node_path_1.resolve)(cwd, ACTIVE_PATH);
|
|
113
186
|
if ((0, node_fs_1.existsSync)(p))
|
|
114
187
|
(0, node_fs_1.rmSync)(p);
|
|
115
188
|
}
|
|
116
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* The active agent's compiled `.md` — the STACK TOP — or null when none is active.
|
|
191
|
+
* The gate reads the top, so a returned nested subagent reveals its parent's
|
|
192
|
+
* contract again (the contract-escape fix).
|
|
193
|
+
*/
|
|
117
194
|
function readActiveAgent(cwd) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
return null;
|
|
121
|
-
try {
|
|
122
|
-
const parsed = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
|
|
123
|
-
return typeof parsed.agent === "string" ? parsed.agent : null;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
195
|
+
const stack = readActiveStack(cwd);
|
|
196
|
+
return stack.length > 0 ? stack[stack.length - 1] : null;
|
|
128
197
|
}
|
|
129
198
|
// ---------------------------------------------------------------------------
|
|
130
199
|
// PreToolUse-hook decision
|
|
131
200
|
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* EXPERIMENTAL — parked (P3), do NOT auto-wire. The subagent-window tracking is
|
|
203
|
+
* now nesting-safe: a depth-aware STACK (push on dispatch, pop on SubagentStop —
|
|
204
|
+
* see {@link pushActiveAgent}/{@link popActiveAgent}) closes the contract-escape
|
|
205
|
+
* the flat single-slot model allowed under Claude Code v2.1.172 depth-5 nesting
|
|
206
|
+
* (certified in research/prototypes/.../AgentWindowStack.tla). The open signal
|
|
207
|
+
* recognizes BOTH spawn tool names (`Task` and the nested-spawn `Agent`), gated on
|
|
208
|
+
* a resolvable `subagent_type` so a non-spawn call never opens a frame. Still
|
|
209
|
+
* parked because the `effect()` sub-region goal it served was dropped (see
|
|
210
|
+
* research/effect-boundary-design.md, "Why dropped") — the stack is shipped for
|
|
211
|
+
* when active-agent contract enforcement under nesting is wanted on its own.
|
|
212
|
+
*
|
|
213
|
+
* Resolve a spawn tool's `subagent_type` to the compiled agent `.md` to
|
|
214
|
+
* activate, or null when none is found. The DETERMINISTIC open signal that
|
|
215
|
+
* replaces the model-invoked `agent-start`: Claude Code fires `PreToolUse` for
|
|
216
|
+
* the parent's `Task` dispatch (and `SubagentStop` when it returns), so the
|
|
217
|
+
* harness — not the model — brackets the subagent's active window. The name is
|
|
218
|
+
* the last ":"-segment (a `--plugin-dir` subagent_type is namespaced
|
|
219
|
+
* "plugin:name"); searched in `agents/` under the cwd then the plugin root. A
|
|
220
|
+
* path under the cwd is returned relative (readActiveAgent resolves vs cwd); a
|
|
221
|
+
* plugin-root hit is absolute. Returns null on an unknown agent (fail-open: an
|
|
222
|
+
* unresolved subagent is simply not gated, exactly as before agent-start ran).
|
|
223
|
+
*/
|
|
224
|
+
function resolveDispatchedAgent(subagentType, cwd, pluginRoot) {
|
|
225
|
+
const name = subagentType.split(":").pop()?.trim();
|
|
226
|
+
if (!name)
|
|
227
|
+
return null;
|
|
228
|
+
const rel = (0, node_path_1.join)("agents", `${name}.md`);
|
|
229
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, rel)))
|
|
230
|
+
return rel;
|
|
231
|
+
if (pluginRoot && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(pluginRoot, rel))) {
|
|
232
|
+
return (0, node_path_1.resolve)(pluginRoot, rel);
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The agent `.md` to activate for a `PreToolUse(Task)` event, or null. Pure
|
|
238
|
+
* (reads `tool_input.subagent_type`, resolves via {@link resolveDispatchedAgent}).
|
|
239
|
+
*/
|
|
240
|
+
function decideTaskDispatch(toolInput, cwd, pluginRoot) {
|
|
241
|
+
const st = toolInput?.subagent_type;
|
|
242
|
+
if (typeof st !== "string" || !st)
|
|
243
|
+
return null;
|
|
244
|
+
return resolveDispatchedAgent(st, cwd, pluginRoot);
|
|
245
|
+
}
|
|
132
246
|
/**
|
|
133
247
|
* PreToolUse-hook decision. If an agent is active, enforce BOTH deterministic
|
|
134
248
|
* rails its compiled `.md` declares, in order:
|
|
@@ -8,6 +8,40 @@
|
|
|
8
8
|
* in its adapter (e.g. `src/adapters/codex/dialect.ts` exporting `codexDialect`).
|
|
9
9
|
*/
|
|
10
10
|
import type { HarnessDialect } from "../../core/dialect.js";
|
|
11
|
+
/**
|
|
12
|
+
* The Claude Code built-in subagent tool catalog as a `const` tuple, so a typed
|
|
13
|
+
* authoring surface can derive a LITERAL union (`ClaudeCodeBuiltinTool`) from it.
|
|
14
|
+
* `claudeCodeDialect.builtinAgentTools` references this same array — one source
|
|
15
|
+
* of truth for the runtime catalog AND the compile-time tool vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
export declare const claudeCodeBuiltinAgentTools: readonly ["Read", "Write", "Edit", "MultiEdit", "Bash", "BashOutput", "KillBash", "Grep", "Glob", "LS", "WebSearch", "WebFetch", "NotebookEdit", "TodoWrite", "Task", "Skill"];
|
|
18
|
+
/**
|
|
19
|
+
* The Claude Code side-effecting tools as a `const` tuple (the complement of
|
|
20
|
+
* read-only within `builtinAgentTools`). `claudeCodeDialect.sideEffectingTools`
|
|
21
|
+
* references this; the typed vocabulary derives the read-only / bounded splits.
|
|
22
|
+
*/
|
|
23
|
+
export declare const claudeCodeSideEffectingTools: readonly ["Bash", "BashOutput", "KillBash", "Edit", "MultiEdit", "Write", "NotebookEdit", "WebFetch", "WebSearch", "Skill", "Task", "TodoWrite"];
|
|
11
24
|
export declare const claudeCodeDialect: HarnessDialect;
|
|
25
|
+
/** Every Claude Code built-in subagent tool (literal union). */
|
|
26
|
+
export type ClaudeCodeBuiltinTool = (typeof claudeCodeBuiltinAgentTools)[number];
|
|
27
|
+
/** The side-effecting subset (literal union). */
|
|
28
|
+
export type ClaudeCodeSideEffectingTool = (typeof claudeCodeSideEffectingTools)[number];
|
|
29
|
+
/**
|
|
30
|
+
* Read-only built-in tools — the complement of the side-effecting set within
|
|
31
|
+
* the built-in catalog. The tools a `pure` CC unit may declare.
|
|
32
|
+
*/
|
|
33
|
+
export type ClaudeCodeReadOnlyTool = Exclude<ClaudeCodeBuiltinTool, ClaudeCodeSideEffectingTool>;
|
|
34
|
+
/**
|
|
35
|
+
* Tools a `bounded` CC unit may declare: read-only ∪ the decidable
|
|
36
|
+
* side-effecting tools (Write/Edit/MultiEdit/NotebookEdit) ∪ `Bash` (its
|
|
37
|
+
* command is decided at RUNTIME by the gate). Bars MCP / unknown / wildcard —
|
|
38
|
+
* those are simply not in the built-in union, so listing one is a `tsc` error.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: `BashOutput`/`KillBash` are read-only-ish helpers tied to a running
|
|
41
|
+
* Bash; `Bash` is the admitting tool, so they're included via the read-only
|
|
42
|
+
* exclusion path only if read-only — here they stay side-effecting, hence the
|
|
43
|
+
* explicit add of the bounded-decidable set plus `Bash`.
|
|
44
|
+
*/
|
|
45
|
+
export type ClaudeCodeBoundedTool = ClaudeCodeReadOnlyTool | "Write" | "Edit" | "MultiEdit" | "NotebookEdit" | "Bash";
|
|
12
46
|
export type { HarnessDialect } from "../../core/dialect.js";
|
|
13
47
|
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -1,28 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.claudeCodeDialect = void 0;
|
|
3
|
+
exports.claudeCodeDialect = exports.claudeCodeSideEffectingTools = exports.claudeCodeBuiltinAgentTools = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The Claude Code built-in subagent tool catalog as a `const` tuple, so a typed
|
|
6
|
+
* authoring surface can derive a LITERAL union (`ClaudeCodeBuiltinTool`) from it.
|
|
7
|
+
* `claudeCodeDialect.builtinAgentTools` references this same array — one source
|
|
8
|
+
* of truth for the runtime catalog AND the compile-time tool vocabulary.
|
|
9
|
+
*/
|
|
10
|
+
exports.claudeCodeBuiltinAgentTools = [
|
|
11
|
+
"Read",
|
|
12
|
+
"Write",
|
|
13
|
+
"Edit",
|
|
14
|
+
"MultiEdit",
|
|
15
|
+
"Bash",
|
|
16
|
+
"BashOutput",
|
|
17
|
+
"KillBash",
|
|
18
|
+
"Grep",
|
|
19
|
+
"Glob",
|
|
20
|
+
"LS",
|
|
21
|
+
"WebSearch",
|
|
22
|
+
"WebFetch",
|
|
23
|
+
"NotebookEdit",
|
|
24
|
+
"TodoWrite",
|
|
25
|
+
"Task",
|
|
26
|
+
"Skill",
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The Claude Code side-effecting tools as a `const` tuple (the complement of
|
|
30
|
+
* read-only within `builtinAgentTools`). `claudeCodeDialect.sideEffectingTools`
|
|
31
|
+
* references this; the typed vocabulary derives the read-only / bounded splits.
|
|
32
|
+
*/
|
|
33
|
+
exports.claudeCodeSideEffectingTools = [
|
|
34
|
+
"Bash",
|
|
35
|
+
"BashOutput",
|
|
36
|
+
"KillBash",
|
|
37
|
+
"Edit",
|
|
38
|
+
"MultiEdit",
|
|
39
|
+
"Write",
|
|
40
|
+
"NotebookEdit",
|
|
41
|
+
"WebFetch",
|
|
42
|
+
"WebSearch",
|
|
43
|
+
"Skill",
|
|
44
|
+
"Task",
|
|
45
|
+
"TodoWrite",
|
|
46
|
+
];
|
|
4
47
|
exports.claudeCodeDialect = {
|
|
5
48
|
name: "claude-code",
|
|
6
49
|
// The tool contract a subagent may declare — the rails it runs on. Anything
|
|
7
50
|
// else must be an MCP tool, else it's a typo / nonexistent tool.
|
|
8
|
-
builtinAgentTools:
|
|
9
|
-
"Read",
|
|
10
|
-
"Write",
|
|
11
|
-
"Edit",
|
|
12
|
-
"MultiEdit",
|
|
13
|
-
"Bash",
|
|
14
|
-
"BashOutput",
|
|
15
|
-
"KillBash",
|
|
16
|
-
"Grep",
|
|
17
|
-
"Glob",
|
|
18
|
-
"LS",
|
|
19
|
-
"WebSearch",
|
|
20
|
-
"WebFetch",
|
|
21
|
-
"NotebookEdit",
|
|
22
|
-
"TodoWrite",
|
|
23
|
-
"Task",
|
|
24
|
-
"Skill",
|
|
25
|
-
],
|
|
51
|
+
builtinAgentTools: exports.claudeCodeBuiltinAgentTools,
|
|
26
52
|
// Tools the platform never exposes to a subagent, whatever the list says — so
|
|
27
53
|
// a subagent listing one is a guaranteed-dead reference only a compiler catches.
|
|
28
54
|
neverAvailableTools: [
|
|
@@ -68,19 +94,6 @@ exports.claudeCodeDialect = {
|
|
|
68
94
|
// which are not in the subagent catalog). Bash is side-effecting because
|
|
69
95
|
// `cat` and `rm -rf` are the same tool at the tool-name level — the
|
|
70
96
|
// sandbox is the only closure for subprocess effects.
|
|
71
|
-
sideEffectingTools:
|
|
72
|
-
"Bash",
|
|
73
|
-
"BashOutput",
|
|
74
|
-
"KillBash",
|
|
75
|
-
"Edit",
|
|
76
|
-
"MultiEdit",
|
|
77
|
-
"Write",
|
|
78
|
-
"NotebookEdit",
|
|
79
|
-
"WebFetch",
|
|
80
|
-
"WebSearch",
|
|
81
|
-
"Skill",
|
|
82
|
-
"Task",
|
|
83
|
-
"TodoWrite",
|
|
84
|
-
],
|
|
97
|
+
sideEffectingTools: exports.claudeCodeSideEffectingTools,
|
|
85
98
|
};
|
|
86
99
|
//# sourceMappingURL=dialect.js.map
|
|
@@ -8,7 +8,7 @@ exports.hasEffectBoundary = hasEffectBoundary;
|
|
|
8
8
|
* Effect-boundary position-aware state tracking.
|
|
9
9
|
* Mirrors active-unit tracking (.vigiles/active-agent.json / .vigiles/active-skill.json).
|
|
10
10
|
* The PreToolUse hook reads this file to decide whether the agent is inside an
|
|
11
|
-
* effect boundary (set by `vigiles effect-enter`) or outside it.
|
|
11
|
+
* effect boundary (set by `vigiles hook-runtime effect-enter`) or outside it.
|
|
12
12
|
*/
|
|
13
13
|
const node_fs_1 = require("node:fs");
|
|
14
14
|
const node_path_1 = require("node:path");
|