@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,
|
|
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
|
|
907
|
-
"
|
|
908
|
-
"
|
|
909
|
-
"APEX:
|
|
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/
|
|
2140
|
-
/**
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
-
/**
|
|
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 -
|
|
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)} —
|
|
2272
|
+
return [`[STALE?] ${(b.raw[0] ?? "").slice(0, 90)} — missing path(s): ${paths.join(", ")}`];
|
|
2204
2273
|
});
|
|
2205
2274
|
}
|
|
2206
|
-
/**
|
|
2207
|
-
|
|
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(`
|
|
2291
|
+
fused.push(`merged: kept ${(win.raw[0] ?? "").slice(0, 60)} · dropped ${(drop.raw[0] ?? "").slice(0, 60)}`);
|
|
2228
2292
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
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
|
-
...
|
|
2316
|
+
...capReport,
|
|
2234
2317
|
...staleReport(blocks, now, root)
|
|
2235
2318
|
].join("\n");
|
|
2236
2319
|
return {
|
|
2237
|
-
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
|
-
/**
|
|
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)
|
|
2436
|
-
|
|
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 = `
|
|
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
|
|
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 {
|
|
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 };
|
package/dist/runtime/index.d.mts
CHANGED
|
@@ -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 };
|
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
|
|
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.
|
|
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",
|