@fusengine/harness 0.1.54 → 0.1.56
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/cli/bin.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
|
|
|
4
4
|
import { t as claudeHome } from "../home-state-D0RLWP8J.mjs";
|
|
5
5
|
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-DZvP_9xB.mjs";
|
|
6
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
7
|
-
import { F as runDoctor, I as runningVersion, L as versionBanner, Lt as todayUtc, t as handleHook } from "../handle-
|
|
7
|
+
import { F as runDoctor, I as runningVersion, L as versionBanner, Lt as todayUtc, t as handleHook } from "../handle-CoyvRORV.mjs";
|
|
8
8
|
import { delimiter, join } from "node:path";
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -1292,6 +1292,28 @@ function validateRulesLoaded(data, home = homedir()) {
|
|
|
1292
1292
|
} catch {}
|
|
1293
1293
|
}
|
|
1294
1294
|
//#endregion
|
|
1295
|
+
//#region src/runtime/burst-window.ts
|
|
1296
|
+
/**
|
|
1297
|
+
* @module burst-window
|
|
1298
|
+
* Single source of truth for the multi-plugin hook fan-out window.
|
|
1299
|
+
*
|
|
1300
|
+
* Every DEPLOYED plugin registers its OWN PreToolUse/PostToolUse hook, so ONE
|
|
1301
|
+
* Claude tool event spawns ~11 sibling harness processes that each record the
|
|
1302
|
+
* same deny / one-shot / sniper reminder within milliseconds. Left unchecked
|
|
1303
|
+
* the deny-loop counter jumped by ~11 per real attempt ([REPEAT] "#9" on the
|
|
1304
|
+
* FIRST try), the one-shot metric inflated ~11×, and the sniper reminder was
|
|
1305
|
+
* injected ~11× (token noise).
|
|
1306
|
+
*
|
|
1307
|
+
* A record landing within this window after an identical prior one (same
|
|
1308
|
+
* operation hash + same `session_id`) is treated as the SAME event and folded
|
|
1309
|
+
* into it instead of re-counted. Two REAL agent retries are always spaced
|
|
1310
|
+
* further apart than the burst, so this never masks a genuine loop. No env var:
|
|
1311
|
+
* the fan-out is a physical property of the installed plugin set, not policy.
|
|
1312
|
+
* @packageDocumentation
|
|
1313
|
+
*/
|
|
1314
|
+
/** Fan-out dedup window (ms). The ~11 sibling hooks for one event land in <2s. */
|
|
1315
|
+
const BURST_DEDUP_MS = 2e3;
|
|
1316
|
+
//#endregion
|
|
1295
1317
|
//#region src/runtime/lifecycle/track-changes.ts
|
|
1296
1318
|
/** Code-file extensions tracked for sniper (mirrors track-session-changes.py). */
|
|
1297
1319
|
const CODE_EXT$1 = /\.(ts|tsx|js|jsx|py|go|rs|java|php|cpp|c|rb|swift|kt|vue|svelte|astro)$/;
|
|
@@ -1326,6 +1348,10 @@ function trackSessionChanges(sessionIdRaw, filePath, home = homedir(), now = Dat
|
|
|
1326
1348
|
lastCheck: new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
1327
1349
|
};
|
|
1328
1350
|
saveSessionState(sid, state, home);
|
|
1351
|
+
if (!oncePerWindow(`sniper:${sid}:${filePath}`, 2e3, {
|
|
1352
|
+
now,
|
|
1353
|
+
dir: sessionsDir(home)
|
|
1354
|
+
})) return "";
|
|
1329
1355
|
return contextResponse("PostToolUse", `SNIPER VALIDATION REQUIRED: Code file '${basename(filePath)}' was modified. You MUST now run the sniper agent (fuse-ai-pilot:sniper) to validate this modification before continuing. This is mandatory per CLAUDE.md rules.`);
|
|
1330
1356
|
}
|
|
1331
1357
|
//#endregion
|
|
@@ -2334,11 +2360,35 @@ function bodyText(block) {
|
|
|
2334
2360
|
function firstSentence(s) {
|
|
2335
2361
|
return (s.split(/(?<=\.)\s/)[0] ?? s).trim();
|
|
2336
2362
|
}
|
|
2337
|
-
/**
|
|
2363
|
+
/**
|
|
2364
|
+
* The bullet's `narrative → rule` delimiter: a SPACED arrow only. A GLUED arrow
|
|
2365
|
+
* between tokens (e.g. `120s→300s`, `s→3`) is prose the author wrote, never a
|
|
2366
|
+
* delimiter — matching on `→` alone chopped rules mid-token (bug: `300s) pensant
|
|
2367
|
+
* corriger…`). Also used to split rule-internal clauses.
|
|
2368
|
+
*/
|
|
2369
|
+
const RULE_ARROW = /\s+→\s+/;
|
|
2370
|
+
/**
|
|
2371
|
+
* Distil the actionable rule from a bullet body. With no spaced arrow the whole
|
|
2372
|
+
* bullet is the rule → its first sentence. Otherwise the rule is everything after
|
|
2373
|
+
* the FIRST spaced arrow; its spaced-arrow-delimited segments are kept whole
|
|
2374
|
+
* except for TRAILING short asides (< {@link MIN_RULE} chars, e.g. `→ (cf.
|
|
2375
|
+
* lecture).`) which are dropped — so an arrow used as PROSE inside a rule (`maps
|
|
2376
|
+
* X → Y doit…`) is preserved intact rather than chopped at the arrow. When the
|
|
2377
|
+
* kept rule is still under {@link MIN_RULE} chars, fall back to the first sentence
|
|
2378
|
+
* of the WHOLE rule part (never the narrative), avoiding an illegible stub.
|
|
2379
|
+
*/
|
|
2380
|
+
function distillRule(text) {
|
|
2381
|
+
const sep = text.search(RULE_ARROW);
|
|
2382
|
+
if (sep < 0) return firstSentence(text);
|
|
2383
|
+
const rulePart = text.slice(sep).replace(RULE_ARROW, "").trim();
|
|
2384
|
+
const segments = rulePart.split(RULE_ARROW).map((s) => s.trim()).filter(Boolean);
|
|
2385
|
+
while (segments.length > 1 && (segments[segments.length - 1]?.length ?? 0) < 40) segments.pop();
|
|
2386
|
+
const rule = firstSentence(segments.join(" → "));
|
|
2387
|
+
return rule.length >= 40 ? rule : firstSentence(rulePart);
|
|
2388
|
+
}
|
|
2389
|
+
/** Collapse one older bullet to `- [date] <rule>`: {@link distillRule}, capped. */
|
|
2338
2390
|
function compressBullet(block) {
|
|
2339
|
-
|
|
2340
|
-
const arrow = text.lastIndexOf("→");
|
|
2341
|
-
let rule = firstSentence((arrow >= 0 ? text.slice(arrow + 1) : text).trim());
|
|
2391
|
+
let rule = distillRule(bodyText(block));
|
|
2342
2392
|
if (rule.length > 200) rule = `${rule.slice(0, 199).trimEnd()}…`;
|
|
2343
2393
|
const date = stamp(block);
|
|
2344
2394
|
return `- ${date ? `[${date}] ` : ""}${rule}`;
|
|
@@ -2641,14 +2691,32 @@ function denyHash(tool, input) {
|
|
|
2641
2691
|
/**
|
|
2642
2692
|
* Pure loop check: given the already-pruned in-window map, compute the running
|
|
2643
2693
|
* count for `hash` and whether it repeats (count > 1). No IO — the caller persists.
|
|
2644
|
-
*
|
|
2694
|
+
*
|
|
2695
|
+
* When `dedupMs` is set (>0) and an identical prior deny landed within that
|
|
2696
|
+
* window, the current call is a sibling hook echoing the SAME event (see
|
|
2697
|
+
* {@link module:burst-window}): it returns the prior verdict VERBATIM with
|
|
2698
|
+
* `deduped:true` and does NOT bump the count, so all N fan-out processes agree
|
|
2699
|
+
* on one number instead of counting to N. Absent `dedupMs` (mono-process
|
|
2700
|
+
* callers / unit tests) the historical increment-every-time behaviour holds.
|
|
2701
|
+
* @param hash - {@link denyHash}-derived map key of the current call.
|
|
2645
2702
|
* @param priorDenies - The `{ hash -> DenyEntry }` map, pruned to `now`/`windowMs`.
|
|
2646
|
-
* @param opts - Clock + window.
|
|
2647
|
-
* @returns `{ isRepeat, count, hash }`.
|
|
2703
|
+
* @param opts - Clock + window, plus an optional burst-dedup window.
|
|
2704
|
+
* @returns `{ isRepeat, count, hash, deduped? }`.
|
|
2648
2705
|
*/
|
|
2649
2706
|
function denyLoopCheck(hash, priorDenies, opts) {
|
|
2650
2707
|
const prev = priorDenies[hash];
|
|
2651
|
-
|
|
2708
|
+
if (!(prev && typeof prev.lastTs === "number" && opts.now - prev.lastTs < opts.windowMs)) return {
|
|
2709
|
+
isRepeat: false,
|
|
2710
|
+
count: 1,
|
|
2711
|
+
hash
|
|
2712
|
+
};
|
|
2713
|
+
if ((opts.dedupMs ?? 0) > 0 && opts.now - prev.lastTs < (opts.dedupMs ?? 0)) return {
|
|
2714
|
+
isRepeat: prev.count > 1,
|
|
2715
|
+
count: prev.count,
|
|
2716
|
+
hash,
|
|
2717
|
+
deduped: true
|
|
2718
|
+
};
|
|
2719
|
+
const count = prev.count + 1;
|
|
2652
2720
|
return {
|
|
2653
2721
|
isRepeat: count > 1,
|
|
2654
2722
|
count,
|
|
@@ -2782,6 +2850,39 @@ function formatSummary(s) {
|
|
|
2782
2850
|
return `gates 7d: ${head}${parts.length ? `; ${parts.join("; ")}` : ""}`;
|
|
2783
2851
|
}
|
|
2784
2852
|
//#endregion
|
|
2853
|
+
//#region src/tracking/one-shot-dedup.ts
|
|
2854
|
+
/**
|
|
2855
|
+
* @module one-shot-dedup
|
|
2856
|
+
* Burst-dedup guard for the one-shot metric ({@link module:one-shot}).
|
|
2857
|
+
*
|
|
2858
|
+
* ONE Claude tool event fans out to ~11 sibling plugin-hook processes, each
|
|
2859
|
+
* calling {@link recordOneShot}; without this the metric would count a single
|
|
2860
|
+
* deny/allow ~11×. Reuses the proven {@link oncePerWindow} cooldown sidecar:
|
|
2861
|
+
* the FIRST process in the {@link module:burst-window} window mutates the
|
|
2862
|
+
* metric, the rest skip. The dedup key includes the outcome KIND (deny-title vs
|
|
2863
|
+
* allow) so a deny and its later fix — different kinds — are never folded into
|
|
2864
|
+
* each other. No `sessionId` → always the first (mono-process + unit-test
|
|
2865
|
+
* parity; a burst can only exist when a real session drives the fan-out).
|
|
2866
|
+
* @packageDocumentation
|
|
2867
|
+
*/
|
|
2868
|
+
/**
|
|
2869
|
+
* True when this `(op, kind)` is the FIRST of its burst for the session — the
|
|
2870
|
+
* process that should actually mutate the metric. Sibling processes firing the
|
|
2871
|
+
* SAME event within {@link BURST_DEDUP_MS} return false and skip the write.
|
|
2872
|
+
* @param op - Content-free operation key ({@link denyHash}("op", …)).
|
|
2873
|
+
* @param kind - Outcome discriminator (`deny:<title>` or `allow`).
|
|
2874
|
+
* @param opts - Clock + state dir + optional session id.
|
|
2875
|
+
* @returns `true` to apply the record, `false` to skip (already counted).
|
|
2876
|
+
*/
|
|
2877
|
+
function burstFirst(op, kind, opts) {
|
|
2878
|
+
const sid = opts.sessionId?.trim();
|
|
2879
|
+
if (!sid) return true;
|
|
2880
|
+
return oncePerWindow(`oneshot:${sid}:${op}:${kind}`, BURST_DEDUP_MS, {
|
|
2881
|
+
now: opts.now,
|
|
2882
|
+
dir: opts.dir
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
//#endregion
|
|
2785
2886
|
//#region src/tracking/one-shot.ts
|
|
2786
2887
|
/**
|
|
2787
2888
|
* @module one-shot
|
|
@@ -2829,12 +2930,13 @@ function loadState(path) {
|
|
|
2829
2930
|
function recordOneShot(prompt, input, opts) {
|
|
2830
2931
|
try {
|
|
2831
2932
|
if (prompt && prompt.kind !== "block") return;
|
|
2832
|
-
const path = join(opts.dir, SIDECAR$1);
|
|
2833
|
-
let s = pruneState(loadState(path), opts.now, WINDOW_MS);
|
|
2834
2933
|
const op = denyHash("op", {
|
|
2835
2934
|
filePath: input.filePath,
|
|
2836
2935
|
command: input.command
|
|
2837
2936
|
});
|
|
2937
|
+
if (!burstFirst(op, prompt ? `deny:${prompt.title}` : "allow", opts)) return;
|
|
2938
|
+
const path = join(opts.dir, SIDECAR$1);
|
|
2939
|
+
let s = pruneState(loadState(path), opts.now, WINDOW_MS);
|
|
2838
2940
|
s = prompt ? applyDeny(s, prompt.title, op, opts.now) : applyAllow(s, op, opts.now, input.content != null || input.command != null);
|
|
2839
2941
|
atomicWrite(path, JSON.stringify(s));
|
|
2840
2942
|
} catch {}
|
|
@@ -5494,6 +5596,50 @@ function frameworkSkillGate(input, refsRead, existingCodeLines) {
|
|
|
5494
5596
|
return skillTriggerGate(input.framework, content, refsRead, forced, input.cwd, input.filePath);
|
|
5495
5597
|
}
|
|
5496
5598
|
//#endregion
|
|
5599
|
+
//#region src/freshness/ref-evidence.ts
|
|
5600
|
+
/**
|
|
5601
|
+
* Platform-transcript reconciliation for `.md` reference reads — the durable
|
|
5602
|
+
* counterpart to {@link agentsRanFromTranscript} for agent freshness.
|
|
5603
|
+
*
|
|
5604
|
+
* WHY: the session track is persisted by a non-atomic load→mutate→save. Under
|
|
5605
|
+
* the multi-plugin hook fan-out (one hook process per installed plugin, ×N) plus
|
|
5606
|
+
* back-to-back tool events, concurrent writers clobber each other (lost update).
|
|
5607
|
+
* `agents`/`authorizations` self-heal — every explore/research/doc call rewrites
|
|
5608
|
+
* them, so a lost update lands again on the next of hundreds of writes — but a
|
|
5609
|
+
* `refsRead` entry is written ONCE (when the `.md` is Read), so a single lost
|
|
5610
|
+
* update erases it permanently, and the lead has no SubagentStop
|
|
5611
|
+
* {@link harvestAgentEvidence} pass to reconcile it (sub-agents do, which is why
|
|
5612
|
+
* only the LEAD's solidReadGate never credited). The Claude-authored transcript
|
|
5613
|
+
* is append-only and race-immune, so folding its `.md` Reads back into the track
|
|
5614
|
+
* restores the lost evidence; each gate still applies its own TTL/session policy.
|
|
5615
|
+
*/
|
|
5616
|
+
/**
|
|
5617
|
+
* Fold every `.md` `Read` in the transcript into `track` as a timestamped ref
|
|
5618
|
+
* read (immutably). PURE reconciliation — the caller owns the track; each read
|
|
5619
|
+
* is stamped with its transcript timestamp (unstamped → `now`, lenient, matching
|
|
5620
|
+
* {@link agentsRanFromTranscript}), and an existing MORE-recent stamp is never
|
|
5621
|
+
* rolled back. Fail-open: an absent/unreadable transcript returns `track`
|
|
5622
|
+
* unchanged (same reference).
|
|
5623
|
+
* @param track - The current (possibly race-damaged) session track.
|
|
5624
|
+
* @param transcriptPath - Claude `transcript_path` for this session.
|
|
5625
|
+
* @param now - Fallback epoch-ms for transcript entries the platform left unstamped.
|
|
5626
|
+
* @returns The track with transcript `.md` reads merged into `refsRead`/`refsReadAt`.
|
|
5627
|
+
*/
|
|
5628
|
+
function reconcileRefReadsFromTranscript(track, transcriptPath, now) {
|
|
5629
|
+
const uses = readAgentToolUses(transcriptPath);
|
|
5630
|
+
if (!uses) return track;
|
|
5631
|
+
let next = track;
|
|
5632
|
+
for (const u of uses) {
|
|
5633
|
+
if (u.name !== "Read") continue;
|
|
5634
|
+
const path = String(u.input?.file_path ?? u.input?.path ?? "");
|
|
5635
|
+
if (!path.endsWith(".md")) continue;
|
|
5636
|
+
const ts = u.ts ?? now;
|
|
5637
|
+
const prev = next.refsReadAt?.[path];
|
|
5638
|
+
if (prev === void 0 || prev < ts) next = recordRefRead(next, path, ts);
|
|
5639
|
+
}
|
|
5640
|
+
return next;
|
|
5641
|
+
}
|
|
5642
|
+
//#endregion
|
|
5497
5643
|
//#region src/policy/shadcn-skill-gate.ts
|
|
5498
5644
|
/** File extensions the shadcn gate polices (source: `\.(tsx|jsx|css|scss|json)$`). */
|
|
5499
5645
|
const SHADCN_FILE_RE = /\.(tsx|jsx|css|scss|json)$/;
|
|
@@ -5854,17 +6000,28 @@ function prune(map, now, windowMs) {
|
|
|
5854
6000
|
*/
|
|
5855
6001
|
function recordDeny(tool, input, opts) {
|
|
5856
6002
|
const hash = denyHash(tool, input);
|
|
6003
|
+
const sid = opts.sessionId?.trim();
|
|
6004
|
+
const key = sid ? `${hash}::${sid}` : hash;
|
|
5857
6005
|
const path = join(opts.dir, SIDECAR);
|
|
5858
6006
|
const map = prune(loadMap(path), opts.now, opts.windowMs);
|
|
5859
|
-
const res = denyLoopCheck(
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
6007
|
+
const res = denyLoopCheck(key, map, {
|
|
6008
|
+
now: opts.now,
|
|
6009
|
+
windowMs: opts.windowMs,
|
|
6010
|
+
dedupMs: sid ? BURST_DEDUP_MS : 0
|
|
6011
|
+
});
|
|
6012
|
+
if (!res.deduped) {
|
|
6013
|
+
map[key] = {
|
|
6014
|
+
count: res.count,
|
|
6015
|
+
lastTs: opts.now
|
|
6016
|
+
};
|
|
6017
|
+
try {
|
|
6018
|
+
atomicWrite(path, JSON.stringify(map));
|
|
6019
|
+
} catch {}
|
|
6020
|
+
}
|
|
6021
|
+
return {
|
|
6022
|
+
...res,
|
|
6023
|
+
hash
|
|
5863
6024
|
};
|
|
5864
|
-
try {
|
|
5865
|
-
atomicWrite(path, JSON.stringify(map));
|
|
5866
|
-
} catch {}
|
|
5867
|
-
return res;
|
|
5868
6025
|
}
|
|
5869
6026
|
/**
|
|
5870
6027
|
* Gate tail: record every block deny; on a repeat, return the enriched prompt.
|
|
@@ -5909,12 +6066,14 @@ async function gate(input) {
|
|
|
5909
6066
|
const dir = dirname(input.trackFile);
|
|
5910
6067
|
recordOneShot(prompt, op, {
|
|
5911
6068
|
now: input.now,
|
|
5912
|
-
dir
|
|
6069
|
+
dir,
|
|
6070
|
+
sessionId: input.sessionId
|
|
5913
6071
|
});
|
|
5914
6072
|
return withDenyLoop(prompt, input.tool, op, {
|
|
5915
6073
|
now: input.now,
|
|
5916
6074
|
dir,
|
|
5917
|
-
windowMs: input.windowMs ?? 12e4
|
|
6075
|
+
windowMs: input.windowMs ?? 12e4,
|
|
6076
|
+
sessionId: input.sessionId
|
|
5918
6077
|
});
|
|
5919
6078
|
}
|
|
5920
6079
|
/** Stateless guards, then the trivial fast path, then the stateful APEX gates. */
|
|
@@ -5942,8 +6101,7 @@ async function runGates(input) {
|
|
|
5942
6101
|
if (modular) return modular;
|
|
5943
6102
|
if (!input.filePath) return null;
|
|
5944
6103
|
const filePath = input.filePath;
|
|
5945
|
-
const
|
|
5946
|
-
const track = await loadTrack(input.trackFile);
|
|
6104
|
+
const track = reconcileRefReadsFromTranscript(await loadTrack(input.trackFile), input.transcriptPath, input.now);
|
|
5947
6105
|
const solidOrSkill = frameworkSkillGate(input, track.refsRead, existingCodeLines);
|
|
5948
6106
|
if (solidOrSkill) return solidOrSkill;
|
|
5949
6107
|
if (isShadcnWrite(input.tool, filePath)) {
|
|
@@ -5964,7 +6122,7 @@ async function runGates(input) {
|
|
|
5964
6122
|
});
|
|
5965
6123
|
if (geminiBlock) return geminiBlock;
|
|
5966
6124
|
if (isApexScoped(input.filePath)) {
|
|
5967
|
-
const apex = await apexScopedGate(input, track,
|
|
6125
|
+
const apex = await apexScopedGate(input, track, input.windowMs ?? 12e4);
|
|
5968
6126
|
if (apex) return apex;
|
|
5969
6127
|
}
|
|
5970
6128
|
return dryGate(input.tool, input.filePath, input.content, input.cwd);
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
|
|
2
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-D0RLWP8J.mjs";
|
|
3
|
-
import { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as lessonsFileFor, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, Ft as securityStateDir, G as generateProjectMap, H as cartoSessionStart, It as securityStatePath, J as loadEnriched, K as isProject, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as listChildren, R as dispatchLessons, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as generateEcosystemMap, V as lessonsStateFileFor, W as writePluginMap, X as countFiles, Y as mergeLines, Z as getFileDesc, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, q as writeTree, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as lessonsArchiveFileFor } from "../handle-
|
|
3
|
+
import { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as lessonsFileFor, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, Ft as securityStateDir, G as generateProjectMap, H as cartoSessionStart, It as securityStatePath, J as loadEnriched, K as isProject, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as listChildren, R as dispatchLessons, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as generateEcosystemMap, V as lessonsStateFileFor, W as writePluginMap, X as countFiles, Y as mergeLines, Z as getFileDesc, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, q as writeTree, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as lessonsArchiveFileFor } from "../handle-CoyvRORV.mjs";
|
|
4
4
|
//#region src/runtime/storage.ts
|
|
5
5
|
/**
|
|
6
6
|
* The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
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",
|