@fusengine/harness 0.1.80 → 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.
- package/dist/adapters/claude/index.d.mts +1 -1
- package/dist/adapters/claude/index.mjs +1 -1
- package/dist/adapters/cline/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +11 -12
- package/dist/adapters/codex/index.mjs +3 -3
- package/dist/adapters/cursor/index.d.mts +29 -29
- package/dist/adapters/cursor/index.mjs +1 -1
- package/dist/adapters/gemini/index.mjs +1 -1
- package/dist/adapters/hermes/index.d.mts +22 -23
- package/dist/adapters/hermes/index.mjs +1 -1
- package/dist/adapters/kimi/index.d.mts +54 -46
- package/dist/adapters/kimi/index.mjs +2 -2
- package/dist/cache/index.d.mts +1 -1
- package/dist/{claude-51RSspJ1.mjs → claude-D93HY8dq.mjs} +2 -2
- package/dist/cli/bin.d.mts +1 -1
- package/dist/cli/bin.mjs +129 -38
- package/dist/cli/index.d.mts +3 -3
- package/dist/cli/index.mjs +1 -1
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +4 -4
- package/dist/detect/index.d.mts +2 -1
- package/dist/{doc-helpers-CEKzGg2u.d.mts → doc-helpers-BGCzP9VF.d.mts} +12 -12
- package/dist/{dotenv-B26N_uGr.mjs → dotenv-DoqNlALG.mjs} +29 -2
- package/dist/{evaluate-ClfbCDiY.mjs → evaluate-B4-MhOy_.mjs} +322 -32
- package/dist/freshness/index.d.mts +2 -2
- package/dist/{handle-B7YufFz9.mjs → handle-J5r2f_O_.mjs} +637 -159
- package/dist/{hermes-rTmVdhUL.mjs → hermes-1PUEoY8q.mjs} +2 -2
- package/dist/{home-state-BXf38Zi1.mjs → home-state-oUGFB4ds.mjs} +4 -2
- package/dist/index-84heAAm_.d.mts +12 -0
- package/dist/index-CaxrMD1A.d.mts +1 -0
- package/dist/index-CigBu4K9.d.mts +14 -0
- package/dist/{index-DGxNjXW7.d.mts → index-D4C2-IIc.d.mts} +6 -8
- package/dist/index-DBgnIZn9.d.mts +756 -0
- package/dist/{index-mmRF3KNp.d.mts → index-DEfz82BP.d.mts} +12 -13
- package/dist/{index-BUwEmIK-.d.mts → index-DhmV2MPh.d.mts} +31 -31
- package/dist/index-oXXPs2xl.d.mts +119 -0
- package/dist/index-p9fLrSOD.d.mts +27 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +8 -8
- package/dist/init/index.d.mts +8 -9
- package/dist/init/index.mjs +1 -1
- package/dist/{kimi-C7FGprQz.mjs → kimi-ChbcU2uI.mjs} +17 -3
- package/dist/{layout-C0jaaCQC.mjs → layout-KWoE_Mqn.mjs} +1 -0
- package/dist/limits-DG5pl-5s.mjs +59 -0
- package/dist/memory/index.mjs +1 -1
- package/dist/policy/index.d.mts +2 -3
- package/dist/policy/index.mjs +4 -4
- package/dist/prompt/index.d.mts +2 -1
- package/dist/refs/index.d.mts +1 -1
- package/dist/{registry-CymilZiZ.mjs → registry-IhHk2KlT.mjs} +1 -1
- package/dist/{run-3tyTW9Ud.mjs → run-D_WchZ3R.mjs} +1 -1
- package/dist/{run-Do2JltgU.mjs → run-TAXNRSpD.mjs} +1 -1
- package/dist/runtime/index.d.mts +453 -451
- package/dist/runtime/index.mjs +3 -3
- package/dist/{session-state-CY4iohn_.d.mts → session-state-D5gLr66m.d.mts} +29 -30
- package/dist/{skill-path-CDvR4bbF.mjs → skill-path-DhBGlQe3.mjs} +2 -2
- package/dist/{store-nDZ3eLBQ.mjs → store-yr_QFsZY.mjs} +101 -13
- package/dist/tracking/index.d.mts +22 -13
- package/dist/tracking/index.mjs +2 -2
- package/dist/{types-DVbIl9md.d.mts → types-C0-igRKr.d.mts} +8 -8
- package/dist/types-DvXTI71X.d.mts +15 -0
- package/dist/util/index.d.mts +1 -1
- package/dist/{validate-CkmquRDV.mjs → validate-Bf-EsbDh.mjs} +279 -20
- package/package.json +163 -163
- package/dist/apex-Wdi1nq_w.d.mts +0 -73
- package/dist/harness-BQ5_Wnva.d.mts +0 -27
- package/dist/index-BEMumjOw.d.mts +0 -27
- package/dist/index-BIGVNQB8.d.mts +0 -576
- package/dist/index-BKZ67WMa.d.mts +0 -1
- package/dist/index-BOBXQ91y.d.mts +0 -12
- package/dist/index-BVVgDSdq.d.mts +0 -1
- package/dist/index-B_VHbsaJ.d.mts +0 -96
- package/dist/limits-CHn8AIL1.mjs +0 -31
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as projectLayout } from "../layout-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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-
|
|
1
|
+
import { t as HOME_DIR } from "./dotenv-DoqNlALG.mjs";
|
|
2
2
|
import { t as detectHarness } from "./harness-BMuLJ9lm.mjs";
|
|
3
|
-
import { l as PLUGINS_DIR } from "./home-state-
|
|
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-
|
|
2
|
-
import { n as fuseHarnessHome } from "./home-state-
|
|
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-
|
|
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
|
|
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 (
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
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 };
|
package/dist/tracking/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 };
|
package/dist/util/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "../index-
|
|
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 };
|