@fusengine/harness 0.1.53 → 0.1.55
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-L4ZNmpwN.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 });
|
|
@@ -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
|
|
@@ -2136,16 +2162,10 @@ function cartoSessionStart(cwd, now = Date.now()) {
|
|
|
2136
2162
|
return ctx ? contextResponse("SessionStart", ctx) : "";
|
|
2137
2163
|
}
|
|
2138
2164
|
//#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;
|
|
2165
|
+
//#region src/runtime/lifecycle/aipilot/lesson-parse.ts
|
|
2166
|
+
/** Milliseconds in a day. */
|
|
2167
|
+
const DAY_MS = 864e5;
|
|
2168
|
+
/** Case-sensitive decision-time tag line (`[TRIGGERS …]`) — opus-lessons format. */
|
|
2149
2169
|
const TRIG = /^\[TRIGGERS\s+.+\]$/;
|
|
2150
2170
|
/** Epoch ms for a `[YYYY-MM-DD HH:MM]` stamp; `NaN` if absent or out of range. */
|
|
2151
2171
|
function parseTs$1(line) {
|
|
@@ -2172,7 +2192,11 @@ function citedPaths(text) {
|
|
|
2172
2192
|
for (const m of text.matchAll(/[\w./@-]+\.\w{1,5}/g)) if (m[0]) out.add(m[0]);
|
|
2173
2193
|
return [...out].filter((p) => p.includes("/") && /\.\w{1,5}$/.test(p));
|
|
2174
2194
|
}
|
|
2175
|
-
/**
|
|
2195
|
+
/** True when a block carries a `[TRIGGERS …]` continuation line. */
|
|
2196
|
+
function hasTrigger(b) {
|
|
2197
|
+
return b.raw.some((l) => TRIG.test(l.trim()));
|
|
2198
|
+
}
|
|
2199
|
+
/** Split content into a verbatim preamble and one Block per `- ` bullet. */
|
|
2176
2200
|
function parse(content) {
|
|
2177
2201
|
const lines = content.split("\n");
|
|
2178
2202
|
const blocks = [];
|
|
@@ -2193,23 +2217,89 @@ function parse(content) {
|
|
|
2193
2217
|
blocks
|
|
2194
2218
|
};
|
|
2195
2219
|
}
|
|
2220
|
+
//#endregion
|
|
2221
|
+
//#region src/runtime/lifecycle/aipilot/lesson-archive.ts
|
|
2222
|
+
/**
|
|
2223
|
+
* Stage 1 — cap→archive split for LESSON.md. When deduped bullets exceed CAP the
|
|
2224
|
+
* OLDEST excess is MOVED (never deleted) to LESSON-archive.md, EXCEPT a
|
|
2225
|
+
* `[TRIGGERS …]` bullet younger than STALE_DAYS: archiving it would blind the
|
|
2226
|
+
* PreToolUse trigger index (src/policy/lessons/trigger-index reads LESSON.md), so
|
|
2227
|
+
* it stays even past the cap. Pure: this module decides the partition and renders
|
|
2228
|
+
* the archive block; the fail-safe, archive-first file write is the caller's job.
|
|
2229
|
+
*/
|
|
2230
|
+
/** Sort key: undated bullets sort oldest, so malformed entries archive first. */
|
|
2231
|
+
function age(b) {
|
|
2232
|
+
return Number.isNaN(b.ts) ? -Infinity : b.ts;
|
|
2233
|
+
}
|
|
2234
|
+
/** A TRIGGERS bullet is protected from archival until older than STALE_DAYS. */
|
|
2235
|
+
function isProtected(b, staleBefore) {
|
|
2236
|
+
return hasTrigger(b) && !(b.ts <= staleBefore);
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* Partition deduped `blocks` (newest-first file order) into the bullets that
|
|
2240
|
+
* stay in LESSON.md and the oldest excess to archive. Archives only enough to
|
|
2241
|
+
* reach CAP, skipping protected TRIGGERS bullets (so the file MAY stay slightly
|
|
2242
|
+
* over cap by design). Order is preserved in both halves; `keep ∪ archive` is
|
|
2243
|
+
* exactly `blocks` with no loss and no mutation.
|
|
2244
|
+
* @param blocks - Deduped bullets, newest first.
|
|
2245
|
+
* @param now - Clock (ms) for the STALE_DAYS protection window.
|
|
2246
|
+
* @returns `{ keep, archive }` — a lossless partition of `blocks`.
|
|
2247
|
+
*/
|
|
2248
|
+
function splitAtCap(blocks, now) {
|
|
2249
|
+
if (blocks.length <= 50) return {
|
|
2250
|
+
keep: blocks,
|
|
2251
|
+
archive: []
|
|
2252
|
+
};
|
|
2253
|
+
const staleBefore = now - 90 * DAY_MS;
|
|
2254
|
+
const oldestFirst = [...blocks].sort((a, b) => age(a) - age(b));
|
|
2255
|
+
const toArchive = /* @__PURE__ */ new Set();
|
|
2256
|
+
let excess = blocks.length - 50;
|
|
2257
|
+
for (const b of oldestFirst) {
|
|
2258
|
+
if (excess <= 0) break;
|
|
2259
|
+
if (isProtected(b, staleBefore)) continue;
|
|
2260
|
+
toArchive.add(b);
|
|
2261
|
+
excess--;
|
|
2262
|
+
}
|
|
2263
|
+
return {
|
|
2264
|
+
keep: blocks.filter((b) => !toArchive.has(b)),
|
|
2265
|
+
archive: blocks.filter((b) => toArchive.has(b))
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Render `archive` bullets as a dated block to PREPEND to LESSON-archive.md
|
|
2270
|
+
* (newest archive session on top). Bullets are emitted BYTE-IDENTICAL (raw lines
|
|
2271
|
+
* rejoined) — zero mutation, so the move stays reversible/auditable.
|
|
2272
|
+
* @param archive - Bullets chosen by {@link splitAtCap}.
|
|
2273
|
+
* @param now - Clock (ms) for the archival header date.
|
|
2274
|
+
* @returns The block text (trailing newline), or "" when nothing is archived.
|
|
2275
|
+
*/
|
|
2276
|
+
function formatArchive(archive, now) {
|
|
2277
|
+
if (archive.length === 0) return "";
|
|
2278
|
+
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`;
|
|
2279
|
+
}
|
|
2280
|
+
//#endregion
|
|
2281
|
+
//#region src/runtime/lifecycle/aipilot/curate-lessons.ts
|
|
2282
|
+
/**
|
|
2283
|
+
* Stage-0 mechanical, LLM-free dedup of MEMORY/LESSON.md bullets + cap→archive
|
|
2284
|
+
* orchestration. Strict-dedup near-identical bullets (keep newest, `[TRIGGERS …]`
|
|
2285
|
+
* preserved), then hand the deduped set to lesson-archive's cap split. Returns the
|
|
2286
|
+
* rewritten LESSON.md content, the archive block to move out, and a human report.
|
|
2287
|
+
* Pure: all file I/O (archive-first, fail-safe) lives in the dispatch caller.
|
|
2288
|
+
*/
|
|
2289
|
+
const SIM_THRESHOLD = .8;
|
|
2290
|
+
const MIN_TOKENS = 4;
|
|
2196
2291
|
/** Report lines for bullets older than STALE_DAYS whose only cited path is gone. */
|
|
2197
2292
|
function staleReport(blocks, now, root) {
|
|
2198
|
-
const cutoff = now -
|
|
2293
|
+
const cutoff = now - 90 * DAY_MS;
|
|
2199
2294
|
return blocks.flatMap((b) => {
|
|
2200
2295
|
if (!(b.ts <= cutoff)) return [];
|
|
2201
2296
|
const paths = citedPaths(b.raw.join(" "));
|
|
2202
2297
|
if (paths.length === 0 || paths.some((p) => existsSync(join(root, p)))) return [];
|
|
2203
|
-
return [`[STALE?] ${(b.raw[0] ?? "").slice(0, 90)} —
|
|
2298
|
+
return [`[STALE?] ${(b.raw[0] ?? "").slice(0, 90)} — missing path(s): ${paths.join(", ")}`];
|
|
2204
2299
|
});
|
|
2205
2300
|
}
|
|
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);
|
|
2301
|
+
/** Strict-dedup: keep the newest of each near-identical pair (TRIGGERS carried over). Returns kept blocks + merge report lines. */
|
|
2302
|
+
function dedup(blocks) {
|
|
2213
2303
|
const kept = [];
|
|
2214
2304
|
const fused = [];
|
|
2215
2305
|
for (const b of blocks) {
|
|
@@ -2224,20 +2314,91 @@ function curateLessons(content, now, root = process.cwd()) {
|
|
|
2224
2314
|
const t = drop.raw.find((l) => TRIG.test(l.trim()));
|
|
2225
2315
|
if (t) win.raw.push(t);
|
|
2226
2316
|
}
|
|
2227
|
-
fused.push(`
|
|
2317
|
+
fused.push(`merged: kept ${(win.raw[0] ?? "").slice(0, 60)} · dropped ${(drop.raw[0] ?? "").slice(0, 60)}`);
|
|
2228
2318
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2319
|
+
return {
|
|
2320
|
+
kept,
|
|
2321
|
+
fused
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Dedup LESSON.md bullets, then archive the oldest excess over CAP (via
|
|
2326
|
+
* lesson-archive). `content` is byte-identical to the input when nothing is
|
|
2327
|
+
* deduped or archived. The `archive` block (possibly "") is what the caller must
|
|
2328
|
+
* PREPEND to LESSON-archive.md, archive-first, before writing `content`.
|
|
2329
|
+
* @param content - Raw LESSON.md text.
|
|
2330
|
+
* @param now - Clock (ms) for stale/archival windows.
|
|
2331
|
+
* @param root - Project root, for resolving cited paths in the stale report.
|
|
2332
|
+
* @returns The rewritten content, the archive block, and the report.
|
|
2333
|
+
*/
|
|
2334
|
+
function curateLessons(content, now, root = process.cwd()) {
|
|
2335
|
+
const { preamble, blocks } = parse(content);
|
|
2336
|
+
const { kept, fused } = dedup(blocks);
|
|
2337
|
+
const { keep, archive } = splitAtCap(kept, now);
|
|
2338
|
+
const rebuilt = fused.length > 0 || archive.length > 0 ? `${preamble}\n${keep.map((b) => b.raw.join("\n")).join("\n\n")}\n` : content;
|
|
2339
|
+
const capReport = archive.length ? [`${kept.length} bullets (> 50) — ${archive.length} oldest archived → LESSON-archive.md`] : [];
|
|
2231
2340
|
const report = [
|
|
2232
2341
|
...fused,
|
|
2233
|
-
...
|
|
2342
|
+
...capReport,
|
|
2234
2343
|
...staleReport(blocks, now, root)
|
|
2235
2344
|
].join("\n");
|
|
2236
2345
|
return {
|
|
2237
|
-
content:
|
|
2346
|
+
content: rebuilt,
|
|
2347
|
+
archive: formatArchive(archive, now),
|
|
2238
2348
|
report
|
|
2239
2349
|
};
|
|
2240
2350
|
}
|
|
2351
|
+
/** The `[YYYY-MM-DD HH:MM]` (or date-only) stamp of a bullet, "" if absent. */
|
|
2352
|
+
function stamp(block) {
|
|
2353
|
+
return (block.raw[0] ?? "").match(/\[(\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2})?)\]/)?.[1] ?? "";
|
|
2354
|
+
}
|
|
2355
|
+
/** Bullet text: raw lines joined, leading "- ", date stamp & TRIGGERS lines stripped. */
|
|
2356
|
+
function bodyText(block) {
|
|
2357
|
+
return block.raw.filter((l) => !/^\s*\[TRIGGERS\s/.test(l)).join(" ").replace(/^-\s*/, "").replace(/\[\d{4}-\d{2}-\d{2}[^\]]*\]\s*/, "").trim();
|
|
2358
|
+
}
|
|
2359
|
+
/** First sentence of `s` (split on a period + whitespace), whole string if none. */
|
|
2360
|
+
function firstSentence(s) {
|
|
2361
|
+
return (s.split(/(?<=\.)\s/)[0] ?? s).trim();
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Distil the actionable rule from a bullet body. With no "→" the whole bullet is
|
|
2365
|
+
* the rule → its first sentence. Otherwise the rule is everything after the FIRST
|
|
2366
|
+
* "→"; among its arrow-delimited segments (trimmed, empty dropped) take the first
|
|
2367
|
+
* sentence of the LONGEST — the information-dense clause, not whichever short
|
|
2368
|
+
* aside the author appended last. When that clause is under {@link MIN_RULE}
|
|
2369
|
+
* chars, fall back to the first sentence of the WHOLE rule part (never the
|
|
2370
|
+
* narrative), so a short trailing segment never yields an illegible stub yet a
|
|
2371
|
+
* legitimately terse rule is still shown intact.
|
|
2372
|
+
*/
|
|
2373
|
+
function distillRule(text) {
|
|
2374
|
+
const arrow = text.indexOf("→");
|
|
2375
|
+
if (arrow < 0) return firstSentence(text);
|
|
2376
|
+
const rulePart = text.slice(arrow + 1);
|
|
2377
|
+
const rule = firstSentence(rulePart.split("→").map((s) => s.trim()).filter(Boolean).reduce((a, b) => b.length > a.length ? b : a, ""));
|
|
2378
|
+
return rule.length >= 40 ? rule : firstSentence(rulePart);
|
|
2379
|
+
}
|
|
2380
|
+
/** Collapse one older bullet to `- [date] <rule>`: {@link distillRule}, capped. */
|
|
2381
|
+
function compressBullet(block) {
|
|
2382
|
+
let rule = distillRule(bodyText(block));
|
|
2383
|
+
if (rule.length > 200) rule = `${rule.slice(0, 199).trimEnd()}…`;
|
|
2384
|
+
const date = stamp(block);
|
|
2385
|
+
return `- ${date ? `[${date}] ` : ""}${rule}`;
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Build the compressed injection body for `content`. The preamble comments are
|
|
2389
|
+
* dropped (format docs, noise for the reader); the `recentFull` newest bullets
|
|
2390
|
+
* stay whole, every older bullet becomes one distilled rule-line.
|
|
2391
|
+
* @param content - Raw LESSON.md text.
|
|
2392
|
+
* @param recentFull - Count of newest bullets to keep verbatim.
|
|
2393
|
+
* @returns The compressed block (bullets only), or the trimmed content when there are no bullets.
|
|
2394
|
+
*/
|
|
2395
|
+
function compressInjection(content, recentFull = 10) {
|
|
2396
|
+
const { blocks } = parse(content);
|
|
2397
|
+
if (blocks.length === 0) return content.trim();
|
|
2398
|
+
const full = blocks.slice(0, recentFull).map((b) => b.raw.join("\n"));
|
|
2399
|
+
const rest = blocks.slice(recentFull).map(compressBullet);
|
|
2400
|
+
return [...full, ...rest].join("\n");
|
|
2401
|
+
}
|
|
2241
2402
|
//#endregion
|
|
2242
2403
|
//#region src/runtime/lifecycle/lessons/state.ts
|
|
2243
2404
|
/**
|
|
@@ -2251,6 +2412,10 @@ function curateLessons(content, now, root = process.cwd()) {
|
|
|
2251
2412
|
function lessonsFileFor(root) {
|
|
2252
2413
|
return join(root, "MEMORY", "LESSON.md");
|
|
2253
2414
|
}
|
|
2415
|
+
/** Absolute `<root>/MEMORY/LESSON-archive.md` — cold storage for capped-out bullets. */
|
|
2416
|
+
function lessonsArchiveFileFor(root) {
|
|
2417
|
+
return join(root, "MEMORY", "LESSON-archive.md");
|
|
2418
|
+
}
|
|
2254
2419
|
/** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
|
|
2255
2420
|
function lessonsStateFileFor(root) {
|
|
2256
2421
|
return join(root, "MEMORY", "state.json");
|
|
@@ -2419,7 +2584,32 @@ function markWrite(payload, now) {
|
|
|
2419
2584
|
* in {@link module:runtime/lifecycle/lessons/reminder}; this module keeps the
|
|
2420
2585
|
* event router + lesson-file injection. Non-fatal by design.
|
|
2421
2586
|
*/
|
|
2422
|
-
/**
|
|
2587
|
+
/**
|
|
2588
|
+
* Persist a curation ATOMICALLY and ARCHIVE-FIRST for zero-loss: prepend the
|
|
2589
|
+
* moved bullets to LESSON-archive.md, THEN rewrite LESSON.md. On ANY write error
|
|
2590
|
+
* the original file is left untouched (returns `original`) so a bullet is never
|
|
2591
|
+
* lost — a rare archive-then-trim-fail leaves a duplicate (never a loss), which
|
|
2592
|
+
* the next dedup pass reconciles.
|
|
2593
|
+
*/
|
|
2594
|
+
function persistCuration(file, root, curated, archive, original) {
|
|
2595
|
+
try {
|
|
2596
|
+
if (archive) {
|
|
2597
|
+
const af = lessonsArchiveFileFor(root);
|
|
2598
|
+
const prev = existsSync(af) ? readFileSync(af, "utf-8") : "";
|
|
2599
|
+
atomicWrite(af, prev ? `${archive}\n${prev}` : archive);
|
|
2600
|
+
}
|
|
2601
|
+
atomicWrite(file, curated);
|
|
2602
|
+
return curated;
|
|
2603
|
+
} catch {
|
|
2604
|
+
return original;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Inject `MEMORY/LESSON.md` for `event`. Mechanical curation (dedup + cap→archive)
|
|
2609
|
+
* rewrites the FILE; the injected BLOCK is then COMPRESSED (newest bullets whole,
|
|
2610
|
+
* older ones distilled to their rule) so a growing file never inflates the
|
|
2611
|
+
* SessionStart/SubagentStart context. Any curation report surfaces via systemMessage.
|
|
2612
|
+
*/
|
|
2423
2613
|
function injectMemory(cwd, event, now) {
|
|
2424
2614
|
const root = projectRoot(cwd);
|
|
2425
2615
|
const file = lessonsFileFor(root);
|
|
@@ -2431,12 +2621,9 @@ function injectMemory(cwd, event, now) {
|
|
|
2431
2621
|
return "";
|
|
2432
2622
|
}
|
|
2433
2623
|
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.`;
|
|
2624
|
+
const { content: curated, archive, report } = curateLessons(content, now, root);
|
|
2625
|
+
if (curated !== content) content = persistCuration(file, root, curated, archive, content);
|
|
2626
|
+
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
2627
|
return report ? attachSystemMessage(contextResponse(event, ctx), `LESSON.md curation:\n${report}`) : contextResponse(event, ctx);
|
|
2441
2628
|
}
|
|
2442
2629
|
/**
|
|
@@ -2495,14 +2682,32 @@ function denyHash(tool, input) {
|
|
|
2495
2682
|
/**
|
|
2496
2683
|
* Pure loop check: given the already-pruned in-window map, compute the running
|
|
2497
2684
|
* count for `hash` and whether it repeats (count > 1). No IO — the caller persists.
|
|
2498
|
-
*
|
|
2685
|
+
*
|
|
2686
|
+
* When `dedupMs` is set (>0) and an identical prior deny landed within that
|
|
2687
|
+
* window, the current call is a sibling hook echoing the SAME event (see
|
|
2688
|
+
* {@link module:burst-window}): it returns the prior verdict VERBATIM with
|
|
2689
|
+
* `deduped:true` and does NOT bump the count, so all N fan-out processes agree
|
|
2690
|
+
* on one number instead of counting to N. Absent `dedupMs` (mono-process
|
|
2691
|
+
* callers / unit tests) the historical increment-every-time behaviour holds.
|
|
2692
|
+
* @param hash - {@link denyHash}-derived map key of the current call.
|
|
2499
2693
|
* @param priorDenies - The `{ hash -> DenyEntry }` map, pruned to `now`/`windowMs`.
|
|
2500
|
-
* @param opts - Clock + window.
|
|
2501
|
-
* @returns `{ isRepeat, count, hash }`.
|
|
2694
|
+
* @param opts - Clock + window, plus an optional burst-dedup window.
|
|
2695
|
+
* @returns `{ isRepeat, count, hash, deduped? }`.
|
|
2502
2696
|
*/
|
|
2503
2697
|
function denyLoopCheck(hash, priorDenies, opts) {
|
|
2504
2698
|
const prev = priorDenies[hash];
|
|
2505
|
-
|
|
2699
|
+
if (!(prev && typeof prev.lastTs === "number" && opts.now - prev.lastTs < opts.windowMs)) return {
|
|
2700
|
+
isRepeat: false,
|
|
2701
|
+
count: 1,
|
|
2702
|
+
hash
|
|
2703
|
+
};
|
|
2704
|
+
if ((opts.dedupMs ?? 0) > 0 && opts.now - prev.lastTs < (opts.dedupMs ?? 0)) return {
|
|
2705
|
+
isRepeat: prev.count > 1,
|
|
2706
|
+
count: prev.count,
|
|
2707
|
+
hash,
|
|
2708
|
+
deduped: true
|
|
2709
|
+
};
|
|
2710
|
+
const count = prev.count + 1;
|
|
2506
2711
|
return {
|
|
2507
2712
|
isRepeat: count > 1,
|
|
2508
2713
|
count,
|
|
@@ -2518,7 +2723,7 @@ function denyLoopCheck(hash, priorDenies, opts) {
|
|
|
2518
2723
|
* @returns A block prompt with `[REPEAT]` title, STOP-prefixed reason, forced research action.
|
|
2519
2724
|
*/
|
|
2520
2725
|
function enrichRepeatDeny(prompt, count) {
|
|
2521
|
-
const stop = `
|
|
2726
|
+
const stop = `Identical attempt #${count} already denied for the same reason. STOP: do not retry this same call. `;
|
|
2522
2727
|
const action = "Launch fuse-ai-pilot:research-expert to find a DIFFERENT approach";
|
|
2523
2728
|
return {
|
|
2524
2729
|
...prompt,
|
|
@@ -2636,6 +2841,39 @@ function formatSummary(s) {
|
|
|
2636
2841
|
return `gates 7d: ${head}${parts.length ? `; ${parts.join("; ")}` : ""}`;
|
|
2637
2842
|
}
|
|
2638
2843
|
//#endregion
|
|
2844
|
+
//#region src/tracking/one-shot-dedup.ts
|
|
2845
|
+
/**
|
|
2846
|
+
* @module one-shot-dedup
|
|
2847
|
+
* Burst-dedup guard for the one-shot metric ({@link module:one-shot}).
|
|
2848
|
+
*
|
|
2849
|
+
* ONE Claude tool event fans out to ~11 sibling plugin-hook processes, each
|
|
2850
|
+
* calling {@link recordOneShot}; without this the metric would count a single
|
|
2851
|
+
* deny/allow ~11×. Reuses the proven {@link oncePerWindow} cooldown sidecar:
|
|
2852
|
+
* the FIRST process in the {@link module:burst-window} window mutates the
|
|
2853
|
+
* metric, the rest skip. The dedup key includes the outcome KIND (deny-title vs
|
|
2854
|
+
* allow) so a deny and its later fix — different kinds — are never folded into
|
|
2855
|
+
* each other. No `sessionId` → always the first (mono-process + unit-test
|
|
2856
|
+
* parity; a burst can only exist when a real session drives the fan-out).
|
|
2857
|
+
* @packageDocumentation
|
|
2858
|
+
*/
|
|
2859
|
+
/**
|
|
2860
|
+
* True when this `(op, kind)` is the FIRST of its burst for the session — the
|
|
2861
|
+
* process that should actually mutate the metric. Sibling processes firing the
|
|
2862
|
+
* SAME event within {@link BURST_DEDUP_MS} return false and skip the write.
|
|
2863
|
+
* @param op - Content-free operation key ({@link denyHash}("op", …)).
|
|
2864
|
+
* @param kind - Outcome discriminator (`deny:<title>` or `allow`).
|
|
2865
|
+
* @param opts - Clock + state dir + optional session id.
|
|
2866
|
+
* @returns `true` to apply the record, `false` to skip (already counted).
|
|
2867
|
+
*/
|
|
2868
|
+
function burstFirst(op, kind, opts) {
|
|
2869
|
+
const sid = opts.sessionId?.trim();
|
|
2870
|
+
if (!sid) return true;
|
|
2871
|
+
return oncePerWindow(`oneshot:${sid}:${op}:${kind}`, BURST_DEDUP_MS, {
|
|
2872
|
+
now: opts.now,
|
|
2873
|
+
dir: opts.dir
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
//#endregion
|
|
2639
2877
|
//#region src/tracking/one-shot.ts
|
|
2640
2878
|
/**
|
|
2641
2879
|
* @module one-shot
|
|
@@ -2683,12 +2921,13 @@ function loadState(path) {
|
|
|
2683
2921
|
function recordOneShot(prompt, input, opts) {
|
|
2684
2922
|
try {
|
|
2685
2923
|
if (prompt && prompt.kind !== "block") return;
|
|
2686
|
-
const path = join(opts.dir, SIDECAR$1);
|
|
2687
|
-
let s = pruneState(loadState(path), opts.now, WINDOW_MS);
|
|
2688
2924
|
const op = denyHash("op", {
|
|
2689
2925
|
filePath: input.filePath,
|
|
2690
2926
|
command: input.command
|
|
2691
2927
|
});
|
|
2928
|
+
if (!burstFirst(op, prompt ? `deny:${prompt.title}` : "allow", opts)) return;
|
|
2929
|
+
const path = join(opts.dir, SIDECAR$1);
|
|
2930
|
+
let s = pruneState(loadState(path), opts.now, WINDOW_MS);
|
|
2692
2931
|
s = prompt ? applyDeny(s, prompt.title, op, opts.now) : applyAllow(s, op, opts.now, input.content != null || input.command != null);
|
|
2693
2932
|
atomicWrite(path, JSON.stringify(s));
|
|
2694
2933
|
} catch {}
|
|
@@ -5708,17 +5947,28 @@ function prune(map, now, windowMs) {
|
|
|
5708
5947
|
*/
|
|
5709
5948
|
function recordDeny(tool, input, opts) {
|
|
5710
5949
|
const hash = denyHash(tool, input);
|
|
5950
|
+
const sid = opts.sessionId?.trim();
|
|
5951
|
+
const key = sid ? `${hash}::${sid}` : hash;
|
|
5711
5952
|
const path = join(opts.dir, SIDECAR);
|
|
5712
5953
|
const map = prune(loadMap(path), opts.now, opts.windowMs);
|
|
5713
|
-
const res = denyLoopCheck(
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5954
|
+
const res = denyLoopCheck(key, map, {
|
|
5955
|
+
now: opts.now,
|
|
5956
|
+
windowMs: opts.windowMs,
|
|
5957
|
+
dedupMs: sid ? BURST_DEDUP_MS : 0
|
|
5958
|
+
});
|
|
5959
|
+
if (!res.deduped) {
|
|
5960
|
+
map[key] = {
|
|
5961
|
+
count: res.count,
|
|
5962
|
+
lastTs: opts.now
|
|
5963
|
+
};
|
|
5964
|
+
try {
|
|
5965
|
+
atomicWrite(path, JSON.stringify(map));
|
|
5966
|
+
} catch {}
|
|
5967
|
+
}
|
|
5968
|
+
return {
|
|
5969
|
+
...res,
|
|
5970
|
+
hash
|
|
5717
5971
|
};
|
|
5718
|
-
try {
|
|
5719
|
-
atomicWrite(path, JSON.stringify(map));
|
|
5720
|
-
} catch {}
|
|
5721
|
-
return res;
|
|
5722
5972
|
}
|
|
5723
5973
|
/**
|
|
5724
5974
|
* Gate tail: record every block deny; on a repeat, return the enriched prompt.
|
|
@@ -5763,12 +6013,14 @@ async function gate(input) {
|
|
|
5763
6013
|
const dir = dirname(input.trackFile);
|
|
5764
6014
|
recordOneShot(prompt, op, {
|
|
5765
6015
|
now: input.now,
|
|
5766
|
-
dir
|
|
6016
|
+
dir,
|
|
6017
|
+
sessionId: input.sessionId
|
|
5767
6018
|
});
|
|
5768
6019
|
return withDenyLoop(prompt, input.tool, op, {
|
|
5769
6020
|
now: input.now,
|
|
5770
6021
|
dir,
|
|
5771
|
-
windowMs: input.windowMs ?? 12e4
|
|
6022
|
+
windowMs: input.windowMs ?? 12e4,
|
|
6023
|
+
sessionId: input.sessionId
|
|
5772
6024
|
});
|
|
5773
6025
|
}
|
|
5774
6026
|
/** Stateless guards, then the trivial fast path, then the stateful APEX gates. */
|
|
@@ -5894,7 +6146,7 @@ function docSourceOf$1(tool) {
|
|
|
5894
6146
|
function cacheHitText(web, hit) {
|
|
5895
6147
|
const kb = Math.floor(hit.body.length / 1024) + 1;
|
|
5896
6148
|
const hours = Math.floor(hit.ageMs / 36e5);
|
|
5897
|
-
return web ? `CACHE HIT WebFetch (~${kb}KB
|
|
6149
|
+
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
6150
|
}
|
|
5899
6151
|
/**
|
|
5900
6152
|
* Pre-event MCP interception: serve a fresh cache hit (deny + cached content),
|
|
@@ -7038,4 +7290,4 @@ async function handleHook(id, payload, opts) {
|
|
|
7038
7290
|
});
|
|
7039
7291
|
}
|
|
7040
7292
|
//#endregion
|
|
7041
|
-
export {
|
|
7293
|
+
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-L4ZNmpwN.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.55",
|
|
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",
|