@fusengine/harness 0.1.53 → 0.1.54

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, It as todayUtc, L as versionBanner, t as handleHook } from "../handle-D93-VIGS.mjs";
7
+ import { F as runDoctor, I as runningVersion, L as versionBanner, Lt as todayUtc, t as handleHook } from "../handle-CVvp1yuc.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";
@@ -903,10 +903,10 @@ function trunc(text, limit) {
903
903
  /** Render fresh cache entries as the markdown injection block. */
904
904
  function render(entries) {
905
905
  const lines = [
906
- "# MCP Cache disponible cette session",
907
- "Avant de lancer mcp__context7/exa, verifie si deja cached.",
908
- "Lis le fichier .md via Read pour recuperer le resultat.",
909
- "APEX: Read sur cache MCP compte comme research-expert satisfait.",
906
+ "# MCP cache available this session",
907
+ "Before calling mcp__context7/exa, check if the result is already cached.",
908
+ "Read the .md file via Read to retrieve the result.",
909
+ "APEX: reading the MCP cache via Read satisfies the research-expert step.",
910
910
  "",
911
911
  "| Tool | Query | File |",
912
912
  "| --- | --- | --- |"
@@ -1233,7 +1233,7 @@ function pad(n) {
1233
1233
  return String(n).padStart(2, "0");
1234
1234
  }
1235
1235
  /** Compact local timestamp `YYYYMMDD-HHMMSS` (mirrors Python strftime). */
1236
- function stamp(now) {
1236
+ function stamp$1(now) {
1237
1237
  const d = new Date(now);
1238
1238
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1239
1239
  }
@@ -1250,7 +1250,7 @@ function saveApexState(cwd, now = Date.now()) {
1250
1250
  if (!existsSync(stateFile)) return "";
1251
1251
  const backupDir = join(apexDir, "backups");
1252
1252
  mkdirSync(backupDir, { recursive: true });
1253
- copyFileSync(stateFile, join(backupDir, `task-${stamp(now)}.json`));
1253
+ copyFileSync(stateFile, join(backupDir, `task-${stamp$1(now)}.json`));
1254
1254
  const backups = readdirSync(backupDir).filter((n) => n.startsWith("task-") && n.endsWith(".json")).sort().reverse();
1255
1255
  for (const old of backups.slice(5)) try {
1256
1256
  rmSync(join(backupDir, old), { force: true });
@@ -2136,16 +2136,10 @@ function cartoSessionStart(cwd, now = Date.now()) {
2136
2136
  return ctx ? contextResponse("SessionStart", ctx) : "";
2137
2137
  }
2138
2138
  //#endregion
2139
- //#region src/runtime/lifecycle/aipilot/curate-lessons.ts
2140
- /**
2141
- * Mechanical, LLM-free curation of `MEMORY/LESSON.md` bullets (anti-obesity):
2142
- * strict-dedup near-identical bullets (keep newest, `[TRIGGERS …]` preserved),
2143
- * flag over-cap + stale (>90d, cited path gone) in a report. Only dedup writes.
2144
- */
2145
- const CAP = 50;
2146
- const STALE_DAYS = 90;
2147
- const SIM_THRESHOLD = .8;
2148
- const MIN_TOKENS = 4;
2139
+ //#region src/runtime/lifecycle/aipilot/lesson-parse.ts
2140
+ /** Milliseconds in a day. */
2141
+ const DAY_MS = 864e5;
2142
+ /** Case-sensitive decision-time tag line (`[TRIGGERS …]`) — opus-lessons format. */
2149
2143
  const TRIG = /^\[TRIGGERS\s+.+\]$/;
2150
2144
  /** Epoch ms for a `[YYYY-MM-DD HH:MM]` stamp; `NaN` if absent or out of range. */
2151
2145
  function parseTs$1(line) {
@@ -2172,7 +2166,11 @@ function citedPaths(text) {
2172
2166
  for (const m of text.matchAll(/[\w./@-]+\.\w{1,5}/g)) if (m[0]) out.add(m[0]);
2173
2167
  return [...out].filter((p) => p.includes("/") && /\.\w{1,5}$/.test(p));
2174
2168
  }
2175
- /** Split into a verbatim preamble and one Block per `- ` bullet. */
2169
+ /** True when a block carries a `[TRIGGERS …]` continuation line. */
2170
+ function hasTrigger(b) {
2171
+ return b.raw.some((l) => TRIG.test(l.trim()));
2172
+ }
2173
+ /** Split content into a verbatim preamble and one Block per `- ` bullet. */
2176
2174
  function parse(content) {
2177
2175
  const lines = content.split("\n");
2178
2176
  const blocks = [];
@@ -2193,23 +2191,89 @@ function parse(content) {
2193
2191
  blocks
2194
2192
  };
2195
2193
  }
2194
+ //#endregion
2195
+ //#region src/runtime/lifecycle/aipilot/lesson-archive.ts
2196
+ /**
2197
+ * Stage 1 — cap→archive split for LESSON.md. When deduped bullets exceed CAP the
2198
+ * OLDEST excess is MOVED (never deleted) to LESSON-archive.md, EXCEPT a
2199
+ * `[TRIGGERS …]` bullet younger than STALE_DAYS: archiving it would blind the
2200
+ * PreToolUse trigger index (src/policy/lessons/trigger-index reads LESSON.md), so
2201
+ * it stays even past the cap. Pure: this module decides the partition and renders
2202
+ * the archive block; the fail-safe, archive-first file write is the caller's job.
2203
+ */
2204
+ /** Sort key: undated bullets sort oldest, so malformed entries archive first. */
2205
+ function age(b) {
2206
+ return Number.isNaN(b.ts) ? -Infinity : b.ts;
2207
+ }
2208
+ /** A TRIGGERS bullet is protected from archival until older than STALE_DAYS. */
2209
+ function isProtected(b, staleBefore) {
2210
+ return hasTrigger(b) && !(b.ts <= staleBefore);
2211
+ }
2212
+ /**
2213
+ * Partition deduped `blocks` (newest-first file order) into the bullets that
2214
+ * stay in LESSON.md and the oldest excess to archive. Archives only enough to
2215
+ * reach CAP, skipping protected TRIGGERS bullets (so the file MAY stay slightly
2216
+ * over cap by design). Order is preserved in both halves; `keep ∪ archive` is
2217
+ * exactly `blocks` with no loss and no mutation.
2218
+ * @param blocks - Deduped bullets, newest first.
2219
+ * @param now - Clock (ms) for the STALE_DAYS protection window.
2220
+ * @returns `{ keep, archive }` — a lossless partition of `blocks`.
2221
+ */
2222
+ function splitAtCap(blocks, now) {
2223
+ if (blocks.length <= 50) return {
2224
+ keep: blocks,
2225
+ archive: []
2226
+ };
2227
+ const staleBefore = now - 90 * DAY_MS;
2228
+ const oldestFirst = [...blocks].sort((a, b) => age(a) - age(b));
2229
+ const toArchive = /* @__PURE__ */ new Set();
2230
+ let excess = blocks.length - 50;
2231
+ for (const b of oldestFirst) {
2232
+ if (excess <= 0) break;
2233
+ if (isProtected(b, staleBefore)) continue;
2234
+ toArchive.add(b);
2235
+ excess--;
2236
+ }
2237
+ return {
2238
+ keep: blocks.filter((b) => !toArchive.has(b)),
2239
+ archive: blocks.filter((b) => toArchive.has(b))
2240
+ };
2241
+ }
2242
+ /**
2243
+ * Render `archive` bullets as a dated block to PREPEND to LESSON-archive.md
2244
+ * (newest archive session on top). Bullets are emitted BYTE-IDENTICAL (raw lines
2245
+ * rejoined) — zero mutation, so the move stays reversible/auditable.
2246
+ * @param archive - Bullets chosen by {@link splitAtCap}.
2247
+ * @param now - Clock (ms) for the archival header date.
2248
+ * @returns The block text (trailing newline), or "" when nothing is archived.
2249
+ */
2250
+ function formatArchive(archive, now) {
2251
+ if (archive.length === 0) return "";
2252
+ return `${`<!-- archived ${new Date(now).toISOString().slice(0, 10)}: ${archive.length} bullet(s) moved from LESSON.md at cap 50 -->`}\n${archive.map((b) => b.raw.join("\n")).join("\n\n")}\n`;
2253
+ }
2254
+ //#endregion
2255
+ //#region src/runtime/lifecycle/aipilot/curate-lessons.ts
2256
+ /**
2257
+ * Stage-0 mechanical, LLM-free dedup of MEMORY/LESSON.md bullets + cap→archive
2258
+ * orchestration. Strict-dedup near-identical bullets (keep newest, `[TRIGGERS …]`
2259
+ * preserved), then hand the deduped set to lesson-archive's cap split. Returns the
2260
+ * rewritten LESSON.md content, the archive block to move out, and a human report.
2261
+ * Pure: all file I/O (archive-first, fail-safe) lives in the dispatch caller.
2262
+ */
2263
+ const SIM_THRESHOLD = .8;
2264
+ const MIN_TOKENS = 4;
2196
2265
  /** Report lines for bullets older than STALE_DAYS whose only cited path is gone. */
2197
2266
  function staleReport(blocks, now, root) {
2198
- const cutoff = now - STALE_DAYS * 864e5;
2267
+ const cutoff = now - 90 * DAY_MS;
2199
2268
  return blocks.flatMap((b) => {
2200
2269
  if (!(b.ts <= cutoff)) return [];
2201
2270
  const paths = citedPaths(b.raw.join(" "));
2202
2271
  if (paths.length === 0 || paths.some((p) => existsSync(join(root, p)))) return [];
2203
- return [`[STALE?] ${(b.raw[0] ?? "").slice(0, 90)} — chemin(s) disparu(s): ${paths.join(", ")}`];
2272
+ return [`[STALE?] ${(b.raw[0] ?? "").slice(0, 90)} — missing path(s): ${paths.join(", ")}`];
2204
2273
  });
2205
2274
  }
2206
- /**
2207
- * Strict-dedup LESSON.md bullets (keep newest, its `[TRIGGERS …]` line preserved —
2208
- * or carried over from the dropped twin if the kept one lacks it) and report
2209
- * cap/stale. `content` unchanged unless a dedup occurred. Returns content + report.
2210
- */
2211
- function curateLessons(content, now, root = process.cwd()) {
2212
- const { preamble, blocks } = parse(content);
2275
+ /** Strict-dedup: keep the newest of each near-identical pair (TRIGGERS carried over). Returns kept blocks + merge report lines. */
2276
+ function dedup(blocks) {
2213
2277
  const kept = [];
2214
2278
  const fused = [];
2215
2279
  for (const b of blocks) {
@@ -2224,20 +2288,76 @@ function curateLessons(content, now, root = process.cwd()) {
2224
2288
  const t = drop.raw.find((l) => TRIG.test(l.trim()));
2225
2289
  if (t) win.raw.push(t);
2226
2290
  }
2227
- fused.push(`fusion: gardé ${(win.raw[0] ?? "").slice(0, 60)} · retiré ${(drop.raw[0] ?? "").slice(0, 60)}`);
2291
+ fused.push(`merged: kept ${(win.raw[0] ?? "").slice(0, 60)} · dropped ${(drop.raw[0] ?? "").slice(0, 60)}`);
2228
2292
  }
2229
- const old = [...kept].sort((a, b) => a.ts - b.ts).slice(0, Math.max(0, kept.length - CAP));
2230
- const cap = old.length ? [`${kept.length} bullets (> ${CAP}) — plus anciens candidats à l'archivage:`, ...old.map((b) => ` ${(b.raw[0] ?? "").slice(0, 80)}`)] : [];
2293
+ return {
2294
+ kept,
2295
+ fused
2296
+ };
2297
+ }
2298
+ /**
2299
+ * Dedup LESSON.md bullets, then archive the oldest excess over CAP (via
2300
+ * lesson-archive). `content` is byte-identical to the input when nothing is
2301
+ * deduped or archived. The `archive` block (possibly "") is what the caller must
2302
+ * PREPEND to LESSON-archive.md, archive-first, before writing `content`.
2303
+ * @param content - Raw LESSON.md text.
2304
+ * @param now - Clock (ms) for stale/archival windows.
2305
+ * @param root - Project root, for resolving cited paths in the stale report.
2306
+ * @returns The rewritten content, the archive block, and the report.
2307
+ */
2308
+ function curateLessons(content, now, root = process.cwd()) {
2309
+ const { preamble, blocks } = parse(content);
2310
+ const { kept, fused } = dedup(blocks);
2311
+ const { keep, archive } = splitAtCap(kept, now);
2312
+ const rebuilt = fused.length > 0 || archive.length > 0 ? `${preamble}\n${keep.map((b) => b.raw.join("\n")).join("\n\n")}\n` : content;
2313
+ const capReport = archive.length ? [`${kept.length} bullets (> 50) — ${archive.length} oldest archived → LESSON-archive.md`] : [];
2231
2314
  const report = [
2232
2315
  ...fused,
2233
- ...cap,
2316
+ ...capReport,
2234
2317
  ...staleReport(blocks, now, root)
2235
2318
  ].join("\n");
2236
2319
  return {
2237
- content: fused.length ? `${preamble}\n${kept.map((b) => b.raw.join("\n")).join("\n\n")}\n` : content,
2320
+ content: rebuilt,
2321
+ archive: formatArchive(archive, now),
2238
2322
  report
2239
2323
  };
2240
2324
  }
2325
+ /** The `[YYYY-MM-DD HH:MM]` (or date-only) stamp of a bullet, "" if absent. */
2326
+ function stamp(block) {
2327
+ return (block.raw[0] ?? "").match(/\[(\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2})?)\]/)?.[1] ?? "";
2328
+ }
2329
+ /** Bullet text: raw lines joined, leading "- ", date stamp & TRIGGERS lines stripped. */
2330
+ function bodyText(block) {
2331
+ return block.raw.filter((l) => !/^\s*\[TRIGGERS\s/.test(l)).join(" ").replace(/^-\s*/, "").replace(/\[\d{4}-\d{2}-\d{2}[^\]]*\]\s*/, "").trim();
2332
+ }
2333
+ /** First sentence of `s` (split on a period + whitespace), whole string if none. */
2334
+ function firstSentence(s) {
2335
+ return (s.split(/(?<=\.)\s/)[0] ?? s).trim();
2336
+ }
2337
+ /** Collapse one older bullet to `- [date] <rule>`: first sentence after last "→", capped. */
2338
+ function compressBullet(block) {
2339
+ const text = bodyText(block);
2340
+ const arrow = text.lastIndexOf("→");
2341
+ let rule = firstSentence((arrow >= 0 ? text.slice(arrow + 1) : text).trim());
2342
+ if (rule.length > 200) rule = `${rule.slice(0, 199).trimEnd()}…`;
2343
+ const date = stamp(block);
2344
+ return `- ${date ? `[${date}] ` : ""}${rule}`;
2345
+ }
2346
+ /**
2347
+ * Build the compressed injection body for `content`. The preamble comments are
2348
+ * dropped (format docs, noise for the reader); the `recentFull` newest bullets
2349
+ * stay whole, every older bullet becomes one distilled rule-line.
2350
+ * @param content - Raw LESSON.md text.
2351
+ * @param recentFull - Count of newest bullets to keep verbatim.
2352
+ * @returns The compressed block (bullets only), or the trimmed content when there are no bullets.
2353
+ */
2354
+ function compressInjection(content, recentFull = 10) {
2355
+ const { blocks } = parse(content);
2356
+ if (blocks.length === 0) return content.trim();
2357
+ const full = blocks.slice(0, recentFull).map((b) => b.raw.join("\n"));
2358
+ const rest = blocks.slice(recentFull).map(compressBullet);
2359
+ return [...full, ...rest].join("\n");
2360
+ }
2241
2361
  //#endregion
2242
2362
  //#region src/runtime/lifecycle/lessons/state.ts
2243
2363
  /**
@@ -2251,6 +2371,10 @@ function curateLessons(content, now, root = process.cwd()) {
2251
2371
  function lessonsFileFor(root) {
2252
2372
  return join(root, "MEMORY", "LESSON.md");
2253
2373
  }
2374
+ /** Absolute `<root>/MEMORY/LESSON-archive.md` — cold storage for capped-out bullets. */
2375
+ function lessonsArchiveFileFor(root) {
2376
+ return join(root, "MEMORY", "LESSON-archive.md");
2377
+ }
2254
2378
  /** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
2255
2379
  function lessonsStateFileFor(root) {
2256
2380
  return join(root, "MEMORY", "state.json");
@@ -2419,7 +2543,32 @@ function markWrite(payload, now) {
2419
2543
  * in {@link module:runtime/lifecycle/lessons/reminder}; this module keeps the
2420
2544
  * event router + lesson-file injection. Non-fatal by design.
2421
2545
  */
2422
- /** Inject `MEMORY/LESSON.md` for `event`, after mechanical curation (a strict dedup rewrites the file in place; any report surfaces to the user via systemMessage). */
2546
+ /**
2547
+ * Persist a curation ATOMICALLY and ARCHIVE-FIRST for zero-loss: prepend the
2548
+ * moved bullets to LESSON-archive.md, THEN rewrite LESSON.md. On ANY write error
2549
+ * the original file is left untouched (returns `original`) so a bullet is never
2550
+ * lost — a rare archive-then-trim-fail leaves a duplicate (never a loss), which
2551
+ * the next dedup pass reconciles.
2552
+ */
2553
+ function persistCuration(file, root, curated, archive, original) {
2554
+ try {
2555
+ if (archive) {
2556
+ const af = lessonsArchiveFileFor(root);
2557
+ const prev = existsSync(af) ? readFileSync(af, "utf-8") : "";
2558
+ atomicWrite(af, prev ? `${archive}\n${prev}` : archive);
2559
+ }
2560
+ atomicWrite(file, curated);
2561
+ return curated;
2562
+ } catch {
2563
+ return original;
2564
+ }
2565
+ }
2566
+ /**
2567
+ * Inject `MEMORY/LESSON.md` for `event`. Mechanical curation (dedup + cap→archive)
2568
+ * rewrites the FILE; the injected BLOCK is then COMPRESSED (newest bullets whole,
2569
+ * older ones distilled to their rule) so a growing file never inflates the
2570
+ * SessionStart/SubagentStart context. Any curation report surfaces via systemMessage.
2571
+ */
2423
2572
  function injectMemory(cwd, event, now) {
2424
2573
  const root = projectRoot(cwd);
2425
2574
  const file = lessonsFileFor(root);
@@ -2431,12 +2580,9 @@ function injectMemory(cwd, event, now) {
2431
2580
  return "";
2432
2581
  }
2433
2582
  if (!content) return "";
2434
- const { content: curated, report } = curateLessons(content, now, root);
2435
- if (curated !== content) try {
2436
- atomicWrite(file, curated);
2437
- content = curated;
2438
- } catch {}
2439
- const ctx = `Project lessons — never reproduce these:\n${content}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`;
2583
+ const { content: curated, archive, report } = curateLessons(content, now, root);
2584
+ if (curated !== content) content = persistCuration(file, root, curated, archive, content);
2585
+ const ctx = `Project lessons — never reproduce these:\n${compressInjection(content)}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`;
2440
2586
  return report ? attachSystemMessage(contextResponse(event, ctx), `LESSON.md curation:\n${report}`) : contextResponse(event, ctx);
2441
2587
  }
2442
2588
  /**
@@ -2518,7 +2664,7 @@ function denyLoopCheck(hash, priorDenies, opts) {
2518
2664
  * @returns A block prompt with `[REPEAT]` title, STOP-prefixed reason, forced research action.
2519
2665
  */
2520
2666
  function enrichRepeatDeny(prompt, count) {
2521
- const stop = `Tentative identique n°${count} déjà refusée pour la même raison. STOP: ne retente pas ce même appel. `;
2667
+ const stop = `Identical attempt #${count} already denied for the same reason. STOP: do not retry this same call. `;
2522
2668
  const action = "Launch fuse-ai-pilot:research-expert to find a DIFFERENT approach";
2523
2669
  return {
2524
2670
  ...prompt,
@@ -5894,7 +6040,7 @@ function docSourceOf$1(tool) {
5894
6040
  function cacheHitText(web, hit) {
5895
6041
  const kb = Math.floor(hit.body.length / 1024) + 1;
5896
6042
  const hours = Math.floor(hit.ageMs / 36e5);
5897
- return web ? `CACHE HIT WebFetch (~${kb}KB economise, cached il y a ${hours}h):\n\n${hit.body}\n\nPour forcer un nouveau fetch, modifie l'URL ou la query.` : `CACHE HIT (~${kb}KB economise, cached il y a ${hours}h): ${hit.body}\n\nReformule pour forcer un re-call.`;
6043
+ return web ? `CACHE HIT WebFetch (~${kb}KB saved, cached ${hours}h ago):\n\n${hit.body}\n\nModify the URL or query to force a fresh fetch.` : `CACHE HIT (~${kb}KB saved, cached ${hours}h ago): ${hit.body}\n\nRephrase to force a re-call.`;
5898
6044
  }
5899
6045
  /**
5900
6046
  * Pre-event MCP interception: serve a fresh cache hit (deny + cached content),
@@ -7038,4 +7184,4 @@ async function handleHook(id, payload, opts) {
7038
7184
  });
7039
7185
  }
7040
7186
  //#endregion
7041
- export { trackSessionChanges as $, trackSkillRead as A, normalizeEvent as At, lessonsStateFileFor as B, seoPostToolUse as C, projectContext as Ct, postTrackingSideEffects as D, defaultStateDir as Dt, securityAdvisory as E, taskContext as Et, runDoctor as F, securityStatePath as Ft, isProject as G, generateEcosystemMap as H, runningVersion as I, todayUtc as It, mergeLines as J, writeTree as K, versionBanner as L, dispatchLifecycle as M, loadSecurityState as Mt, aipilotPostToolUse as N, saveSecurityState as Nt, trackWatchResearch as O, projectHash$1 as Ot, dispatchAipilot as P, securityStateDir as Pt, postEditTypescript as Q, dispatchLessons as R, postEditContext as S, gitContext as St, dispatchMemory as T, promptSubmitContext as Tt, writePluginMap as U, cartoSessionStart as V, generateProjectMap as W, getFileDesc as X, countFiles as Y, listChildren as Z, preCommitGate as _, pruneEmptyDirs as _t, recordActivity as a, trackAgentMemory as at, extractSymbols as b, trimLogFile as bt, MCP_TTL_MS as c, validateSolidGate as ct, isMcpTool as d, detectSolidProfile as dt, validateRulesLoaded as et, queryOf as f, solidDetectStart as ft, gate as g, sessionStartCore as gt, TRIVIAL_BUDGET as h, runSessionStartCleanups as ht, respond as i, validateTeammateOutput as it, trackEnrichment as j, isoUtc as jt, trackMcpResearch as k, trackFile as kt, WEBFETCH_TTL_MS as l, checkFileSize as lt, REQUIRED_AGENTS as m, readRules as mt, activityFor as n, saveApexState as nt, mcpPostStore as o, subagentCacheContext as ot, DEFAULT_WINDOW_MS as p, injectRules as pt, loadEnriched as q, handlePre as r, logToolFailure as rt, mcpPreIntercept as s, validateTailwind as st, handleHook as t, cleanupSession as tt, cacheQueryOf as u, countLoc as ut, detectDuplication as v, purgeTtlTree as vt, seoPostToolUseResponse as w, claudeMdKey as wt, lifecycleStdout as x, devContext as xt, dryGate as y, removeOldFiles as yt, lessonsFileFor as z };
7187
+ export { postEditTypescript as $, trackSkillRead as A, trackFile as At, lessonsFileFor as B, seoPostToolUse as C, gitContext as Ct, postTrackingSideEffects as D, taskContext as Dt, securityAdvisory as E, promptSubmitContext as Et, runDoctor as F, securityStateDir as Ft, generateProjectMap as G, cartoSessionStart as H, runningVersion as I, securityStatePath as It, loadEnriched as J, isProject as K, versionBanner as L, todayUtc as Lt, dispatchLifecycle as M, isoUtc as Mt, aipilotPostToolUse as N, loadSecurityState as Nt, trackWatchResearch as O, defaultStateDir as Ot, dispatchAipilot as P, saveSecurityState as Pt, listChildren as Q, dispatchLessons as R, postEditContext as S, devContext as St, dispatchMemory as T, claudeMdKey as Tt, generateEcosystemMap as U, lessonsStateFileFor as V, writePluginMap as W, countFiles as X, mergeLines as Y, getFileDesc as Z, preCommitGate as _, sessionStartCore as _t, recordActivity as a, validateTeammateOutput as at, extractSymbols as b, removeOldFiles as bt, MCP_TTL_MS as c, validateTailwind as ct, isMcpTool as d, countLoc as dt, trackSessionChanges as et, queryOf as f, detectSolidProfile as ft, gate as g, runSessionStartCleanups as gt, TRIVIAL_BUDGET as h, readRules as ht, respond as i, logToolFailure as it, trackEnrichment as j, normalizeEvent as jt, trackMcpResearch as k, projectHash$1 as kt, WEBFETCH_TTL_MS as l, validateSolidGate as lt, REQUIRED_AGENTS as m, injectRules as mt, activityFor as n, cleanupSession as nt, mcpPostStore as o, trackAgentMemory as ot, DEFAULT_WINDOW_MS as p, solidDetectStart as pt, writeTree as q, handlePre as r, saveApexState as rt, mcpPreIntercept as s, subagentCacheContext as st, handleHook as t, validateRulesLoaded as tt, cacheQueryOf as u, checkFileSize as ut, detectDuplication as v, pruneEmptyDirs as vt, seoPostToolUseResponse as w, projectContext as wt, lifecycleStdout as x, trimLogFile as xt, dryGate as y, purgeTtlTree as yt, lessonsArchiveFileFor as z };
@@ -692,6 +692,8 @@ declare function dispatchLessons(event: string, payload: Record<string, unknown>
692
692
  //#region src/runtime/lifecycle/lessons/state.d.ts
693
693
  /** Absolute `<root>/MEMORY/LESSON.md` — the curated, committable lessons file. */
694
694
  declare function lessonsFileFor(root: string): string;
695
+ /** Absolute `<root>/MEMORY/LESSON-archive.md` — cold storage for capped-out bullets. */
696
+ declare function lessonsArchiveFileFor(root: string): string;
695
697
  /** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
696
698
  declare function lessonsStateFileFor(root: string): string;
697
699
  //#endregion
@@ -826,4 +828,4 @@ interface PreContext {
826
828
  */
827
829
  declare function handlePre(ctx: PreContext): Promise<HandleOutcome>;
828
830
  //#endregion
829
- export { Activity, DEFAULT_WINDOW_MS, DuplicationVerdict, type GateInput, HandleOptions, HandleOutcome, LifecycleInput, MCP_TTL_MS, McpIntercept, NormalizedEvent, PluginScope, PreContext, REQUIRED_AGENTS, SolidProfile, TRIVIAL_BUDGET, ToolEvent, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
831
+ export { Activity, DEFAULT_WINDOW_MS, DuplicationVerdict, type GateInput, HandleOptions, HandleOutcome, LifecycleInput, MCP_TTL_MS, McpIntercept, NormalizedEvent, PluginScope, PreContext, REQUIRED_AGENTS, SolidProfile, TRIVIAL_BUDGET, ToolEvent, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsArchiveFileFor, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
@@ -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 trackSessionChanges, A as trackSkillRead, At as normalizeEvent, B as lessonsStateFileFor, C as seoPostToolUse, Ct as projectContext, D as postTrackingSideEffects, Dt as defaultStateDir, E as securityAdvisory, Et as taskContext, Ft as securityStatePath, G as isProject, H as generateEcosystemMap, It as todayUtc, J as mergeLines, K as writeTree, M as dispatchLifecycle, Mt as loadSecurityState, N as aipilotPostToolUse, Nt as saveSecurityState, O as trackWatchResearch, Ot as projectHash, P as dispatchAipilot, Pt as securityStateDir, Q as postEditTypescript, R as dispatchLessons, S as postEditContext, St as gitContext, T as dispatchMemory, Tt as promptSubmitContext, U as writePluginMap, V as cartoSessionStart, W as generateProjectMap, X as getFileDesc, Y as countFiles, Z as listChildren, _ as preCommitGate, _t as pruneEmptyDirs, a as recordActivity, at as trackAgentMemory, b as extractSymbols, bt as trimLogFile, c as MCP_TTL_MS, ct as validateSolidGate, d as isMcpTool, dt as detectSolidProfile, et as validateRulesLoaded, f as queryOf, ft as solidDetectStart, g as gate, gt as sessionStartCore, h as TRIVIAL_BUDGET, ht as runSessionStartCleanups, i as respond, it as validateTeammateOutput, j as trackEnrichment, jt as isoUtc, k as trackMcpResearch, kt as trackFile, l as WEBFETCH_TTL_MS, lt as checkFileSize, m as REQUIRED_AGENTS, mt as readRules, n as activityFor, nt as saveApexState, o as mcpPostStore, ot as subagentCacheContext, p as DEFAULT_WINDOW_MS, pt as injectRules, q as loadEnriched, r as handlePre, rt as logToolFailure, s as mcpPreIntercept, st as validateTailwind, t as handleHook, tt as cleanupSession, u as cacheQueryOf, ut as countLoc, v as detectDuplication, vt as purgeTtlTree, w as seoPostToolUseResponse, wt as claudeMdKey, x as lifecycleStdout, xt as devContext, y as dryGate, yt as removeOldFiles, z as lessonsFileFor } from "../handle-D93-VIGS.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-CVvp1yuc.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -10,4 +10,4 @@ function harnessStateDir(root) {
10
10
  return projectLayout(root).stateDir;
11
11
  }
12
12
  //#endregion
13
- export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
13
+ export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, WEBFETCH_TTL_MS, activityFor, aipilotPostToolUse, cacheQueryOf, cartoSessionStart, checkFileSize, claudeHome, claudeMdKey, cleanupSession, countFiles, countLoc, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, fuseHarnessHome, fusengineCache, gate, generateEcosystemMap, generateProjectMap, getFileDesc, gitContext, handleHook, handlePre, harnessStateDir, injectRules, isMcpTool, isProject, isoUtc, lessonsArchiveFileFor, lessonsFileFor, lessonsStateFileFor, lifecycleStdout, listChildren, loadEnriched, loadSecurityState, loadSessionState, logToolFailure, mcpPostStore, mcpPreIntercept, mergeLines, normalizeEvent, postEditContext, postEditTypescript, postTrackingSideEffects, preCommitGate, projectContext, projectHash, promptSubmitContext, pruneEmptyDirs, purgeTtlTree, queryOf, readRules, recordActivity, removeOldFiles, respond, runSessionStartCleanups, sanitizeSessionId, saveApexState, saveSecurityState, saveSessionState, securityAdvisory, securityStateDir, securityStatePath, seoPostToolUse, seoPostToolUseResponse, sessionStartCore, sessionStatePath, sessionsDir, solidDetectStart, subagentCacheContext, taskContext, todayUtc, trackAgentMemory, trackEnrichment, trackFile, trackMcpResearch, trackSessionChanges, trackSkillRead, trackWatchResearch, trimLogFile, validateRulesLoaded, validateSolidGate, validateTailwind, validateTeammateOutput, writePluginMap, writeTree };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
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",