@fusengine/harness 0.1.12 → 0.1.14

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 (41) hide show
  1. package/README.md +106 -41
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.mjs +1 -1
  5. package/dist/adapters/cursor/index.mjs +1 -1
  6. package/dist/adapters/gemini/index.mjs +1 -1
  7. package/dist/cache/index.mjs +1 -1
  8. package/dist/{claude-B3aJwu6O.mjs → claude-BoZuNxXl.mjs} +1 -1
  9. package/dist/cli/bin.mjs +3 -3
  10. package/dist/cli/index.mjs +1 -1
  11. package/dist/config/index.d.mts +2 -2
  12. package/dist/config/index.mjs +2 -1
  13. package/dist/{evaluate-DkTgwHNh.mjs → evaluate-qGG2Ul0W.mjs} +1 -1
  14. package/dist/freshness/index.mjs +1 -1
  15. package/dist/{freshness-BK9Xg6oG.mjs → freshness-CezohJHo.mjs} +1 -1
  16. package/dist/{handle-o5np-3T_.mjs → handle-D-DClzB2.mjs} +8 -25
  17. package/dist/index-C8dk1Alr.d.mts +70 -0
  18. package/dist/{index-BEM-mQMC.d.mts → index-DLYhervv.d.mts} +3 -1
  19. package/dist/index.d.mts +3 -3
  20. package/dist/index.mjs +9 -8
  21. package/dist/init/index.mjs +1 -1
  22. package/dist/layout-C0jaaCQC.mjs +30 -0
  23. package/dist/memory/index.d.mts +2 -2
  24. package/dist/memory/index.mjs +2 -2
  25. package/dist/{memory-BVNt4Ary.mjs → memory-BkoEbdec.mjs} +8 -3
  26. package/dist/policy/index.mjs +2 -2
  27. package/dist/{run-D2na7Z2Z.mjs → run-B-_9dPR6.mjs} +2 -2
  28. package/dist/{run-Cdp2Ef9B.mjs → run-D91N4ul1.mjs} +7 -2
  29. package/dist/runtime/index.d.mts +4 -6
  30. package/dist/runtime/index.mjs +12 -2
  31. package/dist/state/index.mjs +1 -1
  32. package/dist/{state-Bc4wdnCG.mjs → state-Cs0Y0MG_.mjs} +1 -1
  33. package/dist/{store-gtbP-Bv9.mjs → store-BnHpq2ZB.mjs} +1 -1
  34. package/dist/tracking/index.mjs +1 -1
  35. package/dist/util/index.mjs +1 -1
  36. package/package.json +111 -23
  37. package/dist/index-B-z0CCiU.d.mts +0 -37
  38. package/dist/{json-io-RH82El2J.mjs → json-io-xpTDuvtn.mjs} +1 -1
  39. package/dist/{policy-Djmqxow2.mjs → policy-Cpa5aWek.mjs} +1 -1
  40. package/dist/{project-root-C4ks_q1G.mjs → project-root-ff0_poWU.mjs} +1 -1
  41. package/dist/{store-BlaEhjab.mjs → store-DeIsfMg5.mjs} +1 -1
package/README.md CHANGED
@@ -1,68 +1,133 @@
1
1
  # @fusengine/harness
2
2
 
3
- Harness-agnostic toolkit for AI coding agents. One package, modular subpaths,
4
- **Bun-native** (the `exports` map points at the TypeScript source no build step).
3
+ A **harness-agnostic enforcement engine** for AI coding agents. It ports the
4
+ guard/gate logic of a Claude Code plugin into one reusable, **Bun-native** npm
5
+ package that runs on **any** harness — Claude Code, OpenAI Codex, Cursor, Cline,
6
+ Gemini CLI — plus a cli-mode fallback for Aider / Windsurf / OpenHands.
5
7
 
6
- It splits cleanly into a **pure policy core** (no harness coupling) and **thin
7
- adapters** that wire it into a specific harness's hook system.
8
+ It splits cleanly into a **pure policy core** (no harness coupling, fully tested)
9
+ and **thin per-harness adapters** that map a hook payload to the policy and back
10
+ to that harness's native response.
8
11
 
9
- ## Why
10
-
11
- The same guard logic (file-size limits, APEX freshness, framework detection,
12
- git guards, project memory) was duplicated across Python + TypeScript hooks and
13
- bound to one harness. This package is the single, tested source of truth — and
14
- it knows which harness it's running in.
12
+ ```
13
+ detect → init (pre+post hooks) → `harness hook` → guards + APEX gates → native deny/ask
14
+ ```
15
15
 
16
16
  ## Install
17
17
 
18
18
  ```sh
19
- bun add @fusengine/harness
19
+ npm i -g @fusengine/harness # for the CLI (harness init/hook/check)
20
+ # or, as a library:
21
+ bun add @fusengine/harness # Bun reads the TS source directly — no build step
20
22
  ```
21
23
 
22
- ## Modules
24
+ ## Quickstart
23
25
 
24
- | Subpath | What |
25
- |---------|------|
26
- | `@fusengine/harness/detect` | `detectHarness()` / `detectMode()` — Claude Code, Codex, Cursor, Cline, Gemini, opencode, Windsurf, Copilot, Aider, Kiro, Goose, Amp (env signals + `AGENT`/`AI_AGENT` standards). `mode` is `hook` or `cli`. |
27
- | `@fusengine/harness/policy` | `evaluate(ctx)` `{ decision, message }`; `evaluateFileSize`, `detectProjectType`, `detectFramework`, git/install guard patterns. |
28
- | `@fusengine/harness/config` | `resolveTtlSec` / `resolveMaxLines` (env-driven, robust parse), `ttlLabel`, `splitTarget`. |
29
- | `@fusengine/harness/memory` | Per-project "never reproduce" lessons: throttle state, multi-project registry by git root. |
30
- | `@fusengine/harness/cache` | `compactMarkdown`, `queryHash`, `jaccardSimilar`, atomic JSON I/O, MCP response extraction. |
31
- | `@fusengine/harness/freshness` | `isDocConsulted` (Context7 + Exa), trivial-edit counter. |
32
- | `@fusengine/harness/refs` | Frontmatter parsing, glob→regex, SOLID reference scoring/routing. |
33
- | `@fusengine/harness/state` | Directory locks, daily APEX state, task.json helpers. |
34
- | `@fusengine/harness/statusline` | Formatters, ANSI colors, progress/gradient bars. |
35
- | `@fusengine/harness/adapters/claude` | Claude Code adapter: read stdin → policy → `hookSpecificOutput`. |
36
-
37
- ## Usage
26
+ ```sh
27
+ cd your-project
28
+ harness init # detects the harness, writes its pre+post hooks
29
+ export FUSE_HARNESS_REFS=.claude/skills # (optional) activate the SOLID-read gate
30
+ ```
38
31
 
39
- ```ts
40
- import { detectHarness, detectMode } from "@fusengine/harness/detect";
41
- import { evaluate } from "@fusengine/harness/policy";
32
+ That's it. `init` writes the wiring file for the detected harness
33
+ (`.claude/settings.json`, `.codex/hooks.json`, `.cursor/hooks.json`,
34
+ `.gemini/settings.json`, or `.clinerules/hooks/PreToolUse`+`PostToolUse`), each
35
+ pointing at `harness hook <id>`. From then on every tool-use is gated, and the
36
+ session activity (agents run, docs consulted, refs read) is recorded
37
+ automatically under `<project>/.harness/` (track, cache, memory).
42
38
 
43
- const { id, mode } = detectHarness(); // e.g. { id: "cursor", mode: "hook" }
39
+ ### CLI
44
40
 
45
- const verdict = evaluate({ tool: "Write", filePath: "src/big.ts", content });
46
- if (verdict.decision === "deny") console.error(verdict.message);
41
+ | Command | What it does |
42
+ |---------|--------------|
43
+ | `harness init [id]` | Write the pre+post hook wiring for the detected (or named) harness. |
44
+ | `harness hook <id>` | Runtime: read a hook payload on stdin, gate (pre) or record (post), print the native response. (Hooks call this — you don't.) |
45
+ | `harness check` | cli-mode: check staged files in a pre-commit step, exit non-zero on a violation. For harnesses without hooks. |
46
+
47
+ cli-mode (Aider / Windsurf / OpenHands), as a pre-commit step:
48
+
49
+ ```sh
50
+ # .husky/pre-commit
51
+ npx harness check
47
52
  ```
48
53
 
49
- Claude Code hook (thin adapter):
54
+ ## What it enforces
55
+
56
+ Ten portable guards + the APEX gate chain, all evaluated before a tool runs:
57
+
58
+ | Guard / gate | Fires on |
59
+ |---|---|
60
+ | file-size (SOLID) | a code file over `FUSE_SOLID_MAX_LINES` (default 100) |
61
+ | git | destructive git (`push --force`, `reset --hard`, …) |
62
+ | bash-write | `python3 -c` / `sed -i` / redirects to code files |
63
+ | install | `npm/pip/brew/...` installs (asks) |
64
+ | security | `rm -rf /`, fork bombs, `curl \| sh`; `sudo` (asks) |
65
+ | interface-separation | top-level interface/type/protocol in a component/controller |
66
+ | protected-path | edits to `.claude/plugins\|logs\|cache`, `.git/` |
67
+ | APEX freshness | `explore-codebase` + `research-expert` not run within the window |
68
+ | APEX doc-consulted | Context7 **and** Exa not consulted this session |
69
+ | APEX solid-read | required SOLID refs (from `FUSE_HARNESS_REFS`) not read |
70
+ | brainstorm | creating a new file without brainstorming (when flagged) |
71
+ | MCP verbosity / cache | caps exa `numResults`; serves a fresh cached MCP/WebFetch result |
72
+
73
+ A trivial-edit fast path lets a few tiny (< 5-line, non-`replace_all`) edits
74
+ through per window without the full APEX gates.
75
+
76
+ ### Environment
77
+
78
+ | Var | Effect |
79
+ |---|---|
80
+ | `FUSE_SOLID_MAX_LINES` | SOLID file-size limit (default `100`). |
81
+ | `FUSE_HARNESS_REFS` | Directory of `.md` SOLID references → activates `solidReadGate`. |
82
+ | `FUSE_ENFORCE_TTL_SEC` | APEX freshness window in seconds. |
83
+ | `FUSE_LESSONS_THROTTLE_MIN` | Lessons-injection throttle (memory module). |
84
+
85
+ ## Library usage
50
86
 
51
87
  ```ts
