@fusengine/harness 0.1.55 → 0.1.57
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 +150 -50
- package/dist/cli/bin.mjs +1 -1
- package/dist/{handle-L4ZNmpwN.mjs → handle-DY79I4a8.mjs} +1953 -1514
- package/dist/runtime/index.d.mts +2 -0
- package/dist/runtime/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# @fusengine/harness
|
|
2
2
|
|
|
3
|
-
A **
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
and **thin per-harness adapters** that map a hook payload to the policy and back
|
|
10
|
-
to that harness's native response.
|
|
3
|
+
A **governance layer for AI coding agents**: gates backed by cited evidence
|
|
4
|
+
(not vibes), a decision-time lesson memory, verification receipts, and a
|
|
5
|
+
one-shot gate metric — ported from a Claude Code plugin into one **Bun-native**
|
|
6
|
+
npm package. The policy core is harness-agnostic; how much of it actually
|
|
7
|
+
*enforces* depends on the harness's own hook system — see the
|
|
8
|
+
[compatibility matrix](#compatibility) below before assuming parity across harnesses.
|
|
11
9
|
|
|
12
10
|
```
|
|
13
|
-
detect → init (
|
|
11
|
+
detect → init (writes that harness's hook wiring) → `harness hook` → guards + APEX gates → native deny/ask/context
|
|
14
12
|
```
|
|
15
13
|
|
|
16
14
|
## Install
|
|
@@ -25,18 +23,37 @@ bun add @fusengine/harness # Bun reads the TS source directly — no build
|
|
|
25
23
|
|
|
26
24
|
```sh
|
|
27
25
|
cd your-project
|
|
28
|
-
harness init # detects the harness, writes its pre+post
|
|
29
|
-
# The SOLID-read gate auto-activates from discovered skills (default marketplace: fusengine-plugins).
|
|
26
|
+
harness init # detects the harness, writes its pre+post hook wiring
|
|
30
27
|
export FUSE_HARNESS_MARKETPLACES=fusengine-plugins # (optional) which marketplaces to auto-scan
|
|
31
28
|
export FUSE_HARNESS_REFS=.claude/skills # (optional) explicit refs dir, overrides auto-discovery
|
|
32
29
|
```
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
`init` writes the wiring file for the detected harness (`.claude/settings.json`,
|
|
32
|
+
`.codex/hooks.json`, `.cursor/hooks.json`, `.gemini/settings.json`, or
|
|
33
|
+
`.clinerules/hooks/PreToolUse`+`PostToolUse`) — for Claude Code, Codex, Cursor,
|
|
34
|
+
Gemini CLI and Cline this covers **PreToolUse + PostToolUse only**
|
|
35
|
+
(`src/init/templates.ts`); Hermes has no `init` runner because its config lives
|
|
36
|
+
outside the project at `~/.hermes/config.yaml` (`src/adapters/hermes/index.ts:3-4`).
|
|
37
|
+
The richer lifecycle (SessionStart, SubagentStart/Stop, Stop, PreCompact/PostCompact,
|
|
38
|
+
TaskCompleted, TeammateIdle, PostToolUseFailure, InstructionsLoaded — 14 distinct
|
|
39
|
+
Claude Code hook events in total, `src/runtime/lifecycle/dispatch.ts` +
|
|
40
|
+
`src/runtime/handle.ts:56-80`) is **implemented** in the runtime but only fires
|
|
41
|
+
when something wires those extra hook events into `.claude/settings.json` — either
|
|
42
|
+
by hand, or via the fusengine-plugins marketplace this repo is itself developed
|
|
43
|
+
under (its ~11 sibling plugins each add their own event, `src/runtime/burst-window.ts:5-11`).
|
|
44
|
+
|
|
45
|
+
**Where state lives** (two separate roots — this changed across 0.1.34→0.1.51,
|
|
46
|
+
don't assume either the old or the "everything in one place" story):
|
|
47
|
+
|
|
48
|
+
- Session track, deny-loop/one-shot sidecars: **out-of-tree**, under
|
|
49
|
+
`~/.fuse-harness/state/<8-char-md5-of-project-path>/` (`src/runtime/paths.ts:5-6,41-43`) —
|
|
50
|
+
deliberately outside the repo so the protected-path guard never has to bless
|
|
51
|
+
writes to its own enforcement state.
|
|
52
|
+
- MCP/WebFetch response cache and the curated `MEMORY/LESSON.md`-equivalent lessons
|
|
53
|
+
file: still **in-tree**, under `<project>/.harness/{cache,memory}`
|
|
54
|
+
(`src/config/layout.ts:15-26`) — `.harness/` is gitignored except
|
|
55
|
+
`memory/LESSON.md`, which is meant to be committed (`STATE_GITIGNORE`,
|
|
56
|
+
`src/config/layout.ts:10`).
|
|
40
57
|
|
|
41
58
|
### CLI
|
|
42
59
|
|
|
@@ -66,7 +83,7 @@ Any consumer `hooks.json` / `settings.json` that runs the harness via `bunx`
|
|
|
66
83
|
|
|
67
84
|
```jsonc
|
|
68
85
|
// .claude/settings.json — correct: exact pin
|
|
69
|
-
"command": "bunx @fusengine/harness@0.1.
|
|
86
|
+
"command": "bunx @fusengine/harness@0.1.56 hook claude"
|
|
70
87
|
// WRONG — may silently keep running a stale global install:
|
|
71
88
|
"command": "bunx @fusengine/harness hook claude"
|
|
72
89
|
```
|
|
@@ -75,9 +92,8 @@ Any consumer `hooks.json` / `settings.json` that runs the harness via `bunx`
|
|
|
75
92
|
prefer an already-installed **global** copy over npm-latest, and publishing a new
|
|
76
93
|
version updates neither that global nor the bunx cache (`bun pm cache rm` does
|
|
77
94
|
**not** remove the global). A hook wired without an exact pin can therefore keep
|
|
78
|
-
executing an old harness indefinitely after you publish a fix
|
|
79
|
-
|
|
80
|
-
`bunx`-resolved binary. Pinning `@X.Y.Z` forces the exact version to resolve.
|
|
95
|
+
executing an old harness indefinitely after you publish a fix. Pinning `@X.Y.Z`
|
|
96
|
+
forces the exact version to resolve.
|
|
81
97
|
|
|
82
98
|
Run `harness doctor` to see which version is actually executing and whether npm
|
|
83
99
|
has a newer one:
|
|
@@ -85,11 +101,11 @@ has a newer one:
|
|
|
85
101
|
```sh
|
|
86
102
|
harness doctor
|
|
87
103
|
# @fusengine/harness doctor
|
|
88
|
-
# running: 0.1.
|
|
104
|
+
# running: 0.1.56
|
|
89
105
|
# package: /Users/you/.bun/install/global/node_modules/@fusengine/harness
|
|
90
106
|
# runtime: /Users/you/.bun/bin/bun
|
|
91
|
-
# npm latest: 0.1.
|
|
92
|
-
# ! stale — npm serves 0.1.
|
|
107
|
+
# npm latest: 0.1.57
|
|
108
|
+
# ! stale — npm serves 0.1.57. Pin "@fusengine/harness@0.1.57" in hooks.json.
|
|
93
109
|
```
|
|
94
110
|
|
|
95
111
|
If `doctor` reports a stale global, clear it and re-pin:
|
|
@@ -100,29 +116,88 @@ bun remove -g @fusengine/harness && bun pm cache rm
|
|
|
100
116
|
|
|
101
117
|
[oven-sh/bun#5791]: https://github.com/oven-sh/bun/issues/5791
|
|
102
118
|
|
|
119
|
+
## Compatibility
|
|
120
|
+
|
|
121
|
+
**There is no "runs the same on any harness."** Each row is a real ceiling, not
|
|
122
|
+
a formatting nuance — read it before assuming a gate that works on Claude Code
|
|
123
|
+
also works elsewhere.
|
|
124
|
+
|
|
125
|
+
| Harness | PreToolUse coverage | Lifecycle (Session/Subagent/Stop/Compact/…) | Known limit |
|
|
126
|
+
|---|---|---|---|
|
|
127
|
+
| **claude-code** | Full: `evaluate` + APEX gates via `handleHook` (`src/adapters/claude/index.ts`) | 14 event types implemented (`dispatch.ts`) — fires once wired into `.claude/settings.json` beyond the `init` default | None found; richer lifecycle needs manual/marketplace wiring (see Quickstart) |
|
|
128
|
+
| **codex** | Reuses the Claude reader/response shape — Codex's hook wire format matches Claude's (`src/adapters/codex/index.ts:1-8`). Bash is gated reliably. | Not wired by `harness init codex` (PreToolUse `Bash\|apply_patch` + PostToolUse only, `src/init/templates.ts:29-38`) | The SOLID/file-size gate covers `apply_patch` edits at **0%** — it keys off `tool_input.file_path` (Write/Edit shape), which Codex's diff-carrying `apply_patch` call never supplies (`codex/index.ts:6-8`). Codex also **parses but does not honor** `permissionDecision:"ask"` — deny-only (`codex/index.ts:6`). |
|
|
129
|
+
| **cursor** | `beforeShellExecution` can deny/ask (shell only, `cursor/index.ts:16-21`) | none | `afterFileEdit` is **observe-only** — "Cursor cannot block here" (`cursor/index.ts:23-24`); a file-edit violation is logged, never prevented (platform limit, not a bug here). |
|
|
130
|
+
| **gemini-cli** | `BeforeTool` denies via `{decision:"deny",reason}` (`gemini/index.ts:22-36`) | none | Thin stateless adapter — no session track, no APEX gates wired through it. |
|
|
131
|
+
| **cline** | `PreToolUse` only; block → `{cancel:true}`, non-block → `contextModification` (`cline/index.ts:24-36`) | none | Same as gemini-cli: stateless guard only. |
|
|
132
|
+
| **hermes** | `pre_tool_call` proven: reuses the Claude stdin reader, blocks via `{decision:"block",reason}` (`hermes/index.ts:12-36`) | untested — no lifecycle dispatch wired for Hermes in this repo | `ask`/`inform` degrade to non-blocking `{context}` — Hermes "has no interactive ask state" (`hermes/index.ts:27-28`). |
|
|
133
|
+
|
|
103
134
|
## What it enforces
|
|
104
135
|
|
|
105
|
-
|
|
136
|
+
Guard/gate chain evaluated before a tool runs (`src/policy/guards/index.ts`,
|
|
137
|
+
`src/policy/apex-gates.ts`, `src/policy/evaluate.ts`):
|
|
106
138
|
|
|
107
139
|
| Guard / gate | Fires on |
|
|
108
140
|
|---|---|
|
|
109
|
-
| file-size (SOLID) | a code file over `FUSE_SOLID_MAX_LINES` (default 100) |
|
|
110
|
-
| git | destructive git (`push --force`, `reset --hard`, …) |
|
|
111
|
-
| bash-write | `python3 -c` / `sed -i` / redirects to code files |
|
|
112
|
-
| install | `npm/pip/brew/...` installs (asks) |
|
|
113
141
|
| security | `rm -rf /`, fork bombs, `curl \| sh`; `sudo` (asks) |
|
|
142
|
+
| protected-path | edits to `.claude/plugins\|logs\|cache`, `.git/`, the harness's own state dirs |
|
|
143
|
+
| bash-write | `python3 -c` / `sed -i` / redirects to code files |
|
|
114
144
|
| interface-separation | top-level interface/type/protocol in a component/controller |
|
|
115
|
-
|
|
|
145
|
+
| install | `npm/pip/brew/...` installs (asks) |
|
|
146
|
+
| git | destructive git (`push --force`, `reset --hard`, …) — block; routine git — ask |
|
|
147
|
+
| file-size (SOLID) | a code file over `FUSE_SOLID_MAX_LINES` (default 100) |
|
|
148
|
+
| APEX brainstorm | creating a new file without brainstorming (when flagged) |
|
|
116
149
|
| APEX freshness | `explore-codebase` + `research-expert` not run within the window |
|
|
117
|
-
| APEX doc-consulted |
|
|
118
|
-
| APEX solid-read | required SOLID refs (auto-discovered, or `FUSE_HARNESS_REFS`) not read |
|
|
119
|
-
| framework sub-skill | framework / shadcn / Tailwind code whose required skill
|
|
150
|
+
| APEX doc-consulted | Context7 **and** Exa not consulted this session (a web-only fallback also passes) |
|
|
151
|
+
| APEX solid-read | required SOLID refs (auto-discovered, or `FUSE_HARNESS_REFS`) not read within the TTL |
|
|
152
|
+
| framework sub-skill | framework / shadcn / Tailwind code whose required skill wasn't read this session |
|
|
120
153
|
| Gemini MCP (opt-in) | hand-written Tailwind UI without a `mcp__gemini-design__*` call — only when `FUSE_ENFORCE_GEMINI_MCP` is set |
|
|
121
|
-
| brainstorm | creating a new file without brainstorming (when flagged) |
|
|
122
154
|
| MCP verbosity / cache | caps exa `numResults`; serves a fresh cached MCP/WebFetch result |
|
|
123
155
|
|
|
124
|
-
A trivial-edit fast path lets a few tiny (< 5-line, non-`replace_all`)
|
|
125
|
-
through per window without the full APEX gates.
|
|
156
|
+
A trivial-edit fast path lets a few tiny (< 5-line, non-`replace_all`) `Edit`s
|
|
157
|
+
through per window without the full APEX gates (`Write` is never trivial).
|
|
158
|
+
|
|
159
|
+
## Beyond gating: memory, receipts, one-shot metric
|
|
160
|
+
|
|
161
|
+
Features shipped since 0.1.44, each with its own test:
|
|
162
|
+
|
|
163
|
+
- **Deny-loop breaker** — an identical retried call that was already denied gets
|
|
164
|
+
a rewritten `[REPEAT] … STOP` message forcing a different approach, instead of
|
|
165
|
+
looping silently (`src/policy/deny-loop.ts`, `test/deny-loop.test.ts`).
|
|
166
|
+
- **Burst-window dedup** — every deployed plugin registers its own hook, so one
|
|
167
|
+
real tool call can fan out to ~11 sibling processes; a same-op record within
|
|
168
|
+
2s is folded into the first instead of re-counted (`src/runtime/burst-window.ts`,
|
|
169
|
+
`test/burst-dedup.test.ts`).
|
|
170
|
+
- **One-shot gate metric** — every gate outcome (deny or its later fix) lands in
|
|
171
|
+
a 7-day sidecar keyed by a content-free op hash, so a deny→allow transition is
|
|
172
|
+
visible (`src/tracking/one-shot.ts`, `test/one-shot.test.ts`).
|
|
173
|
+
- **Verification receipts** — a `tsc`/test run is captured from PostToolUse Bash
|
|
174
|
+
output; `TaskCompleted` **refuses** a "done" over modified code files without a
|
|
175
|
+
fresh passing receipt (`src/tracking/receipts.ts`, `test/receipts.test.ts`).
|
|
176
|
+
- **Decision-time lessons** — a `MEMORY/LESSON.md` bullet tagged with
|
|
177
|
+
`[TRIGGERS tool:… path:… error:… keyword:…]` is injected as `additionalContext`
|
|
178
|
+
the moment a matching call is about to repeat a known mistake, cooldown-guarded
|
|
179
|
+
(`src/policy/lessons/lesson-gate.ts`).
|
|
180
|
+
- **Failure lessons** *(Claude-Code-only — no `PostToolUseFailure` hook on Codex/Hermes,
|
|
181
|
+
`src/runtime/lifecycle/failure-lesson.ts:8-9`)* — a tool failure's error message is
|
|
182
|
+
matched against `error:`-triggered lessons and injected on the spot
|
|
183
|
+
(`test/failure-lesson.test.ts`, `test/sim/scenarios/17-failure-lesson.json`).
|
|
184
|
+
- **TeammateIdle anti-false-done** *(Claude-Code-only, `teammate-idle-check.ts:9-10`)* —
|
|
185
|
+
files a teammate announced as changed are checked against disk; a missing file
|
|
186
|
+
warns the lead before it's treated as done (`test/teammate-idle-check.test.ts`).
|
|
187
|
+
- **PostCompact re-injection** — after context compaction, the reconciliation
|
|
188
|
+
snapshot is re-sent with a "reread files before editing" reminder, deduped per
|
|
189
|
+
session (`src/runtime/lifecycle/post-compact.ts`, `test/post-compact.test.ts`).
|
|
190
|
+
- **Reconciliation snapshot at SessionStart** — git state, running harness
|
|
191
|
+
version + drift vs. npm, `.claude/BOARD.md`, and the one-shot summary, each
|
|
192
|
+
collector isolated so one failure can't blank the rest
|
|
193
|
+
(`src/runtime/lifecycle/snapshot/index.ts`, `test/snapshot.test.ts`).
|
|
194
|
+
- **Injection budget cap** — harness-produced context fragments (lessons,
|
|
195
|
+
snapshot, APEX task context) are capped at ~8000 chars each; owner-authored
|
|
196
|
+
content (CLAUDE.md/rules) is never capped
|
|
197
|
+
(`src/runtime/inject-budget.ts`, `test/inject-budget.test.ts`).
|
|
198
|
+
- **Hook simulator** — 18 end-to-end scenarios (payload in, expected verdict out)
|
|
199
|
+
replayed against the real CLI in both `src` and built `dist` modes in CI
|
|
200
|
+
(`test/sim/README.md`, `test/sim/scenarios/`).
|
|
126
201
|
|
|
127
202
|
### Environment
|
|
128
203
|
|
|
@@ -130,11 +205,15 @@ through per window without the full APEX gates.
|
|
|
130
205
|
|---|---|
|
|
131
206
|
| `FUSE_SOLID_MAX_LINES` | SOLID file-size limit (default `100`). |
|
|
132
207
|
| `FUSE_HARNESS_REFS` | Explicit `path.delimiter`-list of `.md` SOLID-reference dirs → activates `solidReadGate`. Overrides auto-discovery. |
|
|
133
|
-
| `FUSE_HARNESS_MARKETPLACES` | Comma-list of marketplace names whose `solid-*` skill refs are auto-discovered when `FUSE_HARNESS_REFS` is unset (default `fusengine-plugins
|
|
134
|
-
| `FUSE_ENFORCE_TTL_SEC` | APEX freshness window in seconds. |
|
|
135
|
-
| `FUSE_LESSONS_THROTTLE_MIN` | Lessons-injection throttle (
|
|
136
|
-
| `FUSE_ENFORCE_GEMINI_MCP` | **Opt-in (default off).**
|
|
137
|
-
| `
|
|
208
|
+
| `FUSE_HARNESS_MARKETPLACES` | Comma-list of marketplace names whose `solid-*` skill refs are auto-discovered when `FUSE_HARNESS_REFS` is unset (default `fusengine-plugins`). |
|
|
209
|
+
| `FUSE_ENFORCE_TTL_SEC` | APEX freshness window in seconds (default `120`). |
|
|
210
|
+
| `FUSE_LESSONS_THROTTLE_MIN` | Lessons-injection throttle, minutes (default `5`). |
|
|
211
|
+
| `FUSE_ENFORCE_GEMINI_MCP` | **Opt-in (default off).** Blocks hand-written Tailwind UI (`.tsx/.jsx/.vue/.svelte`) until a `mcp__gemini-design__*` call is made this session. Read fresh per call (`src/policy/gemini-mcp-gate.ts`). |
|
|
212
|
+
| `FUSE_DESIGN_GEMINI` | **Opt-in (default off), a *different* gate from the one above.** Enables the design-pipeline's own Gemini gates (`create_frontend` validation + "generate before hand-writing HTML/CSS") — inert unless a design agent is active (`src/policy/design/gates.ts:58-60`, see [docs/design.md](docs/design.md)). |
|
|
213
|
+
| `FUSE_MCP_TTL_SEC` | MCP (Context7/Exa) cache freshness, seconds (default 48h, `src/runtime/mcp-key.ts`). |
|
|
214
|
+
| `FUSE_WEBFETCH_TTL_SEC` | WebFetch cache freshness, seconds (default 24h — pages stale faster than docs). |
|
|
215
|
+
| `RALPH_MODE` | **Opt-in (default off).** Exempts safe git commands (`add`/`commit`/`checkout -b`/`status`/`diff`/`log`) from the confirmation ask and auto-approves project installs. Destructive git (force-push, `reset --hard`) and system installs still gate. |
|
|
216
|
+
| `CLAUDE_PROJECT_DIR` | Overrides the project root used to hash the out-of-tree state dir (`src/runtime/paths.ts:20`). |
|
|
138
217
|
|
|
139
218
|
## Library usage
|
|
140
219
|
|
|
@@ -155,7 +234,8 @@ const prompt = await gate({ sessionId, framework: "react", tool: "Write",
|
|
|
155
234
|
```
|
|
156
235
|
|
|
157
236
|
The `Prompt` it returns (`{ kind: "block" | "ask" | "inform", title, reason, actions? }`)
|
|
158
|
-
is portable; each adapter maps it to the harness's native shape
|
|
237
|
+
is portable; each adapter maps it to the harness's native shape — but, per the
|
|
238
|
+
compatibility matrix above, not every harness can act on every `kind`.
|
|
159
239
|
|
|
160
240
|
### Extend it
|
|
161
241
|
|
|
@@ -177,15 +257,15 @@ registerGuard(({ tool, command }) =>
|
|
|
177
257
|
| Subpath | What |
|
|
178
258
|
|---------|------|
|
|
179
259
|
| `./detect` | `detectHarness()` / `detectMode()` — 13 harnesses, `hook` vs `cli`. |
|
|
180
|
-
| `./policy` | `evaluate(ctx)`, the
|
|
260
|
+
| `./policy` | `evaluate(ctx)`, the guard chain, `evaluateApex`, framework detection. |
|
|
181
261
|
| `./runtime` | `handleHook`, `gate`, `recordActivity`, `activityFor`, per-harness storage + MCP intercept. |
|
|
182
|
-
| `./tracking` | Session track: `recordAgent/Doc/RefRead`, `agentsFresh`,
|
|
262
|
+
| `./tracking` | Session track: `recordAgent/Doc/RefRead`, `agentsFresh`, receipts, one-shot metric. |
|
|
183
263
|
| `./refs` | Frontmatter parse, `loadRefs(dir)`, SOLID ref scoring/routing. |
|
|
184
264
|
| `./prompt` | The portable `Prompt` type + `formatPrompt`. |
|
|
185
265
|
| `./cache` | MCP/WebFetch cache: key, lookup/store, compaction, response extraction. |
|
|
186
266
|
| `./memory` | Per-project "never reproduce" lessons. |
|
|
187
267
|
| `./config` `./util` `./state` `./statusline` `./freshness` `./init` `./cli` | env config, project-root, locks, statusline, doc-freshness, wiring templates, staged checks. |
|
|
188
|
-
| `./adapters/{claude,codex,cursor,cline,gemini}` | Thin per-harness adapters. |
|
|
268
|
+
| `./adapters/{claude,codex,cursor,cline,gemini,hermes}` | Thin per-harness adapters — see [Compatibility](#compatibility) for what each can actually enforce. |
|
|
189
269
|
|
|
190
270
|
## Documentation
|
|
191
271
|
|
|
@@ -193,20 +273,40 @@ registerGuard(({ tool, command }) =>
|
|
|
193
273
|
|-------|------|
|
|
194
274
|
| [docs/index.md](https://github.com/fusengine/harness/blob/main/docs/index.md) | architecture overview + map |
|
|
195
275
|
| [docs/detect.md](https://github.com/fusengine/harness/blob/main/docs/detect.md) | harness detection (`hook` vs `cli`) |
|
|
196
|
-
| [docs/policy.md](https://github.com/fusengine/harness/blob/main/docs/policy.md) | `evaluate`,
|
|
276
|
+
| [docs/policy.md](https://github.com/fusengine/harness/blob/main/docs/policy.md) | `evaluate`, the guard chain, framework, APEX gates |
|
|
197
277
|
| [docs/guards.md](https://github.com/fusengine/harness/blob/main/docs/guards.md) | the guard chain, `registerGuard`, fail-closed |
|
|
198
|
-
| [docs/runtime.md](https://github.com/fusengine/harness/blob/main/docs/runtime.md) | `handleHook`, `gate`, tracking, MCP intercept |
|
|
199
|
-
| [docs/config.md](https://github.com/fusengine/harness/blob/main/docs/config.md) | env config (TTL, max-lines, refs dir) |
|
|
278
|
+
| [docs/runtime.md](https://github.com/fusengine/harness/blob/main/docs/runtime.md) | `handleHook`, `gate`, tracking, MCP intercept, state paths |
|
|
279
|
+
| [docs/config.md](https://github.com/fusengine/harness/blob/main/docs/config.md) | env config (TTL, max-lines, refs dir, Gemini opt-ins) |
|
|
200
280
|
| [docs/modules.md](https://github.com/fusengine/harness/blob/main/docs/modules.md) | cache · refs · state · memory · statusline · util |
|
|
201
|
-
| [docs/adapters.md](https://github.com/fusengine/harness/blob/main/docs/adapters.md) | adapters
|
|
281
|
+
| [docs/adapters.md](https://github.com/fusengine/harness/blob/main/docs/adapters.md) | adapters, compatibility, `harness init`/`hook` wiring |
|
|
282
|
+
| [docs/design.md](https://github.com/fusengine/harness/blob/main/docs/design.md) | design-agent pipeline — state machine, gates, opt-in Gemini |
|
|
202
283
|
| [CHANGELOG.md](https://github.com/fusengine/harness/blob/main/CHANGELOG.md) | release history |
|
|
203
284
|
|
|
204
285
|
Run `bun run docs:api` for the generated typedoc API reference.
|
|
205
286
|
|
|
287
|
+
## Known limitations
|
|
288
|
+
|
|
289
|
+
- **Codex file edits are not gated.** The SOLID/file-size gate keys off
|
|
290
|
+
`tool_input.file_path`, which Codex's `apply_patch` call never supplies — only
|
|
291
|
+
Bash is reliably gated on Codex today (see [Compatibility](#compatibility)).
|
|
292
|
+
- **Cursor file edits are advisory-only.** `afterFileEdit` fires after the edit
|
|
293
|
+
already happened — a platform limit, not something this harness can work around.
|
|
294
|
+
- **Hook fan-out is mitigated, not eliminated.** The ~11-sibling-plugin burst is
|
|
295
|
+
deduped within a 2s window (`BURST_DEDUP_MS`), but that window is a heuristic,
|
|
296
|
+
not a protocol guarantee — an unusually slow fan-out could in theory land
|
|
297
|
+
outside it.
|
|
298
|
+
- **Sidechain hook reliability is a platform issue, worked around, not fixed.**
|
|
299
|
+
Sub-agent `PostToolUse` hooks don't always fire on Claude Code (documented
|
|
300
|
+
platform issues #43612/#27655/#34692); `SubagentStop` transcript harvesting
|
|
301
|
+
(`src/freshness/evidence-harvest-io.ts`) compensates, but only at that
|
|
302
|
+
checkpoint, not continuously.
|
|
303
|
+
- **Hermes coverage beyond `pre_tool_call` is unverified** — no lifecycle events
|
|
304
|
+
have been proven against a live Hermes install in this repo.
|
|
305
|
+
|
|
206
306
|
## Develop
|
|
207
307
|
|
|
208
308
|
```sh
|
|
209
|
-
bun test #
|
|
309
|
+
bun test # 484 tests (94 files)
|
|
210
310
|
bunx tsc --noEmit # typecheck (isolatedDeclarations)
|
|
211
311
|
bun run build # dist + .d.mts via tsdown (for Node/bundler consumers)
|
|
212
312
|
bun run docs:api # generate the typedoc API reference
|
package/dist/cli/bin.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
|
|
|
4
4
|
import { t as claudeHome } from "../home-state-D0RLWP8J.mjs";
|
|
5
5
|
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-DZvP_9xB.mjs";
|
|
6
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { J as runningVersion, Lt as todayUtc, Y as versionBanner, q as runDoctor, t as handleHook } from "../handle-DY79I4a8.mjs";
|
|
8
8
|
import { delimiter, join } from "node:path";
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|