@fusengine/harness 0.1.79 → 0.1.81

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 (76) hide show
  1. package/README.md +14 -1
  2. package/dist/adapters/claude/index.d.mts +1 -1
  3. package/dist/adapters/claude/index.mjs +1 -1
  4. package/dist/adapters/cline/index.mjs +1 -1
  5. package/dist/adapters/codex/index.d.mts +11 -12
  6. package/dist/adapters/codex/index.mjs +3 -3
  7. package/dist/adapters/cursor/index.d.mts +29 -29
  8. package/dist/adapters/cursor/index.mjs +1 -1
  9. package/dist/adapters/gemini/index.mjs +1 -1
  10. package/dist/adapters/hermes/index.d.mts +22 -23
  11. package/dist/adapters/hermes/index.mjs +1 -1
  12. package/dist/adapters/kimi/index.d.mts +87 -0
  13. package/dist/adapters/kimi/index.mjs +2 -0
  14. package/dist/cache/index.d.mts +1 -1
  15. package/dist/{claude-51RSspJ1.mjs → claude-D93HY8dq.mjs} +2 -2
  16. package/dist/cli/bin.d.mts +1 -1
  17. package/dist/cli/bin.mjs +130 -39
  18. package/dist/cli/index.d.mts +3 -3
  19. package/dist/cli/index.mjs +1 -1
  20. package/dist/config/index.d.mts +2 -2
  21. package/dist/config/index.mjs +4 -4
  22. package/dist/detect/index.d.mts +2 -1
  23. package/dist/detect/index.mjs +1 -1
  24. package/dist/{doc-helpers-CEKzGg2u.d.mts → doc-helpers-BGCzP9VF.d.mts} +12 -12
  25. package/dist/{dotenv-Jj8aL1FL.mjs → dotenv-DoqNlALG.mjs} +31 -3
  26. package/dist/{evaluate-ClfbCDiY.mjs → evaluate-B4-MhOy_.mjs} +322 -32
  27. package/dist/freshness/index.d.mts +2 -2
  28. package/dist/{handle-Dthtlp7Z.mjs → handle-J5r2f_O_.mjs} +665 -164
  29. package/dist/{harness-Cb9xR8dC.mjs → harness-BMuLJ9lm.mjs} +4 -2
  30. package/dist/{hermes-rTmVdhUL.mjs → hermes-1PUEoY8q.mjs} +2 -2
  31. package/dist/{home-state-BXf38Zi1.mjs → home-state-oUGFB4ds.mjs} +4 -2
  32. package/dist/index-84heAAm_.d.mts +12 -0
  33. package/dist/index-CaxrMD1A.d.mts +1 -0
  34. package/dist/index-CigBu4K9.d.mts +14 -0
  35. package/dist/{index-DGxNjXW7.d.mts → index-D4C2-IIc.d.mts} +6 -8
  36. package/dist/index-DBgnIZn9.d.mts +756 -0
  37. package/dist/{index-mmRF3KNp.d.mts → index-DEfz82BP.d.mts} +12 -13
  38. package/dist/{index-BUwEmIK-.d.mts → index-DhmV2MPh.d.mts} +31 -31
  39. package/dist/index-oXXPs2xl.d.mts +119 -0
  40. package/dist/index-p9fLrSOD.d.mts +27 -0
  41. package/dist/index.d.mts +11 -11
  42. package/dist/index.mjs +9 -9
  43. package/dist/init/index.d.mts +8 -9
  44. package/dist/init/index.mjs +1 -1
  45. package/dist/kimi-ChbcU2uI.mjs +94 -0
  46. package/dist/{layout-C0jaaCQC.mjs → layout-KWoE_Mqn.mjs} +1 -0
  47. package/dist/limits-DG5pl-5s.mjs +59 -0
  48. package/dist/memory/index.mjs +1 -1
  49. package/dist/policy/index.d.mts +2 -3
  50. package/dist/policy/index.mjs +4 -4
  51. package/dist/prompt/index.d.mts +2 -1
  52. package/dist/refs/index.d.mts +1 -1
  53. package/dist/{registry-CymilZiZ.mjs → registry-IhHk2KlT.mjs} +1 -1
  54. package/dist/{run-3tyTW9Ud.mjs → run-D_WchZ3R.mjs} +1 -1
  55. package/dist/{run-Do2JltgU.mjs → run-TAXNRSpD.mjs} +1 -1
  56. package/dist/runtime/index.d.mts +455 -446
  57. package/dist/runtime/index.mjs +3 -3
  58. package/dist/{session-state-CY4iohn_.d.mts → session-state-D5gLr66m.d.mts} +29 -30
  59. package/dist/{skill-path-BO2N0XvB.mjs → skill-path-DhBGlQe3.mjs} +3 -3
  60. package/dist/{store-Bcn08y9m.mjs → store-yr_QFsZY.mjs} +101 -13
  61. package/dist/tracking/index.d.mts +22 -13
  62. package/dist/tracking/index.mjs +2 -2
  63. package/dist/{types-DVbIl9md.d.mts → types-C0-igRKr.d.mts} +8 -8
  64. package/dist/types-DvXTI71X.d.mts +15 -0
  65. package/dist/util/index.d.mts +1 -1
  66. package/dist/{validate-DhOX5hDK.mjs → validate-Bf-EsbDh.mjs} +348 -32
  67. package/package.json +163 -158
  68. package/dist/apex-Wdi1nq_w.d.mts +0 -73
  69. package/dist/harness-s1Fce_dL.d.mts +0 -27
  70. package/dist/index-BEMumjOw.d.mts +0 -27
  71. package/dist/index-BIGVNQB8.d.mts +0 -576
  72. package/dist/index-BKZ67WMa.d.mts +0 -1
  73. package/dist/index-BOBXQ91y.d.mts +0 -12
  74. package/dist/index-BVVgDSdq.d.mts +0 -1
  75. package/dist/index-jj_EzZRv.d.mts +0 -96
  76. package/dist/limits-CHn8AIL1.mjs +0 -31