52
- import { readClaudeInput, fileSizeGuard } from "@fusengine/harness/adapters/claude";
88
+ import { detectHarness } from "@fusengine/harness/detect";
89
+ import { evaluate } from "@fusengine/harness/policy";
90
+ import { gate } from "@fusengine/harness/runtime";
91
+
92
+ const { id, mode } = detectHarness(); // { id: "cursor", mode: "hook" }
53
93
 
54
- const deny = fileSizeGuard(await readClaudeInput());
55
- if (deny) { console.log(deny); process.exit(2); }
94
+ // stateless guards (file-size, git, security, …)
95
+ const verdict = evaluate({ tool: "Write", filePath: "src/big.ts", content });
96
+ if (verdict.decision !== "allow") console.error(verdict.prompt?.reason);
97
+
98
+ // full gate (stateless + stateful APEX, fed from the session track)
99
+ const prompt = await gate({ sessionId, framework: "react", tool: "Write",
100
+ filePath: "src/Button.tsx", content, now: Date.now(), trackFile });
56
101
  ```
57
102
 
58
- Harness without hooks (Aider/Windsurf/OpenHands) → `cli` mode: run the same
59
- `evaluate()` from a pre-commit step instead.
103
+ The `Prompt` it returns (`{ kind: "block" | "ask" | "inform", title, reason, actions? }`)
104
+ is portable; each adapter maps it to the harness's native shape.
105
+
106
+ ## Subpath exports
107
+
108
+ | Subpath | What |
109
+ |---------|------|
110
+ | `./detect` | `detectHarness()` / `detectMode()` — 13 harnesses, `hook` vs `cli`. |
111
+ | `./policy` | `evaluate(ctx)`, the 10 guards, `evaluateApex`, framework detection. |
112
+ | `./runtime` | `handleHook`, `gate`, `recordActivity`, `activityFor`, per-harness storage + MCP intercept. |
113
+ | `./tracking` | Session track: `recordAgent/Doc/RefRead`, `agentsFresh`, trivial-edit counter. |
114
+ | `./refs` | Frontmatter parse, `loadRefs(dir)`, SOLID ref scoring/routing. |
115
+ | `./prompt` | The portable `Prompt` type + `formatPrompt`. |
116
+ | `./cache` | MCP/WebFetch cache: key, lookup/store, compaction, response extraction. |
117
+ | `./memory` | Per-project "never reproduce" lessons. |
118
+ | `./config` `./util` `./state` `./statusline` `./freshness` `./init` `./cli` | env config, project-root, locks, statusline, doc-freshness, wiring templates, staged checks. |
119
+ | `./adapters/{claude,codex,cursor,cline,gemini}` | Thin per-harness adapters. |
120
+
121
+ See [`docs/`](./docs) for per-module guides, and run `bun run docs:api` for the
122
+ generated typedoc API reference.
60
123
 
61
124
  ## Develop
62
125
 
63
126
  ```sh