@@ -1,6 +1,6 @@
1
- import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
2
- import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-BXf38Zi1.mjs";
3
- import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-Dthtlp7Z.mjs";
1
+ import { r as projectLayout } from "../layout-KWoE_Mqn.mjs";
2
+ import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-oUGFB4ds.mjs";
3
+ import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-J5r2f_O_.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -1,5 +1,4 @@
1
- import { t as AuthEntry } from "./doc-helpers-CEKzGg2u.mjs";
2
-
1
+ import { t as AuthEntry } from "./doc-helpers-BGCzP9VF.mjs";
3
2
  //#region src/policy/apex-authorization.d.ts
4
3
  /** Pending doc-credit target written on a Check-1 deny (parity enforce-apex-phases.ts:80 `state.target`). */
5
4
  interface SessionTarget {
@@ -11,11 +10,11 @@ interface SessionTarget {
11
10
  //#endregion
12
11
  //#region src/tracking/receipts.d.ts
13
12
  /**
14
- * A verification receipt captured at PostToolUse from a Bash verification
15
- * command. Feeds the TaskCompleted receipt gate: a "done" over modified code
16
- * files is refused unless a fresh, passing receipt (`exitCode === 0`, `fail === 0`)
17
- * exists. `pass`/`fail` are parsed only for test runs; `tsc` carries the code alone.
18
- */
13
+ * A verification receipt captured at PostToolUse from a Bash verification
14
+ * command. Feeds the TaskCompleted receipt gate: a "done" over modified code
15
+ * files is refused unless a fresh, passing receipt (`exitCode === 0`, `fail === 0`)
16
+ * exists. `pass`/`fail` are parsed only for test runs; `tsc` carries the code alone.
17
+ */
19
18
  interface Receipt {
20
19
  kind: "tsc" | "test";
21
20
  exitCode: number;
@@ -32,13 +31,13 @@ interface SessionTrack {
32
31
  authorizations: Record<string, AuthEntry>;
33
32
  refsRead: string[];
34
33
  /**
35
- * Epoch-ms timestamp of each reference read, keyed by `refsRead` path (parity
36
- * track-solid-reads.py `solid_reads[].timestamp`). Optional: tracks persisted
37
- * before this field existed only carry `refsRead`, and such paths count as
38
- * read (backward compat). PARITY: only the SOLID-read gate TTL-checks these —
39
- * Python TTL-izes SOLID reads exclusively (require-solid-read.py); the other
40
- * refsRead consumers (skill-trigger/shadcn/tailwind/design) stay session-scoped.
41
- */
34
+ * Epoch-ms timestamp of each reference read, keyed by `refsRead` path (parity
35
+ * track-solid-reads.py `solid_reads[].timestamp`). Optional: tracks persisted
36
+ * before this field existed only carry `refsRead`, and such paths count as
37
+ * read (backward compat). PARITY: only the SOLID-read gate TTL-checks these —
38
+ * Python TTL-izes SOLID reads exclusively (require-solid-read.py); the other
39
+ * refsRead consumers (skill-trigger/shadcn/tailwind/design) stay session-scoped.
40
+ */
42
41
  refsReadAt?: Record<string, number>;
43
42
  /** Framework awaiting doc credit after a Check-1 deny. PERSISTS — {@link recordDoc} cross-credits it on EVERY consultation, never clears it; only the next deny replaces it via {@link recordTarget} (parity track-doc-consultation.py:62). */
44
43
  target?: SessionTarget;
@@ -55,26 +54,26 @@ interface SessionTrack {
55
54
  /** A fresh, empty track. */
56
55
  declare function emptyTrack(): SessionTrack;
57
56
  /**
58
- * Record a doc consultation (Context7/Exa/web) for a framework in this session:
59
- * the Check-2 credit (`doc_sessions` + `sources`) plus the Check-1 stamp
60
- * (`sessions` + `doc_consulted` = ISO of `now`). PARITY track-doc-consultation.py
61
- * :62-70 — a `target` left by a Check-1 deny (enforce-apex-phases.ts:80) is
62
- * cross-credited on EVERY consultation, with NO TTL on `target.set_at`, and the
63
- * target PERSISTS (only the next deny replaces it via {@link recordTarget});
64
- * single-shot clearing or TTL-gating the target re-opened the deny loop when a
65
- * consultation landed > TTL after the deny. Immutable.
66
- */
57
+ * Record a doc consultation (Context7/Exa/web) for a framework in this session:
58
+ * the Check-2 credit (`doc_sessions` + `sources`) plus the Check-1 stamp
59
+ * (`sessions` + `doc_consulted` = ISO of `now`). PARITY track-doc-consultation.py
60
+ * :62-70 — a `target` left by a Check-1 deny (enforce-apex-phases.ts:80) is
61
+ * cross-credited on EVERY consultation, with NO TTL on `target.set_at`, and the
62
+ * target PERSISTS (only the next deny replaces it via {@link recordTarget});
63
+ * single-shot clearing or TTL-gating the target re-opened the deny loop when a
64
+ * consultation landed > TTL after the deny. Immutable.
65
+ */
67
66
  declare function recordDoc(track: SessionTrack, framework: string, sessionId: string, source: string, now?: number): SessionTrack;
68
67
  /** Set the pending doc-credit target (written by the runtime on a Check-1 deny). Immutable. */
69
68
  declare function recordTarget(track: SessionTrack, target: SessionTarget): SessionTrack;
70
69
  /**
71
- * Record that a SOLID reference file was read (deduped). Immutable. When `now`
72
- * (epoch ms — the tool event's own timestamp, never a fresh `Date.now()`) is
73
- * supplied, the read is stamped in `refsReadAt`, refreshed on re-reads so the
74
- * LATEST read drives the SOLID TTL (parity track-solid-reads.py, which appends
75
- * a timestamped entry per read; require-solid-read.py checks the most recent).
76
- * Callers that omit `now` keep the legacy untimestamped behavior.
77
- */
70
+ * Record that a SOLID reference file was read (deduped). Immutable. When `now`
71
+ * (epoch ms — the tool event's own timestamp, never a fresh `Date.now()`) is
72
+ * supplied, the read is stamped in `refsReadAt`, refreshed on re-reads so the
73
+ * LATEST read drives the SOLID TTL (parity track-solid-reads.py, which appends
74
+ * a timestamped entry per read; require-solid-read.py checks the most recent).
75
+ * Callers that omit `now` keep the legacy untimestamped behavior.
76
+ */
78
77
  declare function recordRefRead(track: SessionTrack, path: string, now?: number): SessionTrack;
79
78
  /** Record an agent/tool call with a timestamp + optional quality. Immutable. */
80
79
  declare function recordAgent(track: SessionTrack, name: string, ts: number, quality?: AgentQuality): SessionTrack;
@@ -1,6 +1,6 @@
1
- import { t as HOME_DIR } from "./dotenv-Jj8aL1FL.mjs";
2
- import { t as detectHarness } from "./harness-Cb9xR8dC.mjs";
3
- import { l as PLUGINS_DIR } from "./home-state-BXf38Zi1.mjs";
1
+ import { t as HOME_DIR } from "./dotenv-DoqNlALG.mjs";
2
+ import { t as detectHarness } from "./harness-BMuLJ9lm.mjs";
3
+ import { l as PLUGINS_DIR } from "./home-state-oUGFB4ds.mjs";
4
4
  import { join } from "node:path";
5
5
  import { existsSync, readFileSync, readdirSync } from "node:fs";
6
6
  import { homedir } from "node:os";
@@ -1,11 +1,11 @@
1
- import { c as ttlLabel } from "./dotenv-Jj8aL1FL.mjs";
2
- import { n as fuseHarnessHome } from "./home-state-BXf38Zi1.mjs";
1
+ import { c as ttlLabel } from "./dotenv-DoqNlALG.mjs";
2
+ import { n as fuseHarnessHome } from "./home-state-oUGFB4ds.mjs";
3
3
  import { a as writeJsonFile, i as readJsonFile } from "./json-io-DisYd2fb.mjs";
4
4
  import { i as resolveSessions } from "./doc-helpers-CWZegVdR.mjs";
5
5
  import { t as routeReferences } from "./router-PKVNBHge.mjs";
6
- import { t as resolveSkillPath } from "./skill-path-BO2N0XvB.mjs";
7
- import { join } from "node:path";
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { t as resolveSkillPath } from "./skill-path-DhBGlQe3.mjs";
7
+ import { dirname, join } from "node:path";
8
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
9
9
  import { createHmac, randomBytes } from "node:crypto";
10
10
  //#region src/policy/skill-source.ts
11
11
  /**
@@ -132,7 +132,7 @@ const apexAuthorizationGate = (ctx) => {
132
132
  kind: "block",
133
133
  title,
134
134
  reason: [
135
- `APEX: Read specific SOLID references (expires every ${ttl}) for ${ctx.framework}.`,
135
+ `APEX: Read ALL SOLID references below (including Optional), then retry (expires every ${ttl}) for ${ctx.framework}.`,
136
136
  `Editing: ${ctx.filePath}`,
137
137
  ...optional.length ? [`Optional: ${optional.join(", ")}`] : [],
138
138
  `Full skill: ${routed.skillPath}`
@@ -268,15 +268,26 @@ const NONCE_PATH = join(HARNESS_DIR, ".nonce");
268
268
  /** Load (or create on first use) the per-machine HMAC key stored at mode 0600. */
269
269
  function loadOrCreateKey() {
270
270
  mkdirSync(HARNESS_DIR, { recursive: true });
271
- if (!existsSync(KEY_PATH)) {
272
- const key = randomBytes(32).toString("hex");
273
- writeFileSync(KEY_PATH, key, {
271
+ if (existsSync(KEY_PATH)) return readFileSync(KEY_PATH, "utf8").trim();
272
+ const candidate = randomBytes(32).toString("hex");
273
+ try {
274
+ writeFileSync(KEY_PATH, candidate, {
274
275
  encoding: "utf8",
275
- mode: 384
276
+ mode: 384,
277
+ flag: "wx"
276
278
  });
277
- return key;
279
+ return candidate;
280
+ } catch (e) {
281
+ if (e.code !== "EEXIST") throw e;
282
+ }
283
+ for (let i = 0; i < 5; i++) {
284
+ const existing = readFileSync(KEY_PATH, "utf8").trim();
285
+ if (existing) return existing;
286
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
278
287
  }
279
- return readFileSync(KEY_PATH, "utf8").trim();
288
+ const key = readFileSync(KEY_PATH, "utf8").trim();
289
+ if (!key) throw new Error("fuse: empty .key after write contention");
290
+ return key;
280
291
  }
281
292
  /**
282
293
  * Persist the last-seen nonce for advisory diagnostics (mode 0600).
@@ -322,6 +333,69 @@ function verifyTrack(envelope) {
322
333
  }
323
334
  }
324
335
  //#endregion
336
+ //#region src/tracking/track-lock.ts
337
+ /**
338
+ * Cross-process lock for session-track read-modify-write. The ~11-hook
339
+ * fan-out races plain `loadTrack → mutate → saveTrack` (writes silently
340
+ * lost — `refsRead` was the only mitigated channel, via its journal). This
341
+ * lockfile (`wx`, atomic create) serializes the whole RMW:
342
+ * - retry is bounded in TOTAL time (400 ms, far under any hook timeout);
343
+ * - after the budget, NEVER crash: stderr is logged and the caller skips the
344
+ * write — a named lost write beats a fail-open crash (hook fail-open
345
+ * semantics preserved);
346
+ * - an orphaned lockfile (dead process) is reclaimed after a 10 s TTL, so a
347
+ * crash mid-write never blocks a project forever.
348
+ */
349
+ /** Orphaned lockfile lifetime before reclamation. */
350
+ const LOCK_TTL_MS = 1e4;
351
+ /** Delay between two lock acquisition attempts. */
352
+ const RETRY_STEP_MS = 8;
353
+ /** Sentinel returned by {@link withTrackLock} when the lock could not be taken. */
354
+ const LOCK_FAILED = Symbol("track-lock-failed");
355
+ /** True when the lockfile is older than the TTL (orphaned by a dead process). */
356
+ function isStale(lock) {
357
+ try {
358
+ return Date.now() - statSync(lock).mtimeMs > LOCK_TTL_MS;
359
+ } catch {
360
+ return false;
361
+ }
362
+ }
363
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
364
+ /**
365
+ * Run `fn` under the track lock of `dir`, or skip with a stderr note.
366
+ * @param dir - The track state directory (lockfile lives inside).
367
+ * @param fn - The protected read-modify-write.
368
+ * @returns `fn`'s result, or {@link LOCK_FAILED} when the lock stayed busy.
369
+ */
370
+ async function withTrackLock(dir, fn) {
371
+ mkdirSync(dir, { recursive: true });
372
+ const lock = join(dir, "track.lock");
373
+ const deadline = Date.now() + 400;
374
+ for (;;) try {
375
+ closeSync(openSync(lock, "wx"));
376
+ break;
377
+ } catch {
378
+ if (isStale(lock)) {
379
+ try {
380
+ unlinkSync(lock);
381
+ } catch {}
382
+ continue;
383
+ }
384
+ if (Date.now() >= deadline) {
385
+ process.stderr.write(`harness: track lock busy, write skipped (${lock})\n`);
386
+ return LOCK_FAILED;
387
+ }
388
+ await sleep(RETRY_STEP_MS);
389
+ }
390
+ try {
391
+ return await fn();
392
+ } finally {
393
+ try {
394
+ unlinkSync(lock);
395
+ } catch {}
396
+ }
397
+ }
398
+ //#endregion
325
399
  //#region src/tracking/store.ts
326
400
  /**
327
401
  * Load and verify a session track from a signed envelope file.
@@ -345,5 +419,19 @@ async function saveTrack(file, track) {
345
419
  await writeJsonFile(file, envelope);
346
420
  writeLastNonce(envelope.nonce);
347
421
  }
422
+ /**
423
+ * Locked read-modify-write on a session track (the fan-out-safe way to mutate
424
+ * it): loads under the lock, applies `mutate`, persists atomically. On lock
425
+ * contention the write is SKIPPED (fail-open, stderr already logged) and
426
+ * `false` is returned — a named lost write, never a crash.
427
+ * @param file - The session track file.
428
+ * @param mutate - Pure mutation over the freshly loaded track.
429
+ * @returns True when the write landed.
430
+ */
431
+ async function withTrack(file, mutate) {
432
+ return await withTrackLock(dirname(file), async () => {
433
+ await saveTrack(file, await mutate(await loadTrack(file)));
434
+ }) !== LOCK_FAILED;
435
+ }
348
436
  //#endregion
349
- export { writeLastNonce as a, recordAgent as c, recordRefRead as d, recordTarget as f, apexAuthorizationGate as h, verifyTrack as i, recordBrainstormRequired as l, trivialCount as m, saveTrack as n, agentsFresh as o, recordTrivialEdit as p, signTrack as r, emptyTrack as s, loadTrack as t, recordDoc as u };
437
+ export { trivialCount as _, LOCK_TTL_MS as a, writeLastNonce as c, recordAgent as d, recordBrainstormRequired as f, recordTrivialEdit as g, recordTarget as h, LOCK_FAILED as i, agentsFresh as l, recordRefRead as m, saveTrack as n, signTrack as o, recordDoc as p, withTrack as r, verifyTrack as s, loadTrack as t, emptyTrack as u, apexAuthorizationGate as v };
@@ -1,19 +1,28 @@
1
- import { a as recordAgent, c as recordRefRead, d as trivialCount, i as emptyTrack, l as recordTarget, n as SessionTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as AgentQuality, u as recordTrivialEdit } from "../session-state-CY4iohn_.mjs";
2
-
1
+ import { a as recordAgent, c as recordRefRead, d as trivialCount, i as emptyTrack, l as recordTarget, n as SessionTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as AgentQuality, u as recordTrivialEdit } from "../session-state-D5gLr66m.mjs";
3
2
  //#region src/tracking/store.d.ts
4
3
  /**
5
- * Load and verify a session track from a signed envelope file.
6
- *
7
- * Returns {@link emptyTrack} (fail closed) when the file is absent, corrupt, or
8
- * fails MAC validation — so the gates re-require real agents rather than trust a
9
- * forged track. Only a MAC mismatch triggers fail-closed; the nonce is advisory
10
- * and is never checked during load.
11
- */
4
+ * Load and verify a session track from a signed envelope file.
5
+ *
6
+ * Returns {@link emptyTrack} (fail closed) when the file is absent, corrupt, or
7
+ * fails MAC validation — so the gates re-require real agents rather than trust a
8
+ * forged track. Only a MAC mismatch triggers fail-closed; the nonce is advisory
9
+ * and is never checked during load.
10
+ */
12
11
  declare function loadTrack(file: string): Promise<SessionTrack>;
13
12
  /**
14
- * Sign and persist a session track as a tamper-evident envelope, then write the
15
- * advisory nonce watermark.
16
- */
13
+ * Sign and persist a session track as a tamper-evident envelope, then write the
14
+ * advisory nonce watermark.
15
+ */
17
16
  declare function saveTrack(file: string, track: SessionTrack): Promise<void>;
17
+ /**
18
+ * Locked read-modify-write on a session track (the fan-out-safe way to mutate
19
+ * it): loads under the lock, applies `mutate`, persists atomically. On lock
20
+ * contention the write is SKIPPED (fail-open, stderr already logged) and
21
+ * `false` is returned — a named lost write, never a crash.
22
+ * @param file - The session track file.
23
+ * @param mutate - Pure mutation over the freshly loaded track.
24
+ * @returns True when the write landed.
25
+ */
26
+ declare function withTrack(file: string, mutate: (track: SessionTrack) => SessionTrack | Promise<SessionTrack>): Promise<boolean>;
18
27
  //#endregion
19
- export { AgentQuality, SessionTrack, agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTarget, recordTrivialEdit, saveTrack, trivialCount };
28
+ export { AgentQuality, SessionTrack, agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTarget, recordTrivialEdit, saveTrack, trivialCount, withTrack };
@@ -1,2 +1,2 @@
1
- import { c as recordAgent, d as recordRefRead, f as recordTarget, l as recordBrainstormRequired, m as trivialCount, n as saveTrack, o as agentsFresh, p as recordTrivialEdit, s as emptyTrack, t as loadTrack, u as recordDoc } from "../store-Bcn08y9m.mjs";
2
- export { agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTarget, recordTrivialEdit, saveTrack, trivialCount };
1
+ import { _ as trivialCount, d as recordAgent, f as recordBrainstormRequired, g as recordTrivialEdit, h as recordTarget, l as agentsFresh, m as recordRefRead, n as saveTrack, p as recordDoc, r as withTrack, t as loadTrack, u as emptyTrack } from "../store-yr_QFsZY.mjs";
2
+ export { agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTarget, recordTrivialEdit, saveTrack, trivialCount, withTrack };
@@ -2,10 +2,10 @@
2
2
  /** How a policy outcome should surface in a harness. */
3
3
  type PromptKind = "ask" | "block" | "inform";
4
4
  /**
5
- * Portable, harness-agnostic representation of a policy outcome as a prompt.
6
- * Adapters render this into their harness's native shape (a Claude
7
- * `permissionDecision`, a Cursor block, a CLI exit + message, ...).
8
- */
5
+ * Portable, harness-agnostic representation of a policy outcome as a prompt.
6
+ * Adapters render this into their harness's native shape (a Claude
7
+ * `permissionDecision`, a Cursor block, a CLI exit + message, ...).
8
+ */
9
9
  interface Prompt {
10
10
  kind: PromptKind;
11
11
  /** Short title, e.g. "SOLID file-size limit". */
@@ -15,10 +15,10 @@ interface Prompt {
15
15
  /** Concrete next actions to proceed. */
16
16
  actions?: string[];
17
17
  /**
18
- * User-facing notice (Python `hook_output.allow_pass`/`post_pass` parity):
19
- * shown to the HUMAN — Claude Code's `systemMessage` — never to the agent,
20
- * unlike `reason`/additionalContext. Excluded from {@link formatPrompt}.
21
- */
18
+ * User-facing notice (Python `hook_output.allow_pass`/`post_pass` parity):
19
+ * shown to the HUMAN — Claude Code's `systemMessage` — never to the agent,
20
+ * unlike `reason`/additionalContext. Excluded from {@link formatPrompt}.
21
+ */
22
22
  userMessage?: string;
23
23
  }
24
24
  /** Render a {@link Prompt} as a consistent, agent-readable memo block. */
@@ -0,0 +1,15 @@
1
+ //#region src/detect/interfaces/types.d.ts
2
+ /** Known AI coding harnesses detectable at runtime. */
3
+ type HarnessId = "claude-code" | "codex" | "cursor" | "cline" | "gemini-cli" | "opencode" | "windsurf" | "copilot" | "aider" | "kiro" | "goose" | "amp" | "hermes" | "kimi" | "unknown";
4
+ /** Integration mode: `hook` = native lifecycle hooks; `cli` = run as an external step. */
5
+ type HarnessMode = "hook" | "cli";
6
+ /** How the harness was identified. */
7
+ type HarnessVia = "agent-std" | "ai-agent-std" | "env" | "fallback";
8
+ /** Result of {@link detectHarness}. */
9
+ interface HarnessInfo {
10
+ id: HarnessId;
11
+ mode: HarnessMode;
12
+ via: HarnessVia;
13
+ }
14
+ //#endregion
15
+ export { HarnessVia as i, HarnessInfo as n, HarnessMode as r, HarnessId as t };
@@ -1,2 +1,2 @@
1
- import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "../index-BEMumjOw.mjs";
1
+ import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "../index-p9fLrSOD.mjs";
2
2
  export { compactJson, isCodeFile, projectRoot, projectRootOrNull, walkUpFor };