64
- bun test # test suite
65
- bunx tsc --noEmit # typecheck
127
+ bun test # 105 tests
128
+ bunx tsc --noEmit # typecheck (isolatedDeclarations)
129
+ bun run build # dist + .d.mts via tsdown (for Node/bundler consumers)
130
+ bun run docs:api # generate the typedoc API reference
66
131
  ```
67
132
 
68
- CI runs both on every PR. MIT licensed.
133
+ CI runs test + typecheck on every PR. MIT licensed.
@@ -1,2 +1,2 @@
1
- import { a as readClaudeInput, i as guard, n as denyResponse, o as toClaudeResponse, r as fileSizeGuard, t as contextResponse } from "../../claude-B3aJwu6O.mjs";
1
+ import { a as readClaudeInput, i as guard, n as denyResponse, o as toClaudeResponse, r as fileSizeGuard, t as contextResponse } from "../../claude-BoZuNxXl.mjs";
2
2
  export { contextResponse, denyResponse, fileSizeGuard, guard, readClaudeInput, toClaudeResponse };
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "../../evaluate-DkTgwHNh.mjs";
1
+ import { t as evaluate } from "../../evaluate-qGG2Ul0W.mjs";
2
2
  import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
3
  //#region src/adapters/cline/index.ts
4
4
  /**
@@ -1,2 +1,2 @@
1
- import { a as readClaudeInput, i as guard, n as denyResponse, t as contextResponse } from "../../claude-B3aJwu6O.mjs";
1
+ import { a as readClaudeInput, i as guard, n as denyResponse, t as contextResponse } from "../../claude-BoZuNxXl.mjs";
2
2
  export { contextResponse, denyResponse, guard, readClaudeInput as readCodexInput };
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "../../evaluate-DkTgwHNh.mjs";
1
+ import { t as evaluate } from "../../evaluate-qGG2Ul0W.mjs";
2
2
  import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
3
  //#region src/adapters/cursor/index.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "../../evaluate-DkTgwHNh.mjs";
1
+ import { t as evaluate } from "../../evaluate-qGG2Ul0W.mjs";
2
2
  import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
3
  //#region src/adapters/gemini/index.ts
4
4
  /**
@@ -1,3 +1,3 @@
1
1
  import { a as queryHash, i as jaccardSimilar, n as summarizeIndex, r as compactMarkdown, t as loadIndex } from "../cache-BzbX-ztL.mjs";
2
- import { a as extractText, i as mcpCacheKey, n as cachePath, r as cacheStore, t as cacheLookup } from "../store-BlaEhjab.mjs";
2
+ import { a as extractText, i as mcpCacheKey, n as cachePath, r as cacheStore, t as cacheLookup } from "../store-DeIsfMg5.mjs";
3
3
  export { cacheLookup, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, queryHash, summarizeIndex };
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "./evaluate-DkTgwHNh.mjs";
1
+ import { t as evaluate } from "./evaluate-qGG2Ul0W.mjs";
2
2
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
3
3
  //#region src/adapters/claude/index.ts
4
4
  /**
package/dist/cli/bin.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as detectHarness } from "../harness-C8Nxxyn_.mjs";
3
- import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-D2na7Z2Z.mjs";
4
- import { n as writeInitFile, t as initFor } from "../run-Cdp2Ef9B.mjs";
5
- import { t as handleHook } from "../handle-o5np-3T_.mjs";
3
+ import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-B-_9dPR6.mjs";
4
+ import { n as writeInitFile, t as initFor } from "../run-D91N4ul1.mjs";
5
+ import { t as handleHook } from "../handle-D-DClzB2.mjs";
6
6
  //#region src/cli/bin.ts
7
7
  /**
8
8
  * harness — CLI for @fusengine/harness.
@@ -1,2 +1,2 @@
1
- import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-D2na7Z2Z.mjs";
1
+ import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-B-_9dPR6.mjs";
2
2
  export { checkStaged, stagedContent, stagedFiles };
@@ -1,2 +1,2 @@
1
- import { a as DEFAULT_TTL_SEC, c as ttlLabel, i as splitTarget, l as parseEnvInt, n as MAX_LINES_ENV_KEY, o as TTL_ENV_KEY, r as resolveMaxLines, s as resolveTtlSec, t as DEFAULT_MAX_LINES } from "../index-B-z0CCiU.mjs";
2
- export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, TTL_ENV_KEY, parseEnvInt, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
1
+ import { a as DEFAULT_MAX_LINES, c as splitTarget, d as resolveTtlSec, f as ttlLabel, i as projectLayout, l as DEFAULT_TTL_SEC, n as STATE_GITIGNORE, o as MAX_LINES_ENV_KEY, p as parseEnvInt, r as STATE_ROOT, s as resolveMaxLines, t as ProjectLayout, u as TTL_ENV_KEY } from "../index-C8dk1Alr.mjs";
2
+ export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, ProjectLayout, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
@@ -1,3 +1,4 @@
1
1
  import { a as parseEnvInt, i as splitTarget, n as MAX_LINES_ENV_KEY, r as resolveMaxLines, t as DEFAULT_MAX_LINES } from "../limits-CHn8AIL1.mjs";
2
2
  import { i as ttlLabel, n as TTL_ENV_KEY, r as resolveTtlSec, t as DEFAULT_TTL_SEC } from "../config-BG55s6HZ.mjs";
3
- export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, TTL_ENV_KEY, parseEnvInt, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
3
+ import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "../layout-C0jaaCQC.mjs";
4
+ export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
@@ -1,5 +1,5 @@
1
1
  import { i as splitTarget, r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
2
- import { t as isCodeFile } from "./project-root-C4ks_q1G.mjs";
2
+ import { t as isCodeFile } from "./project-root-ff0_poWU.mjs";
3
3
  //#region src/policy/detect-framework.ts
4
4
  /**
5
5
  * Detect the framework from a file path extension + content patterns.
@@ -1,3 +1,3 @@
1
1
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "../doc-helpers-Dd_x1-tZ.mjs";
2
- import { t as incrementTrivialEditCounter } from "../freshness-BK9Xg6oG.mjs";
2
+ import { t as incrementTrivialEditCounter } from "../freshness-CezohJHo.mjs";
3
3
  export { formatDocDeny, formatDocSatisfactionStatus, incrementTrivialEditCounter, isDocConsulted, resolveSessions };
@@ -1,4 +1,4 @@
1
- import { n as readJsonFile, r as writeJsonFile, t as ensureDir } from "./json-io-RH82El2J.mjs";
1
+ import { n as readJsonFile, r as writeJsonFile, t as ensureDir } from "./json-io-xpTDuvtn.mjs";
2
2
  import { dirname } from "node:path";
3
3
  //#region src/freshness/trivial-edit-counter.ts
4
4
  /**
@@ -1,9 +1,10 @@
1
- import { O as detectFramework, t as evaluate } from "./evaluate-DkTgwHNh.mjs";
1
+ import { r as projectLayout } from "./layout-C0jaaCQC.mjs";
2
+ import { O as detectFramework, t as evaluate } from "./evaluate-qGG2Ul0W.mjs";
2
3
  import { n as capVerbosity, o as evaluateApex } from "./verbosity-ZYT7tLCw.mjs";
3
4
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
4
- import { a as extractText, r as cacheStore, t as cacheLookup } from "./store-BlaEhjab.mjs";
5
+ import { a as extractText, r as cacheStore, t as cacheLookup } from "./store-DeIsfMg5.mjs";
5
6
  import { t as loadRefs } from "./loader-BephwI8n.mjs";
6
- import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-gtbP-Bv9.mjs";
7
+ import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-BnHpq2ZB.mjs";
7
8
  import { join } from "node:path";
8
9
  import { tmpdir } from "node:os";
9
10
  //#region src/runtime/activity.ts
@@ -229,24 +230,6 @@ function respond(id, prompt) {
229
230
  }
230
231
  }
231
232
  //#endregion
232
- //#region src/runtime/storage.ts
233
- /** Per-harness config dir (relative to the project root) where state lives. */
234
- const STATE_DIR = {
235
- "claude-code": ".claude",
236
- codex: ".codex",
237
- cursor: ".cursor",
238
- "gemini-cli": ".gemini",
239
- cline: ".clinerules"
240
- };
241
- /**
242
- * Directory where a harness's fuse-harness track files live — under that
243
- * harness's own config dir (`.claude`, `.codex`, …) so state sits next to its
244
- * hooks. Falls back to `.fuse-harness` for harnesses without a known config dir.
245
- */
246
- function harnessTrackDir(id, projectRoot) {
247
- return join(projectRoot, STATE_DIR[id] ?? ".fuse-harness", "harness");
248
- }
249
- //#endregion
250
233
  //#region src/runtime/handle.ts
251
234
  /**
252
235
  * The full hook handler: on a PRE event it gates the tool-use (stateless guards
@@ -256,9 +239,9 @@ function harnessTrackDir(id, projectRoot) {
256
239
  */
257
240
  async function handleHook(id, payload, opts) {
258
241
  const event = normalizeEvent(id, payload);
259
- const dir = harnessTrackDir(id, opts.cwd);
260
- const file = trackFile(event.sessionId, dir);
261
- const mcpDir = join(dir, "mcp");
242
+ const layout = projectLayout(opts.cwd);
243
+ const file = trackFile(event.sessionId, layout.trackDir);
244
+ const mcpDir = layout.cacheDir;
262
245
  const framework = detectFramework(event.filePath ?? "", event.content ?? "");
263
246
  if (event.phase === "post") {
264
247
  mcpPostStore(event.tool, event.input, payload.tool_response ?? payload.tool_output, mcpDir);
@@ -301,4 +284,4 @@ async function handleHook(id, payload, opts) {
301
284
  };
302
285
  }
303
286
  //#endregion
304
- export { trackFile as a, isMcpTool as c, queryOf as d, DEFAULT_WINDOW_MS as f, activityFor as g, gate as h, recordActivity as i, mcpPostStore as l, TRIVIAL_BUDGET as m, harnessTrackDir as n, normalizeEvent as o, REQUIRED_AGENTS as p, respond as r, MCP_TTL_MS as s, handleHook as t, mcpPreIntercept as u };
287
+ export { normalizeEvent as a, mcpPostStore as c, DEFAULT_WINDOW_MS as d, REQUIRED_AGENTS as f, activityFor as h, trackFile as i, mcpPreIntercept as l, gate as m, respond as n, MCP_TTL_MS as o, TRIVIAL_BUDGET as p, recordActivity as r, isMcpTool as s, handleHook as t, queryOf as u };
@@ -0,0 +1,70 @@
1
+ //#region src/config/env.d.ts
2
+ /**
3
+ * Robust integer-from-env parser.
4
+ * undefined / empty / whitespace / NaN / float / <= 0 all fall back to `fallback`.
5
+ * `Number("")` is 0, so the empty guard is required before `Number()`.
6
+ */
7
+ declare function parseEnvInt(raw: string | undefined, fallback: number): number;
8
+ //#endregion
9
+ //#region src/config/ttl.d.ts
10
+ /** Default enforcement-freshness window, in seconds (2 minutes). */
11
+ declare const DEFAULT_TTL_SEC = 120;
12
+ /** Default env var name carrying the TTL override. */
13
+ declare const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
14
+ /**
15
+ * Resolve the enforcement TTL (seconds) from an env map.
16
+ * @param env - environment map (defaults to `process.env`)
17
+ * @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
18
+ */
19
+ declare function resolveTtlSec(env?: Record<string, string | undefined>, key?: string): number;
20
+ /** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
21
+ declare function ttlLabel(sec: number): string;
22
+ //#endregion
23
+ //#region src/config/limits.d.ts
24
+ /** Default SOLID max lines per file. */
25
+ declare const DEFAULT_MAX_LINES = 100;
26
+ /** Default env var name carrying the max-lines override. */
27
+ declare const MAX_LINES_ENV_KEY = "FUSE_SOLID_MAX_LINES";
28
+ /**
29
+ * Resolve the SOLID max-lines limit from an env map.
30
+ * @param env - environment map (defaults to `process.env`)
31
+ * @param key - env var name (defaults to `FUSE_SOLID_MAX_LINES`)
32
+ */
33
+ declare function resolveMaxLines(env?: Record<string, string | undefined>, key?: string): number;
34
+ /** Advisory module-split headroom = `maxLines - 10` (never below 1). */
35
+ declare function splitTarget(maxLines: number): number;
36
+ //#endregion
37
+ //#region src/config/layout.d.ts
38
+ /** Top-level project state dir — neutral + harness-agnostic (not `.claude/…`). */
39
+ declare const STATE_ROOT = ".harness";
40
+ /**
41
+ * The selective `.gitignore` for `.harness/`: ignore the machine-local session
42
+ * state (track, cache, throttle) but keep the curated `memory/LESSON.md`.
43
+ */
44
+ declare const STATE_GITIGNORE = "track/\ncache/\nmemory/state.json\n";
45
+ /** Every project-state path the package uses, derived from one project root. */
46
+ interface ProjectLayout {
47
+ root: string;
48
+ /** `<root>/.harness` — the single state dir. */
49
+ stateDir: string;
50
+ /** `<root>/.harness/track` — session track files. */
51
+ trackDir: string;
52
+ /** `<root>/.harness/cache` — MCP/WebFetch cache. */
53
+ cacheDir: string;
54
+ /** `<root>/.harness/memory` — lessons + throttle. */
55
+ memoryDir: string;
56
+ /** `<root>/.harness/memory/LESSON.md` — curated, committable. */
57
+ lessonsFile: string;
58
+ /** `<root>/.harness/memory/state.json` — machine-local throttle. */
59
+ memoryStateFile: string;
60
+ /** `<root>/.harness/.gitignore`. */
61
+ gitignoreFile: string;
62
+ }
63
+ /**
64
+ * The single source of truth for where every package file lives under a project
65
+ * root. All modules (runtime storage, memory, init) derive their paths here —
66
+ * nothing hardcodes a directory name elsewhere.
67
+ */
68
+ declare function projectLayout(root: string): ProjectLayout;
69
+ //#endregion
70
+ export { DEFAULT_MAX_LINES as a, splitTarget as c, resolveTtlSec as d, ttlLabel as f, projectLayout as i, DEFAULT_TTL_SEC as l, STATE_GITIGNORE as n, MAX_LINES_ENV_KEY as o, parseEnvInt as p, STATE_ROOT as r, resolveMaxLines as s, ProjectLayout as t, TTL_ENV_KEY as u };
@@ -6,6 +6,8 @@ interface ReminderState {
6
6
  }
7
7
  /** Absolute path of the per-project throttle state file. */
8
8
  declare function stateFileFor(root: string): string;
9
+ /** Absolute path of the per-project curated lessons file (committable). */
10
+ declare function lessonsFileFor(root: string): string;
9
11
  /** Read both throttle timestamps; missing/corrupt fields default to 0. */
10
12
  declare function readState(file: string): ReminderState;
11
13
  /** Persist one field without clobbering the other (read-modify-write). */
@@ -27,4 +29,4 @@ declare function addRoot(root: string, home?: string): void;
27
29
  /** Ensure `<memoryDir>/.gitignore` ignores the machine-local `state.json`. */
28
30
  declare function ensureMemoryGitignore(memoryDir: string): void;
29
31
  //#endregion
30
- export { ReminderState as a, setStateField as c, registryFile as i, stateFileFor as l, addRoot as n, nowStamp as o, readRoots as r, readState as s, ensureMemoryGitignore as t, throttleMs as u };
32
+ export { ReminderState as a, readState as c, throttleMs as d, registryFile as i, setStateField as l, addRoot as n, lessonsFileFor as o, readRoots as r, nowStamp as s, ensureMemoryGitignore as t, stateFileFor as u };
package/dist/index.d.mts CHANGED
@@ -1,14 +1,14 @@
1
1
  import { n as PromptKind, r as formatPrompt, t as Prompt } from "./types-D56jSgD9.mjs";
2
2
  import { a as extractText, c as summarizeIndex, d as queryHash, i as mcpCacheKey, l as compactMarkdown, n as cachePath, o as IndexSummary, r as cacheStore, s as loadIndex, t as cacheLookup, u as jaccardSimilar } from "./index-DPkCX_AR.mjs";
3
- import { a as DEFAULT_TTL_SEC, c as ttlLabel, i as splitTarget, l as parseEnvInt, n as MAX_LINES_ENV_KEY, o as TTL_ENV_KEY, r as resolveMaxLines, s as resolveTtlSec, t as DEFAULT_MAX_LINES } from "./index-B-z0CCiU.mjs";
3
+ import { a as DEFAULT_MAX_LINES, c as splitTarget, d as resolveTtlSec, f as ttlLabel, i as projectLayout, l as DEFAULT_TTL_SEC, n as STATE_GITIGNORE, o as MAX_LINES_ENV_KEY, p as parseEnvInt, r as STATE_ROOT, s as resolveMaxLines, t as ProjectLayout, u as TTL_ENV_KEY } from "./index-C8dk1Alr.mjs";
4
4
  import { a as detectHarness, i as HarnessVia, n as HarnessInfo, o as detectMode, r as HarnessMode, s as modeFor, t as HarnessId } from "./harness-DwJskkz_.mjs";
5
5
  import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "./doc-helpers-CG1nuf-c.mjs";
6
6
  import { t as incrementTrivialEditCounter } from "./index-BOBXQ91y.mjs";
7
7
  import { i as compactJson, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./index-C1vLIMwN.mjs";
8
8
  import { A as evaluateApex, B as matchPatterns, C as Guard, D as ApexGate, E as ApexContext, F as evaluate, G as DEV_KEYWORDS, H as countLines, I as GIT_ASK, J as isApexCommand, K as ProjectType, L as GIT_BLOCKED, M as solidReadGate, N as PolicyContext, O as brainstormGate, P as PolicyResult, R as PROJECT_INSTALL, S as securityGuard, T as APEX_GATES, U as evaluateFileSize, V as FileSizeVerdict, W as detectFramework, _ as bashWriteGuard, a as runGuards, b as ASK_PATTERNS, c as installGuard, d as SWIFT_PROTO_RE, f as TS_DECL_RE, g as CODE_REDIRECT, h as CODE_MUTATORS, i as GUARDS, j as freshnessGate, k as docConsultedGate, l as PHP_DECL_RE, m as ASK_WRITERS, n as capVerbosity, o as PROJECT_INSTALL_RE, p as interfaceSeparationGuard, q as detectProjectType, r as detectCreationIntent, s as SYSTEM_INSTALL_RE, t as MAX_EXA_RESULTS, u as PY_MODEL_RE, v as PROTECTED_FRAGMENTS, w as GuardContext, x as CRITICAL_PATTERNS, y as protectedPathGuard, z as SYSTEM_INSTALL } from "./index-CQOPshSM.mjs";
9
9
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "./types-CY5qT2X1.mjs";
10
- import { a as ReminderState, c as setStateField, i as registryFile, l as stateFileFor, n as addRoot, o as nowStamp, r as readRoots, s as readState, t as ensureMemoryGitignore, u as throttleMs } from "./index-BEM-mQMC.mjs";
10
+ import { a as ReminderState, c as readState, d as throttleMs, i as registryFile, l as setStateField, n as addRoot, o as lessonsFileFor, r as readRoots, s as nowStamp, t as ensureMemoryGitignore, u as stateFileFor } from "./index-DLYhervv.mjs";
11
11
  import { a as globToRe, i as scoreReferences, n as toRefMeta, o as parseFrontmatter, r as routeReferences, t as loadRefs } from "./index-hL_r6tlc.mjs";
12
12
  import { a as taskStart, c as ensureStateDir, d as stateFilePath, f as acquireLock, i as taskCreate, l as loadState, n as ApexTaskFile, o as ApexState, r as taskComplete, s as apexStateDir, t as ApexTask, u as saveState } from "./index-CPoF_hLP.mjs";
13
13
  import { _ as TIME_INTERVALS, a as formatCost, c as formatTokens, d as colors, f as progressiveColor, g as PROGRESS_CHARS, h as PROGRESS_BAR_DEFAULTS, i as formatBasename, l as ColorFn, m as GRADIENT_BLOCKS, n as generateGradientBar, o as formatPath, p as COLOR_THRESHOLDS, r as generateProgressBar, s as formatTimeLeft, t as ProgressBarOptions, u as Palette } from "./index-BWK8slRi.mjs";
14
- export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, ApexContext, ApexGate, ApexState, ApexTask, ApexTaskFile, AuthEntry, CODE_MUTATORS, CODE_REDIRECT, COLOR_THRESHOLDS, CRITICAL_PATTERNS, ColorFn, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, DocSatisfactionStatus, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, GUARDS, Guard, GuardContext, HarnessId, HarnessInfo, HarnessMode, HarnessVia, IndexSummary, MAX_EXA_RESULTS, MAX_LINES_ENV_KEY, PHP_DECL_RE, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, PROJECT_INSTALL_RE, PROTECTED_FRAGMENTS, PY_MODEL_RE, Palette, PolicyContext, PolicyResult, ProgressBarOptions, ProjectType, Prompt, PromptKind, RefMeta, ReminderState, RouteResult, SWIFT_PROTO_RE, SYSTEM_INSTALL, SYSTEM_INSTALL_RE, ScoredRef, TIME_INTERVALS, TS_DECL_RE, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, bashWriteGuard, brainstormGate, cacheLookup, cachePath, cacheStore, capVerbosity, colors, compactJson, compactMarkdown, countLines, detectCreationIntent, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, installGuard, interfaceSeparationGuard, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadRefs, loadState, matchPatterns, mcpCacheKey, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, protectedPathGuard, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, runGuards, saveState, scoreReferences, securityGuard, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
14
+ export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, ApexContext, ApexGate, ApexState, ApexTask, ApexTaskFile, AuthEntry, CODE_MUTATORS, CODE_REDIRECT, COLOR_THRESHOLDS, CRITICAL_PATTERNS, ColorFn, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, DocSatisfactionStatus, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, GUARDS, Guard, GuardContext, HarnessId, HarnessInfo, HarnessMode, HarnessVia, IndexSummary, MAX_EXA_RESULTS, MAX_LINES_ENV_KEY, PHP_DECL_RE, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, PROJECT_INSTALL_RE, PROTECTED_FRAGMENTS, PY_MODEL_RE, Palette, PolicyContext, PolicyResult, ProgressBarOptions, ProjectLayout, ProjectType, Prompt, PromptKind, RefMeta, ReminderState, RouteResult, STATE_GITIGNORE, STATE_ROOT, SWIFT_PROTO_RE, SYSTEM_INSTALL, SYSTEM_INSTALL_RE, ScoredRef, TIME_INTERVALS, TS_DECL_RE, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, bashWriteGuard, brainstormGate, cacheLookup, cachePath, cacheStore, capVerbosity, colors, compactJson, compactMarkdown, countLines, detectCreationIntent, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, installGuard, interfaceSeparationGuard, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, lessonsFileFor, loadIndex, loadRefs, loadState, matchPatterns, mcpCacheKey, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectLayout, projectRoot, projectRootOrNull, protectedPathGuard, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, runGuards, saveState, scoreReferences, securityGuard, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
package/dist/index.mjs CHANGED
@@ -1,19 +1,20 @@
1
1
  import { a as parseEnvInt, i as splitTarget, n as MAX_LINES_ENV_KEY, r as resolveMaxLines, t as DEFAULT_MAX_LINES } from "./limits-CHn8AIL1.mjs";
2
2
  import { i as ttlLabel, n as TTL_ENV_KEY, r as resolveTtlSec, t as DEFAULT_TTL_SEC } from "./config-BG55s6HZ.mjs";
3
+ import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "./layout-C0jaaCQC.mjs";
3
4
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
4
- import { n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-C4ks_q1G.mjs";
5
+ import { n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-ff0_poWU.mjs";
5
6
  import { n as detectMode, r as modeFor, t as detectHarness } from "./harness-C8Nxxyn_.mjs";
6
- import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "./policy-Djmqxow2.mjs";
7
- import { C as PROJECT_INSTALL, D as evaluateFileSize, E as countLines, O as detectFramework, S as GIT_BLOCKED, T as matchPatterns, _ as protectedPathGuard, a as SYSTEM_INSTALL_RE, b as securityGuard, c as PY_MODEL_RE, d as interfaceSeparationGuard, f as ASK_WRITERS, g as PROTECTED_FRAGMENTS, h as bashWriteGuard, i as PROJECT_INSTALL_RE, l as SWIFT_PROTO_RE, m as CODE_REDIRECT, n as GUARDS, o as installGuard, p as CODE_MUTATORS, r as runGuards, s as PHP_DECL_RE, t as evaluate, u as TS_DECL_RE, v as ASK_PATTERNS, w as SYSTEM_INSTALL, x as GIT_ASK, y as CRITICAL_PATTERNS } from "./evaluate-DkTgwHNh.mjs";
7
+ import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "./policy-Cpa5aWek.mjs";
8
+ import { C as PROJECT_INSTALL, D as evaluateFileSize, E as countLines, O as detectFramework, S as GIT_BLOCKED, T as matchPatterns, _ as protectedPathGuard, a as SYSTEM_INSTALL_RE, b as securityGuard, c as PY_MODEL_RE, d as interfaceSeparationGuard, f as ASK_WRITERS, g as PROTECTED_FRAGMENTS, h as bashWriteGuard, i as PROJECT_INSTALL_RE, l as SWIFT_PROTO_RE, m as CODE_REDIRECT, n as GUARDS, o as installGuard, p as CODE_MUTATORS, r as runGuards, s as PHP_DECL_RE, t as evaluate, u as TS_DECL_RE, v as ASK_PATTERNS, w as SYSTEM_INSTALL, x as GIT_ASK, y as CRITICAL_PATTERNS } from "./evaluate-qGG2Ul0W.mjs";
8
9
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-Dd_x1-tZ.mjs";
9
10
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-Dj3AfgBE.mjs";
10
11
  import { a as docConsultedGate, c as solidReadGate, i as brainstormGate, n as capVerbosity, o as evaluateApex, r as APEX_GATES, s as freshnessGate, t as MAX_EXA_RESULTS } from "./verbosity-ZYT7tLCw.mjs";
11
12
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
12
- import { a as readState, c as throttleMs, i as nowStamp, l as ensureMemoryGitignore, n as readRoots, o as setStateField, r as registryFile, s as stateFileFor, t as addRoot } from "./memory-BVNt4Ary.mjs";
13
+ import { a as nowStamp, c as stateFileFor, i as lessonsFileFor, l as throttleMs, n as readRoots, o as readState, r as registryFile, s as setStateField, t as addRoot, u as ensureMemoryGitignore } from "./memory-BkoEbdec.mjs";
13
14
  import { a as queryHash, i as jaccardSimilar, n as summarizeIndex, r as compactMarkdown, t as loadIndex } from "./cache-BzbX-ztL.mjs";
14
- import { a as extractText, i as mcpCacheKey, n as cachePath, r as cacheStore, t as cacheLookup } from "./store-BlaEhjab.mjs";
15
- import { t as incrementTrivialEditCounter } from "./freshness-BK9Xg6oG.mjs";
15
+ import { a as extractText, i as mcpCacheKey, n as cachePath, r as cacheStore, t as cacheLookup } from "./store-DeIsfMg5.mjs";
16
+ import { t as incrementTrivialEditCounter } from "./freshness-CezohJHo.mjs";
16
17
  import { n as toRefMeta, t as loadRefs } from "./loader-BephwI8n.mjs";
17
- import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "./state-Bc4wdnCG.mjs";
18
+ import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "./state-Cs0Y0MG_.mjs";
18
19
  import { a as formatPath, c as colors, d as GRADIENT_BLOCKS, f as PROGRESS_BAR_DEFAULTS, i as formatCost, l as progressiveColor, m as TIME_INTERVALS, n as generateProgressBar, o as formatTimeLeft, p as PROGRESS_CHARS, r as formatBasename, s as formatTokens, t as generateGradientBar, u as COLOR_THRESHOLDS } from "./statusline-D87eUNXl.mjs";
19
- export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, COLOR_THRESHOLDS, CRITICAL_PATTERNS, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, GUARDS, MAX_EXA_RESULTS, MAX_LINES_ENV_KEY, PHP_DECL_RE, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, PROJECT_INSTALL_RE, PROTECTED_FRAGMENTS, PY_MODEL_RE, SWIFT_PROTO_RE, SYSTEM_INSTALL, SYSTEM_INSTALL_RE, TIME_INTERVALS, TS_DECL_RE, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, bashWriteGuard, brainstormGate, cacheLookup, cachePath, cacheStore, capVerbosity, colors, compactJson, compactMarkdown, countLines, detectCreationIntent, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, installGuard, interfaceSeparationGuard, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadRefs, loadState, matchPatterns, mcpCacheKey, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, protectedPathGuard, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, runGuards, saveState, scoreReferences, securityGuard, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
20
+ export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, COLOR_THRESHOLDS, CRITICAL_PATTERNS, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, GUARDS, MAX_EXA_RESULTS, MAX_LINES_ENV_KEY, PHP_DECL_RE, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, PROJECT_INSTALL_RE, PROTECTED_FRAGMENTS, PY_MODEL_RE, STATE_GITIGNORE, STATE_ROOT, SWIFT_PROTO_RE, SYSTEM_INSTALL, SYSTEM_INSTALL_RE, TIME_INTERVALS, TS_DECL_RE, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, bashWriteGuard, brainstormGate, cacheLookup, cachePath, cacheStore, capVerbosity, colors, compactJson, compactMarkdown, countLines, detectCreationIntent, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, installGuard, interfaceSeparationGuard, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, lessonsFileFor, loadIndex, loadRefs, loadState, matchPatterns, mcpCacheKey, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectLayout, projectRoot, projectRootOrNull, protectedPathGuard, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, runGuards, saveState, scoreReferences, securityGuard, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
@@ -1,2 +1,2 @@
1
- import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-Cdp2Ef9B.mjs";
1
+ import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-D91N4ul1.mjs";
2
2
  export { claudeInit, clineInit, codexInit, cursorInit, geminiInit, initFor, writeInitFile };
@@ -0,0 +1,30 @@
1
+ import { join } from "node:path";
2
+ //#region src/config/layout.ts
3
+ /** Top-level project state dir — neutral + harness-agnostic (not `.claude/…`). */
4
+ const STATE_ROOT = ".harness";
5
+ /**
6
+ * The selective `.gitignore` for `.harness/`: ignore the machine-local session
7
+ * state (track, cache, throttle) but keep the curated `memory/LESSON.md`.
8
+ */
9
+ const STATE_GITIGNORE = "track/\ncache/\nmemory/state.json\n";
10
+ /**
11
+ * The single source of truth for where every package file lives under a project
12
+ * root. All modules (runtime storage, memory, init) derive their paths here —
13
+ * nothing hardcodes a directory name elsewhere.
14
+ */
15
+ function projectLayout(root) {
16
+ const stateDir = join(root, STATE_ROOT);
17
+ const memoryDir = join(stateDir, "memory");
18
+ return {
19
+ root,
20
+ stateDir,
21
+ trackDir: join(stateDir, "track"),
22
+ cacheDir: join(stateDir, "cache"),
23
+ memoryDir,
24
+ lessonsFile: join(memoryDir, "LESSON.md"),
25
+ memoryStateFile: join(memoryDir, "state.json"),
26
+ gitignoreFile: join(stateDir, ".gitignore")
27
+ };
28
+ }
29
+ //#endregion
30
+ export { STATE_ROOT as n, projectLayout as r, STATE_GITIGNORE as t };
@@ -1,2 +1,2 @@
1
- import { a as ReminderState, c as setStateField, i as registryFile, l as stateFileFor, n as addRoot, o as nowStamp, r as readRoots, s as readState, t as ensureMemoryGitignore, u as throttleMs } from "../index-BEM-mQMC.mjs";
2
- export { ReminderState, addRoot, ensureMemoryGitignore, nowStamp, readRoots, readState, registryFile, setStateField, stateFileFor, throttleMs };
1
+ import { a as ReminderState, c as readState, d as throttleMs, i as registryFile, l as setStateField, n as addRoot, o as lessonsFileFor, r as readRoots, s as nowStamp, t as ensureMemoryGitignore, u as stateFileFor } from "../index-DLYhervv.mjs";
2
+ export { ReminderState, addRoot, ensureMemoryGitignore, lessonsFileFor, nowStamp, readRoots, readState, registryFile, setStateField, stateFileFor, throttleMs };
@@ -1,2 +1,2 @@
1
- import { a as readState, c as throttleMs, i as nowStamp, l as ensureMemoryGitignore, n as readRoots, o as setStateField, r as registryFile, s as stateFileFor, t as addRoot } from "../memory-BVNt4Ary.mjs";
2
- export { addRoot, ensureMemoryGitignore, nowStamp, readRoots, readState, registryFile, setStateField, stateFileFor, throttleMs };
1
+ import { a as nowStamp, c as stateFileFor, i as lessonsFileFor, l as throttleMs, n as readRoots, o as readState, r as registryFile, s as setStateField, t as addRoot, u as ensureMemoryGitignore } from "../memory-BkoEbdec.mjs";
2
+ export { addRoot, ensureMemoryGitignore, lessonsFileFor, nowStamp, readRoots, readState, registryFile, setStateField, stateFileFor, throttleMs };
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { r as projectLayout } from "./layout-C0jaaCQC.mjs";
2
2
  import { dirname } from "node:path";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
4
  //#region src/memory/gitignore.ts
4
5
  /** Ensure `<memoryDir>/.gitignore` ignores the machine-local `state.json`. */
5
6
  function ensureMemoryGitignore(memoryDir) {
@@ -14,7 +15,11 @@ function ensureMemoryGitignore(memoryDir) {
14
15
  //#region src/memory/state.ts
15
16
  /** Absolute path of the per-project throttle state file. */
16
17
  function stateFileFor(root) {
17
- return `${root}/MEMORY/state.json`;
18
+ return projectLayout(root).memoryStateFile;
19
+ }
20
+ /** Absolute path of the per-project curated lessons file (committable). */
21
+ function lessonsFileFor(root) {
22
+ return projectLayout(root).lessonsFile;
18
23
  }
19
24
  function num(v) {
20
25
  return typeof v === "number" && Number.isFinite(v) ? v : 0;
@@ -91,4 +96,4 @@ function addRoot(root, home) {
91
96
  } catch {}
92
97
  }
93
98
  //#endregion
94
- export { readState as a, throttleMs as c, nowStamp as i, ensureMemoryGitignore as l, readRoots as n, setStateField as o, registryFile as r, stateFileFor as s, addRoot as t };
99
+ export { nowStamp as a, stateFileFor as c, lessonsFileFor as i, throttleMs as l, readRoots as n, readState as o, registryFile as r, setStateField as s, addRoot as t, ensureMemoryGitignore as u };
@@ -1,4 +1,4 @@
1
- import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "../policy-Djmqxow2.mjs";
2
- import { C as PROJECT_INSTALL, D as evaluateFileSize, E as countLines, O as detectFramework, S as GIT_BLOCKED, T as matchPatterns, _ as protectedPathGuard, a as SYSTEM_INSTALL_RE, b as securityGuard, c as PY_MODEL_RE, d as interfaceSeparationGuard, f as ASK_WRITERS, g as PROTECTED_FRAGMENTS, h as bashWriteGuard, i as PROJECT_INSTALL_RE, l as SWIFT_PROTO_RE, m as CODE_REDIRECT, n as GUARDS, o as installGuard, p as CODE_MUTATORS, r as runGuards, s as PHP_DECL_RE, t as evaluate, u as TS_DECL_RE, v as ASK_PATTERNS, w as SYSTEM_INSTALL, x as GIT_ASK, y as CRITICAL_PATTERNS } from "../evaluate-DkTgwHNh.mjs";
1
+ import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "../policy-Cpa5aWek.mjs";
2
+ import { C as PROJECT_INSTALL, D as evaluateFileSize, E as countLines, O as detectFramework, S as GIT_BLOCKED, T as matchPatterns, _ as protectedPathGuard, a as SYSTEM_INSTALL_RE, b as securityGuard, c as PY_MODEL_RE, d as interfaceSeparationGuard, f as ASK_WRITERS, g as PROTECTED_FRAGMENTS, h as bashWriteGuard, i as PROJECT_INSTALL_RE, l as SWIFT_PROTO_RE, m as CODE_REDIRECT, n as GUARDS, o as installGuard, p as CODE_MUTATORS, r as runGuards, s as PHP_DECL_RE, t as evaluate, u as TS_DECL_RE, v as ASK_PATTERNS, w as SYSTEM_INSTALL, x as GIT_ASK, y as CRITICAL_PATTERNS } from "../evaluate-qGG2Ul0W.mjs";
3
3
  import { a as docConsultedGate, c as solidReadGate, i as brainstormGate, n as capVerbosity, o as evaluateApex, r as APEX_GATES, s as freshnessGate, t as MAX_EXA_RESULTS } from "../verbosity-ZYT7tLCw.mjs";
4
4
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GUARDS, MAX_EXA_RESULTS, PHP_DECL_RE, PROJECT_INSTALL, PROJECT_INSTALL_RE, PROTECTED_FRAGMENTS, PY_MODEL_RE, SWIFT_PROTO_RE, SYSTEM_INSTALL, SYSTEM_INSTALL_RE, TS_DECL_RE, bashWriteGuard, brainstormGate, capVerbosity, countLines, detectCreationIntent, detectFramework, detectProjectType, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, matchPatterns, protectedPathGuard, runGuards, securityGuard, solidReadGate };
@@ -1,5 +1,5 @@
1
- import { t as isCodeFile } from "./project-root-C4ks_q1G.mjs";
2
- import { t as evaluate } from "./evaluate-DkTgwHNh.mjs";
1
+ import { t as isCodeFile } from "./project-root-ff0_poWU.mjs";
2
+ import { t as evaluate } from "./evaluate-qGG2Ul0W.mjs";
3
3
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
4
4
  import { execSync } from "node:child_process";
5
5
  //#region src/cli/run.ts
@@ -1,5 +1,6 @@
1
- import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { n as STATE_ROOT, t as STATE_GITIGNORE } from "./layout-C0jaaCQC.mjs";
2
2
  import { dirname, join } from "node:path";
3
+ import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
3
4
  //#region src/init/templates.ts
4
5
  function json(obj) {
5
6
  return `${JSON.stringify(obj, null, 2)}\n`;
@@ -114,7 +115,11 @@ const RUNNERS = {
114
115
  */
115
116
  function initFor(id, command = `npx harness hook ${id}`) {
116
117
  const make = RUNNERS[id];
117
- return make ? make(command) : null;
118
+ if (!make) return null;
119
+ return [...make(command), {
120
+ path: `${STATE_ROOT}/.gitignore`,
121
+ content: STATE_GITIGNORE
122
+ }];
118
123
  }
119
124
  /** Write an {@link InitFile} under `root` (creates dirs; `chmod +x` when executable). */
120
125
  function writeInitFile(root, file) {
@@ -1,5 +1,4 @@
1
1
  import { t as Prompt } from "../types-D56jSgD9.mjs";
2
- import { t as HarnessId } from "../harness-DwJskkz_.mjs";
3
2
  import { t as RefMeta } from "../types-CY5qT2X1.mjs";
4
3
 
5
4
  //#region src/runtime/paths.d.ts
@@ -8,11 +7,10 @@ declare function trackFile(sessionId: string, baseDir?: string): string;
8
7
  //#endregion
9
8
  //#region src/runtime/storage.d.ts
10
9
  /**
11
- * Directory where a harness's fuse-harness track files liveunder that
12
- * harness's own config dir (`.claude`, `.codex`, …) so state sits next to its
13
- * hooks. Falls back to `.fuse-harness` for harnesses without a known config dir.
10
+ * The project's single state dir (`<root>/.harness`)neutral + harness-agnostic,
11
+ * derived from {@link projectLayout}. (Was the per-harness `.claude/harness`.)
14
12
  */
15
- declare function harnessTrackDir(id: HarnessId, projectRoot: string): string;
13
+ declare function harnessStateDir(root: string): string;
16
14
  //#endregion
17
15
  //#region src/runtime/record.d.ts
18
16
  /** A unit of session activity to record (discriminated union on `kind`). */
@@ -142,4 +140,4 @@ interface HandleOutcome {
142
140
  */
143
141
  declare function handleHook(id: string, payload: Record<string, unknown>, opts: HandleOptions): Promise<HandleOutcome>;
144
142
  //#endregion
145
- export { Activity, DEFAULT_WINDOW_MS, GateInput, HandleOptions, HandleOutcome, MCP_TTL_MS, NormalizedEvent, REQUIRED_AGENTS, TRIVIAL_BUDGET, ToolEvent, activityFor, gate, handleHook, harnessTrackDir, isMcpTool, mcpPostStore, mcpPreIntercept, normalizeEvent, queryOf, recordActivity, respond, trackFile };
143
+ export { Activity, DEFAULT_WINDOW_MS, GateInput, HandleOptions, HandleOutcome, MCP_TTL_MS, NormalizedEvent, REQUIRED_AGENTS, TRIVIAL_BUDGET, ToolEvent, activityFor, gate, handleHook, harnessStateDir, isMcpTool, mcpPostStore, mcpPreIntercept, normalizeEvent, queryOf, recordActivity, respond, trackFile };
@@ -1,2 +1,12 @@
1
- import { a as trackFile, c as isMcpTool, d as queryOf, f as DEFAULT_WINDOW_MS, g as activityFor, h as gate, i as recordActivity, l as mcpPostStore, m as TRIVIAL_BUDGET, n as harnessTrackDir, o as normalizeEvent, p as REQUIRED_AGENTS, r as respond, s as MCP_TTL_MS, t as handleHook, u as mcpPreIntercept } from "../handle-o5np-3T_.mjs";
2
- export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, activityFor, gate, handleHook, harnessTrackDir, isMcpTool, mcpPostStore, mcpPreIntercept, normalizeEvent, queryOf, recordActivity, respond, trackFile };
1
+ import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
2
+ import { a as normalizeEvent, c as mcpPostStore, d as DEFAULT_WINDOW_MS, f as REQUIRED_AGENTS, h as activityFor, i as trackFile, l as mcpPreIntercept, m as gate, n as respond, o as MCP_TTL_MS, p as TRIVIAL_BUDGET, r as recordActivity, s as isMcpTool, t as handleHook, u as queryOf } from "../handle-D-DClzB2.mjs";
3
+ //#region src/runtime/storage.ts
4
+ /**
5
+ * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
6
+ * derived from {@link projectLayout}. (Was the per-harness `.claude/harness`.)
7
+ */
8
+ function harnessStateDir(root) {
9
+ return projectLayout(root).stateDir;
10
+ }
11
+ //#endregion
12
+ export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, activityFor, gate, handleHook, harnessStateDir, isMcpTool, mcpPostStore, mcpPreIntercept, normalizeEvent, queryOf, recordActivity, respond, trackFile };
@@ -1,2 +1,2 @@
1
- import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "../state-Bc4wdnCG.mjs";
1
+ import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "../state-Cs0Y0MG_.mjs";
2
2
  export { acquireLock, apexStateDir, ensureStateDir, loadState, saveState, stateFilePath, taskComplete, taskCreate, taskStart };
@@ -1,4 +1,4 @@
1
- import { n as readJsonFile, r as writeJsonFile, t as ensureDir } from "./json-io-RH82El2J.mjs";
1
+ import { n as readJsonFile, r as writeJsonFile, t as ensureDir } from "./json-io-xpTDuvtn.mjs";
2
2
  import { mkdir, rmdir } from "node:fs/promises";
3
3
  //#region src/state/lock.ts
4
4
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -1,4 +1,4 @@
1
- import { n as readJsonFile, r as writeJsonFile } from "./json-io-RH82El2J.mjs";
1
+ import { n as readJsonFile, r as writeJsonFile } from "./json-io-xpTDuvtn.mjs";
2
2
  //#region src/tracking/session-state.ts
3
3
  /** A fresh, empty track. */
4
4
  function emptyTrack() {
@@ -1,2 +1,2 @@
1
- import { a as recordAgent, c as recordRefRead, i as emptyTrack, l as recordTrivialEdit, n as saveTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "../store-gtbP-Bv9.mjs";
1
+ import { a as recordAgent, c as recordRefRead, i as emptyTrack, l as recordTrivialEdit, n as saveTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "../store-BnHpq2ZB.mjs";
2
2
  export { agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTrivialEdit, saveTrack, trivialCount };
@@ -1,4 +1,4 @@
1
1
  import { t as compactJson } from "../compact-json-DK2nX-MK.mjs";
2
- import { n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "../project-root-C4ks_q1G.mjs";
2
+ import { n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "../project-root-ff0_poWU.mjs";
3
3
  import "../util-la_KkjCS.mjs";
4
4
  export { compactJson, isCodeFile, projectRoot, projectRootOrNull };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, per-harness adapters (Claude/Cursor/Cline/Gemini) and a cli-mode harness-check binary. Bun-native, with a built dist for Node + bundlers.",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -9,27 +9,111 @@
9
9
  "harness": "./dist/cli/bin.mjs"
10
10
  },
11
11
  "exports": {
12
- ".": { "bun": "./src/index.ts", "types": "./dist/index.d.mts", "import": "./dist/index.mjs" },
13
- "./config": { "bun": "./src/config/index.ts", "types": "./dist/config/index.d.mts", "import": "./dist/config/index.mjs" },
14
- "./util": { "bun": "./src/util/index.ts", "types": "./dist/util/index.d.mts", "import": "./dist/util/index.mjs" },
15
- "./detect": { "bun": "./src/detect/index.ts", "types": "./dist/detect/index.d.mts", "import": "./dist/detect/index.mjs" },
16
- "./policy": { "bun": "./src/policy/index.ts", "types": "./dist/policy/index.d.mts", "import": "./dist/policy/index.mjs" },
17
- "./prompt": { "bun": "./src/prompt/index.ts", "types": "./dist/prompt/index.d.mts", "import": "./dist/prompt/index.mjs" },
18
- "./memory": { "bun": "./src/memory/index.ts", "types": "./dist/memory/index.d.mts", "import": "./dist/memory/index.mjs" },
19
- "./cache": { "bun": "./src/cache/index.ts", "types": "./dist/cache/index.d.mts", "import": "./dist/cache/index.mjs" },
20
- "./freshness": { "bun": "./src/freshness/index.ts", "types": "./dist/freshness/index.d.mts", "import": "./dist/freshness/index.mjs" },
21
- "./refs": { "bun": "./src/refs/index.ts", "types": "./dist/refs/index.d.mts", "import": "./dist/refs/index.mjs" },
22
- "./state": { "bun": "./src/state/index.ts", "types": "./dist/state/index.d.mts", "import": "./dist/state/index.mjs" },
23
- "./statusline": { "bun": "./src/statusline/index.ts", "types": "./dist/statusline/index.d.mts", "import": "./dist/statusline/index.mjs" },
24
- "./cli": { "bun": "./src/cli/index.ts", "types": "./dist/cli/index.d.mts", "import": "./dist/cli/index.mjs" },
25
- "./init": { "bun": "./src/init/index.ts", "types": "./dist/init/index.d.mts", "import": "./dist/init/index.mjs" },
26
- "./tracking": { "bun": "./src/tracking/index.ts", "types": "./dist/tracking/index.d.mts", "import": "./dist/tracking/index.mjs" },
27
- "./runtime": { "bun": "./src/runtime/index.ts", "types": "./dist/runtime/index.d.mts", "import": "./dist/runtime/index.mjs" },
28
- "./adapters/claude": { "bun": "./src/adapters/claude/index.ts", "types": "./dist/adapters/claude/index.d.mts", "import": "./dist/adapters/claude/index.mjs" },
29
- "./adapters/codex": { "bun": "./src/adapters/codex/index.ts", "types": "./dist/adapters/codex/index.d.mts", "import": "./dist/adapters/codex/index.mjs" },
30
- "./adapters/cursor": { "bun": "./src/adapters/cursor/index.ts", "types": "./dist/adapters/cursor/index.d.mts", "import": "./dist/adapters/cursor/index.mjs" },
31
- "./adapters/cline": { "bun": "./src/adapters/cline/index.ts", "types": "./dist/adapters/cline/index.d.mts", "import": "./dist/adapters/cline/index.mjs" },
32
- "./adapters/gemini": { "bun": "./src/adapters/gemini/index.ts", "types": "./dist/adapters/gemini/index.d.mts", "import": "./dist/adapters/gemini/index.mjs" }
12
+ ".": {
13
+ "bun": "./src/index.ts",
14
+ "types": "./dist/index.d.mts",
15
+ "import": "./dist/index.mjs"
16
+ },
17
+ "./config": {
18
+ "bun": "./src/config/index.ts",
19
+ "types": "./dist/config/index.d.mts",
20
+ "import": "./dist/config/index.mjs"
21
+ },
22
+ "./util": {
23
+ "bun": "./src/util/index.ts",
24
+ "types": "./dist/util/index.d.mts",
25
+ "import": "./dist/util/index.mjs"
26
+ },
27
+ "./detect": {
28
+ "bun": "./src/detect/index.ts",
29
+ "types": "./dist/detect/index.d.mts",
30
+ "import": "./dist/detect/index.mjs"
31
+ },
32
+ "./policy": {
33
+ "bun": "./src/policy/index.ts",
34
+ "types": "./dist/policy/index.d.mts",
35
+ "import": "./dist/policy/index.mjs"
36
+ },
37
+ "./prompt": {
38
+ "bun": "./src/prompt/index.ts",
39
+ "types": "./dist/prompt/index.d.mts",
40
+ "import": "./dist/prompt/index.mjs"
41
+ },
42
+ "./memory": {
43
+ "bun": "./src/memory/index.ts",
44
+ "types": "./dist/memory/index.d.mts",
45
+ "import": "./dist/memory/index.mjs"
46
+ },
47
+ "./cache": {
48
+ "bun": "./src/cache/index.ts",
49
+ "types": "./dist/cache/index.d.mts",
50
+ "import": "./dist/cache/index.mjs"
51
+ },
52
+ "./freshness": {
53
+ "bun": "./src/freshness/index.ts",
54
+ "types": "./dist/freshness/index.d.mts",
55
+ "import": "./dist/freshness/index.mjs"
56
+ },
57
+ "./refs": {
58
+ "bun": "./src/refs/index.ts",
59
+ "types": "./dist/refs/index.d.mts",
60
+ "import": "./dist/refs/index.mjs"
61
+ },
62
+ "./state": {
63
+ "bun": "./src/state/index.ts",
64
+ "types": "./dist/state/index.d.mts",
65
+ "import": "./dist/state/index.mjs"
66
+ },
67
+ "./statusline": {
68
+ "bun": "./src/statusline/index.ts",
69
+ "types": "./dist/statusline/index.d.mts",
70
+ "import": "./dist/statusline/index.mjs"
71
+ },
72
+ "./cli": {
73
+ "bun": "./src/cli/index.ts",
74
+ "types": "./dist/cli/index.d.mts",
75
+ "import": "./dist/cli/index.mjs"
76
+ },
77
+ "./init": {
78
+ "bun": "./src/init/index.ts",
79
+ "types": "./dist/init/index.d.mts",
80
+ "import": "./dist/init/index.mjs"
81
+ },
82
+ "./tracking": {
83
+ "bun": "./src/tracking/index.ts",
84
+ "types": "./dist/tracking/index.d.mts",
85
+ "import": "./dist/tracking/index.mjs"
86
+ },
87
+ "./runtime": {
88
+ "bun": "./src/runtime/index.ts",
89
+ "types": "./dist/runtime/index.d.mts",
90
+ "import": "./dist/runtime/index.mjs"
91
+ },
92
+ "./adapters/claude": {
93
+ "bun": "./src/adapters/claude/index.ts",
94
+ "types": "./dist/adapters/claude/index.d.mts",
95
+ "import": "./dist/adapters/claude/index.mjs"
96
+ },
97
+ "./adapters/codex": {
98
+ "bun": "./src/adapters/codex/index.ts",
99
+ "types": "./dist/adapters/codex/index.d.mts",
100
+ "import": "./dist/adapters/codex/index.mjs"
101
+ },
102
+ "./adapters/cursor": {
103
+ "bun": "./src/adapters/cursor/index.ts",
104
+ "types": "./dist/adapters/cursor/index.d.mts",
105
+ "import": "./dist/adapters/cursor/index.mjs"
106
+ },
107
+ "./adapters/cline": {
108
+ "bun": "./src/adapters/cline/index.ts",
109
+ "types": "./dist/adapters/cline/index.d.mts",
110
+ "import": "./dist/adapters/cline/index.mjs"
111
+ },
112
+ "./adapters/gemini": {
113
+ "bun": "./src/adapters/gemini/index.ts",
114
+ "types": "./dist/adapters/gemini/index.d.mts",
115
+ "import": "./dist/adapters/gemini/index.mjs"
116
+ }
33
117
  },
34
118
  "files": ["dist", "README.md", "LICENSE"],
35
119
  "license": "MIT",
@@ -37,6 +121,7 @@
37
121
  "scripts": {
38
122
  "test": "bun test",
39
123
  "typecheck": "tsc --noEmit",
124
+ "docs:api": "typedoc",
40
125
  "build": "tsdown src/index.ts src/config/index.ts src/util/index.ts src/detect/index.ts src/policy/index.ts src/prompt/index.ts src/memory/index.ts src/cache/index.ts src/freshness/index.ts src/refs/index.ts src/state/index.ts src/statusline/index.ts src/cli/index.ts src/cli/bin.ts src/init/index.ts src/tracking/index.ts src/runtime/index.ts src/adapters/claude/index.ts src/adapters/codex/index.ts src/adapters/cursor/index.ts src/adapters/cline/index.ts src/adapters/gemini/index.ts --dts --format esm --clean --out-dir dist",
41
126
  "prepublishOnly": "bun test && tsc --noEmit && bun run build"
42
127
  },
@@ -47,11 +132,14 @@
47
132
  "@vercel/detect-agent": "*"
48
133
  },
49
134
  "peerDependenciesMeta": {
50
- "@vercel/detect-agent": { "optional": true }
135
+ "@vercel/detect-agent": {
136
+ "optional": true
137
+ }
51
138
  },
52
139
  "devDependencies": {
53
140
  "@types/bun": "latest",
54
141
  "tsdown": "^0.22.3",
142
+ "typedoc": "^0.28.19",
55
143
  "typescript": "^6.0.3"
56
144
  }
57
145
  }
@@ -1,37 +0,0 @@
1
- //#region src/config/env.d.ts
2
- /**
3
- * Robust integer-from-env parser.
4
- * undefined / empty / whitespace / NaN / float / <= 0 all fall back to `fallback`.
5
- * `Number("")` is 0, so the empty guard is required before `Number()`.
6
- */
7
- declare function parseEnvInt(raw: string | undefined, fallback: number): number;
8
- //#endregion
9
- //#region src/config/ttl.d.ts
10
- /** Default enforcement-freshness window, in seconds (2 minutes). */
11
- declare const DEFAULT_TTL_SEC = 120;
12
- /** Default env var name carrying the TTL override. */
13
- declare const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
14
- /**
15
- * Resolve the enforcement TTL (seconds) from an env map.
16
- * @param env - environment map (defaults to `process.env`)
17
- * @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
18
- */
19
- declare function resolveTtlSec(env?: Record<string, string | undefined>, key?: string): number;
20
- /** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
21
- declare function ttlLabel(sec: number): string;
22
- //#endregion
23
- //#region src/config/limits.d.ts
24
- /** Default SOLID max lines per file. */
25
- declare const DEFAULT_MAX_LINES = 100;
26
- /** Default env var name carrying the max-lines override. */
27
- declare const MAX_LINES_ENV_KEY = "FUSE_SOLID_MAX_LINES";
28
- /**
29
- * Resolve the SOLID max-lines limit from an env map.
30
- * @param env - environment map (defaults to `process.env`)
31
- * @param key - env var name (defaults to `FUSE_SOLID_MAX_LINES`)
32
- */
33
- declare function resolveMaxLines(env?: Record<string, string | undefined>, key?: string): number;
34
- /** Advisory module-split headroom = `maxLines - 10` (never below 1). */
35
- declare function splitTarget(maxLines: number): number;
36
- //#endregion
37
- export { DEFAULT_TTL_SEC as a, ttlLabel as c, splitTarget as i, parseEnvInt as l, MAX_LINES_ENV_KEY as n, TTL_ENV_KEY as o, resolveMaxLines as r, resolveTtlSec as s, DEFAULT_MAX_LINES as t };
@@ -1,6 +1,6 @@
1
1
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
3
2
  import { dirname } from "node:path";
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { mkdir } from "node:fs/promises";
6
6
  //#region src/util/json-io.ts
@@ -1,5 +1,5 @@
1
- import { existsSync } from "node:fs";
2
1
  import { join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
3
  //#region src/policy/detect-project.ts
4
4
  /** Keywords that signal a development task (APEX trigger). */
5
5
  const DEV_KEYWORDS = /\b(implement|create|build|fix|add|refactor|develop|feature|bug|update|modify|change|write|code)\b/i;
@@ -1,5 +1,5 @@
1
- import { existsSync } from "node:fs";
2
1
  import { dirname, resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
3
  //#region src/util/project-root.ts
4
4
  /** Source-code file extensions worth tracking as "code was written". */
5
5
  const CODE_EXT = /\.(ts|tsx|js|jsx|py|php|swift|go|rs|rb|java|vue|svelte|astro|css|kt|dart|cpp|c)$/;
@@ -1,5 +1,5 @@
1
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
1
  import { dirname, join } from "node:path";
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
3
  //#region src/cache/mcp-response.ts
4
4
  const MAX_DEPTH = 5;
5
5
  /**