@fusengine/harness 0.1.34 → 0.1.36

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
@@ -3,13 +3,85 @@ import { r as resolveTtlSec } from "../ttl-BG55s6HZ.mjs";
3
3
  import { t as detectHarness } from "../harness-C8Nxxyn_.mjs";
4
4
  import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-CUL70W0k.mjs";
5
5
  import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
6
- import { t as handleHook } from "../handle-DMHjTY1E.mjs";
6
+ import { Dt as claudeHome, Et as todayUtc, t as handleHook } from "../handle-DL2sZiWH.mjs";
7
+ import { join } from "node:path";
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ //#region src/changelog/fetch.ts
11
+ /**
12
+ * Changelog scanner — ports the changelog-watcher plugin's `fetch-changelog`
13
+ * into the harness (exposed as the `harness changelog` CLI verb). Fetches the
14
+ * official Claude Code changelog, detects how many versions are new since the
15
+ * last check, persists per-day state, and returns a JSON summary. Dual-runtime:
16
+ * global `fetch` + `node:fs` (works under Node 20+ and Bun, no imports needed).
17
+ */
18
+ const CHANGELOG_URL = "https://code.claude.com/docs/en/changelog.md";
19
+ /**
20
+ * Parse up to 10 semver versions from the changelog, newest first. Matches the
21
+ * current docs format (`<Update label="X.Y.Z" …>` MDX blocks) AND the legacy
22
+ * markdown headers (`## vX.Y.Z` / `## X.Y.Z`) so it survives a format rollback.
23
+ */
24
+ function parseVersions(md) {
25
+ return [...md.matchAll(/<Update\s+label="v?(\d+\.\d+\.\d+)"|^##\s+v?(\d+\.\d+\.\d+)/gm)].map((m) => m[1] ?? m[2] ?? "").filter(Boolean).slice(0, 10);
26
+ }
27
+ /** Count versions newer than `lastKnown` (stops at the first match). */
28
+ function countNew(versions, lastKnown) {
29
+ if (!lastKnown) return 0;
30
+ let n = 0;
31
+ for (const v of versions) {
32
+ if (v === lastKnown) break;
33
+ n++;
34
+ }
35
+ return n;
36
+ }
37
+ /** Read the saved `last_version` for today's state file ("" when absent/corrupt). */
38
+ function lastKnownVersion(stateFile) {
39
+ if (!existsSync(stateFile)) return "";
40
+ try {
41
+ return JSON.parse(readFileSync(stateFile, "utf8")).last_version ?? "";
42
+ } catch {
43
+ return "";
44
+ }
45
+ }
46
+ /**
47
+ * Fetch + parse the changelog, diff against the saved state, persist, and return
48
+ * the scan summary. Throws on network failure (the CLI maps it to exit 1).
49
+ * @param now - Clock (ms).
50
+ * @param home - Home dir.
51
+ */
52
+ async function scanChangelog(now = Date.now(), home = homedir()) {
53
+ const res = await fetch(CHANGELOG_URL, { signal: AbortSignal.timeout(1e4) });
54
+ if (!res.ok) throw new Error(`changelog fetch failed: ${res.status}`);
55
+ const versions = parseVersions(await res.text());
56
+ const latest = versions[0] ?? "";
57
+ const dir = join(claudeHome(home), "logs", "00-changelog");
58
+ const today = todayUtc(now);
59
+ const stateFile = join(dir, `${today}-state.json`);
60
+ const lastKnown = lastKnownVersion(stateFile);
61
+ const newCount = countNew(versions, lastKnown);
62
+ try {
63
+ mkdirSync(dir, { recursive: true });
64
+ writeFileSync(stateFile, JSON.stringify({
65
+ last_version: latest,
66
+ previous: lastKnown,
67
+ new_versions: newCount,
68
+ checked: today
69
+ }, null, 2));
70
+ } catch {}
71
+ return {
72
+ latest,
73
+ new_since_last_check: newCount,
74
+ recent_versions: versions
75
+ };
76
+ }
77
+ //#endregion
7
78
  //#region src/cli/bin.ts
8
79
  /**
9
80
  * harness — CLI for @fusengine/harness.
10
81
  * harness check cli-mode: check staged files (pre-commit), exit non-zero on a violation
11
82
  * harness init [id] write the wiring file for a harness (defaults to the detected one)
12
83
  * harness hook <id> runtime: read a hook payload on stdin, route to the adapter, print the response
84
+ * harness changelog fetch + diff the Claude Code changelog, print a JSON summary (changelog-watcher)
13
85
  */
14
86
  async function readStdin() {
15
87
  const chunks = [];
@@ -35,7 +107,8 @@ if (cmd === "hook") {
35
107
  "changelog",
36
108
  "aipilot",
37
109
  "lessons",
38
- "seo"
110
+ "seo",
111
+ "memory"
39
112
  ])).has(scopeArg) ? scopeArg : "core";
40
113
  const outcome = await handleHook(id, await readStdin(), {
41
114
  now: Date.now(),
@@ -56,7 +129,17 @@ if (cmd === "hook") {
56
129
  const written = files.map((f) => writeInitFile(process.cwd(), f));
57
130
  process.stdout.write(`harness: wired ${id} -> ${written.join(", ")}\n`);
58
131
  process.exit(0);
59
- } else {
132
+ } else if (cmd === "changelog") try {
133
+ process.stdout.write(JSON.stringify(await scanChangelog()) + "\n");
134
+ process.exit(0);
135
+ } catch (e) {
136
+ process.stdout.write(JSON.stringify({
137
+ status: "error",
138
+ message: e instanceof Error ? e.message : "changelog fetch failed"
139
+ }) + "\n");
140
+ process.exit(1);
141
+ }
142
+ else {
60
143
  const files = stagedFiles();
61
144
  if (files.length === 0) process.exit(0);
62
145
  const violations = checkStaged(files, stagedContent);
@@ -16,6 +16,88 @@ import { homedir } from "node:os";
16
16
  import { createHash } from "node:crypto";
17
17
  import { mkdir, rmdir } from "node:fs/promises";
18
18
  import { execFileSync } from "node:child_process";
19
+ //#region src/runtime/home-state.ts
20
+ /** Home `~/.claude` dir (single source for every home-based hook path). */
21
+ function claudeHome(home = homedir()) {
22
+ return join(home, ".claude");
23
+ }
24
+ /** `~/.claude/fusengine-cache` base dir for legacy session/cache state. */
25
+ function fusengineCache(home = homedir()) {
26
+ return join(claudeHome(home), "fusengine-cache");
27
+ }
28
+ /** `~/.claude/fusengine-cache/sessions` — per-session JSON state dir. */
29
+ function sessionsDir(home = homedir()) {
30
+ return join(fusengineCache(home), "sessions");
31
+ }
32
+ const SID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
33
+ /** Validate a session id (1-128 url-safe chars); null when invalid. */
34
+ function sanitizeSessionId(sid) {
35
+ const s = String(sid ?? "").trim();
36
+ return SID_RE.test(s) ? s : null;
37
+ }
38
+ /** Unified per-session state file path: `sessions/session-<sid>.json`. */
39
+ function sessionStatePath(sid, home = homedir()) {
40
+ return join(sessionsDir(home), `session-${sid}.json`);
41
+ }
42
+ /** Load a session-state dict, or `{}` when missing/corrupt (mirrors Python). */
43
+ function loadSessionState(sid, home = homedir()) {
44
+ const path = sessionStatePath(sid, home);
45
+ try {
46
+ if (!existsSync(path)) return {};
47
+ const data = JSON.parse(readFileSync(path, "utf-8"));
48
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
49
+ } catch {
50
+ return {};
51
+ }
52
+ }
53
+ /** Atomically persist a session-state dict (0o600 via atomicWrite, indent 2). */
54
+ function saveSessionState(sid, state, home = homedir()) {
55
+ mkdirSync(sessionsDir(home), {
56
+ recursive: true,
57
+ mode: 448
58
+ });
59
+ atomicWrite(sessionStatePath(sid, home), JSON.stringify(state, null, 2));
60
+ }
61
+ //#endregion
62
+ //#region src/runtime/lifecycle/security/skill-state.ts
63
+ /**
64
+ * Shared security-tracker state: per-UTC-day JSON under
65
+ * `~/.claude/logs/00-security`. Ports the state helpers of
66
+ * `check-security-skill.py` / `track-skill-read.py` / `track-mcp-research.py`.
67
+ */
68
+ /** `~/.claude/logs/00-security` state directory. */
69
+ function securityStateDir(home = homedir()) {
70
+ return join(claudeHome(home), "logs", "00-security");
71
+ }
72
+ /** Current UTC date as `YYYY-MM-DD`. */
73
+ function todayUtc(now = Date.now()) {
74
+ return new Date(now).toISOString().slice(0, 10);
75
+ }
76
+ /** Current UTC instant as `YYYY-MM-DDTHH:MM:SSZ` (seconds, no millis). */
77
+ function isoUtc(now = Date.now()) {
78
+ return new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z");
79
+ }
80
+ /** Today's security-state file path. */
81
+ function securityStatePath(now = Date.now(), home = homedir()) {
82
+ return join(securityStateDir(home), `${todayUtc(now)}-state.json`);
83
+ }
84
+ /** Load today's security state, or `{}` when missing/corrupt. */
85
+ function loadSecurityState(now = Date.now(), home = homedir()) {
86
+ const path = securityStatePath(now, home);
87
+ try {
88
+ if (!existsSync(path)) return {};
89
+ const data = JSON.parse(readFileSync(path, "utf-8"));
90
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
91
+ } catch {
92
+ return {};
93
+ }
94
+ }
95
+ /** Persist today's security state (indent 2, no trailing newline). */
96
+ function saveSecurityState(state, now = Date.now(), home = homedir()) {
97
+ mkdirSync(securityStateDir(home), { recursive: true });
98
+ writeFileSync(securityStatePath(now, home), JSON.stringify(state, null, 2), "utf-8");
99
+ }
100
+ //#endregion
19
101
  //#region src/runtime/activity.ts
20
102
  /** Min response length (chars) for a lead agent call to count as `sufficient`. */
21
103
  const AGENT_QUALITY_MIN = 500;
@@ -671,49 +753,6 @@ function taskContext(cwd) {
671
753
  return ctx ? contextResponse("PreToolUse", ctx) : "";
672
754
  }
673
755
  //#endregion
674
- //#region src/runtime/home-state.ts
675
- /** Home `~/.claude` dir (single source for every home-based hook path). */
676
- function claudeHome(home = homedir()) {
677
- return join(home, ".claude");
678
- }
679
- /** `~/.claude/fusengine-cache` base dir for legacy session/cache state. */
680
- function fusengineCache(home = homedir()) {
681
- return join(claudeHome(home), "fusengine-cache");
682
- }
683
- /** `~/.claude/fusengine-cache/sessions` — per-session JSON state dir. */
684
- function sessionsDir(home = homedir()) {
685
- return join(fusengineCache(home), "sessions");
686
- }
687
- const SID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
688
- /** Validate a session id (1-128 url-safe chars); null when invalid. */
689
- function sanitizeSessionId(sid) {
690
- const s = String(sid ?? "").trim();
691
- return SID_RE.test(s) ? s : null;
692
- }
693
- /** Unified per-session state file path: `sessions/session-<sid>.json`. */
694
- function sessionStatePath(sid, home = homedir()) {
695
- return join(sessionsDir(home), `session-${sid}.json`);
696
- }
697
- /** Load a session-state dict, or `{}` when missing/corrupt (mirrors Python). */
698
- function loadSessionState(sid, home = homedir()) {
699
- const path = sessionStatePath(sid, home);
700
- try {
701
- if (!existsSync(path)) return {};
702
- const data = JSON.parse(readFileSync(path, "utf-8"));
703
- return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
704
- } catch {
705
- return {};
706
- }
707
- }
708
- /** Atomically persist a session-state dict (0o600 via atomicWrite, indent 2). */
709
- function saveSessionState(sid, state, home = homedir()) {
710
- mkdirSync(sessionsDir(home), {
711
- recursive: true,
712
- mode: 448
713
- });
714
- atomicWrite(sessionStatePath(sid, home), JSON.stringify(state, null, 2));
715
- }
716
- //#endregion
717
756
  //#region src/runtime/dev-context.ts
718
757
  /** Run a git subcommand in `cwd`, returning trimmed stdout or "" on error. */
719
758
  function git(cwd, args) {
@@ -1258,7 +1297,7 @@ function trackSessionChanges(sessionIdRaw, filePath, home = homedir(), now = Dat
1258
1297
  //#endregion
1259
1298
  //#region src/runtime/lifecycle/post-edit-ts.ts
1260
1299
  const TS_EXT$1 = /\.(ts|tsx)$/;
1261
- const TIMEOUT_MS$1 = 1e4;
1300
+ const TIMEOUT_MS$2 = 1e4;
1262
1301
  /** True when `bin` is resolvable on PATH (mirrors shutil.which). */
1263
1302
  function hasBin(bin) {
1264
1303
  try {
@@ -1279,7 +1318,7 @@ function run(bin, args) {
1279
1318
  out: execFileSync(bin, args, {
1280
1319
  encoding: "utf-8",
1281
1320
  stdio: "pipe",
1282
- timeout: TIMEOUT_MS$1
1321
+ timeout: TIMEOUT_MS$2
1283
1322
  })
1284
1323
  };
1285
1324
  } catch (err) {
@@ -3017,45 +3056,6 @@ function trackEnrichment(filePath) {
3017
3056
  } catch {}
3018
3057
  }
3019
3058
  //#endregion
3020
- //#region src/runtime/lifecycle/security/skill-state.ts
3021
- /**
3022
- * Shared security-tracker state: per-UTC-day JSON under
3023
- * `~/.claude/logs/00-security`. Ports the state helpers of
3024
- * `check-security-skill.py` / `track-skill-read.py` / `track-mcp-research.py`.
3025
- */
3026
- /** `~/.claude/logs/00-security` state directory. */
3027
- function securityStateDir(home = homedir()) {
3028
- return join(claudeHome(home), "logs", "00-security");
3029
- }
3030
- /** Current UTC date as `YYYY-MM-DD`. */
3031
- function todayUtc(now = Date.now()) {
3032
- return new Date(now).toISOString().slice(0, 10);
3033
- }
3034
- /** Current UTC instant as `YYYY-MM-DDTHH:MM:SSZ` (seconds, no millis). */
3035
- function isoUtc(now = Date.now()) {
3036
- return new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z");
3037
- }
3038
- /** Today's security-state file path. */
3039
- function securityStatePath(now = Date.now(), home = homedir()) {
3040
- return join(securityStateDir(home), `${todayUtc(now)}-state.json`);
3041
- }
3042
- /** Load today's security state, or `{}` when missing/corrupt. */
3043
- function loadSecurityState(now = Date.now(), home = homedir()) {
3044
- const path = securityStatePath(now, home);
3045
- try {
3046
- if (!existsSync(path)) return {};
3047
- const data = JSON.parse(readFileSync(path, "utf-8"));
3048
- return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
3049
- } catch {
3050
- return {};
3051
- }
3052
- }
3053
- /** Persist today's security state (indent 2, no trailing newline). */
3054
- function saveSecurityState(state, now = Date.now(), home = homedir()) {
3055
- mkdirSync(securityStateDir(home), { recursive: true });
3056
- writeFileSync(securityStatePath(now, home), JSON.stringify(state, null, 2), "utf-8");
3057
- }
3058
- //#endregion
3059
3059
  //#region src/runtime/lifecycle/security/track-skill-read.ts
3060
3060
  /**
3061
3061
  * Security skill-read tracker (PostToolUse Read). Ports `track-skill-read.py`:
@@ -3204,6 +3204,260 @@ function securityAdvisory(tool, filePath, now = Date.now(), home = homedir()) {
3204
3204
  } });
3205
3205
  }
3206
3206
  //#endregion
3207
+ //#region src/runtime/lifecycle/memory/client.ts
3208
+ /**
3209
+ * Graphiti neural-memory HTTP client (best-effort). Ports the urllib calls in
3210
+ * the memory-neural scripts: POST /episodes (store) + POST /search (recall).
3211
+ * Every call swallows network errors and honors a 5s timeout, so a hook never
3212
+ * fails or hangs when the Graphiti server is absent.
3213
+ */
3214
+ const TIMEOUT_MS$1 = 5e3;
3215
+ /** Base URL `http://<NEURAL_MEMORY_HOST>:<GRAPHITI_PORT>` (env-overridable). */
3216
+ function neuralBase(env = process.env) {
3217
+ return `http://${env.NEURAL_MEMORY_HOST ?? "localhost"}:${env.GRAPHITI_PORT ?? "8000"}`;
3218
+ }
3219
+ /** POST an episode to Graphiti `/episodes`. Resolves silently on any failure. */
3220
+ async function postEpisode(ep, env = process.env) {
3221
+ try {
3222
+ await fetch(`${neuralBase(env)}/episodes`, {
3223
+ method: "POST",
3224
+ headers: { "Content-Type": "application/json" },
3225
+ body: JSON.stringify(ep),
3226
+ signal: AbortSignal.timeout(TIMEOUT_MS$1)
3227
+ });
3228
+ } catch {}
3229
+ }
3230
+ /**
3231
+ * POST a query to Graphiti `/search`; returns hits or `[]` on any failure
3232
+ * (network error, timeout, non-2xx, malformed JSON).
3233
+ * @param query - The search query.
3234
+ * @param numResults - Max results requested.
3235
+ * @param env - Env (for host/port overrides).
3236
+ * @returns The recall hits, possibly empty.
3237
+ */
3238
+ async function searchMemory(query, numResults, env = process.env) {
3239
+ try {
3240
+ const resp = await fetch(`${neuralBase(env)}/search`, {
3241
+ method: "POST",
3242
+ headers: { "Content-Type": "application/json" },
3243
+ body: JSON.stringify({
3244
+ query,
3245
+ num_results: numResults
3246
+ }),
3247
+ signal: AbortSignal.timeout(TIMEOUT_MS$1)
3248
+ });
3249
+ if (!resp.ok) return [];
3250
+ const data = await resp.json();
3251
+ return Array.isArray(data.results) ? data.results : [];
3252
+ } catch {
3253
+ return [];
3254
+ }
3255
+ }
3256
+ /** Severity (1-10) of a Bash stderr by keyword (mirrors auto-capture-error). */
3257
+ function bashSeverity(stderr) {
3258
+ const s = stderr.toLowerCase();
3259
+ if (s.includes("fatal") || s.includes("panic")) return 10;
3260
+ if (s.includes("error") || s.includes("failed")) return 8;
3261
+ if (s.includes("warning")) return 4;
3262
+ if (s.includes("deprecated")) return 2;
3263
+ return 5;
3264
+ }
3265
+ /** Severity (1-10) of a finished agent by name (mirrors capture-agent-lesson). */
3266
+ function agentSeverity(name) {
3267
+ if (name === "sniper" || name === "sniper-faster") return 8;
3268
+ if (name === "research-expert") return 6;
3269
+ if (name.endsWith("-expert")) return 7;
3270
+ return 5;
3271
+ }
3272
+ /** Salience from severity: 0.40·sev/10 + 0.30 + 0.20·0.5 + 0.10·0.5. */
3273
+ function salience(severity) {
3274
+ return .4 * severity / 10 + .3 + .2 * .5 + .1 * .5;
3275
+ }
3276
+ //#endregion
3277
+ //#region src/runtime/lifecycle/memory/state.ts
3278
+ /**
3279
+ * fuse-memory-neural scope state: per-line logs under
3280
+ * `~/.claude/logs/00-memory` + project-type detection. Ports the shared
3281
+ * filesystem helpers of the four memory-neural scripts.
3282
+ */
3283
+ /** `~/.claude/logs/00-memory` log directory. */
3284
+ function memoryLogDir(home = homedir()) {
3285
+ return join(claudeHome(home), "logs", "00-memory");
3286
+ }
3287
+ /**
3288
+ * Append a line to a memory log file, creating the dir. When `rotateAt > 0` and
3289
+ * the file exceeds it, keep only the newest `keep` lines. Best-effort (errors
3290
+ * swallowed), so a hook never fails on a logging issue.
3291
+ * @param name - Log file name (e.g. `operations.log`).
3292
+ * @param line - Line to append (newline added).
3293
+ * @param rotateAt - Rotate when line count exceeds this (0 disables).
3294
+ * @param keep - Lines to keep on rotation.
3295
+ * @param home - Home dir.
3296
+ */
3297
+ function appendMemoryLog(name, line, rotateAt = 0, keep = 0, home = homedir()) {
3298
+ const dir = memoryLogDir(home);
3299
+ try {
3300
+ mkdirSync(dir, { recursive: true });
3301
+ const file = join(dir, name);
3302
+ appendFileSync(file, `${line}\n`, "utf-8");
3303
+ if (rotateAt > 0) {
3304
+ const lines = readFileSync(file, "utf-8").split("\n").filter((l) => l.length > 0);
3305
+ if (lines.length > rotateAt) writeFileSync(file, `${lines.slice(-keep).join("\n")}\n`, "utf-8");
3306
+ }
3307
+ } catch {}
3308
+ }
3309
+ /** Detect the project type from cwd markers (mirrors recall-on-session.py). */
3310
+ function detectProjectType(cwd) {
3311
+ for (const [f, t] of [
3312
+ ["package.json", "node"],
3313
+ ["composer.json", "php"],
3314
+ ["Package.swift", "swift"],
3315
+ ["Cargo.toml", "rust"],
3316
+ ["go.mod", "go"]
3317
+ ]) if (existsSync(join(cwd, f))) return t;
3318
+ if (existsSync(join(cwd, "requirements.txt")) || existsSync(join(cwd, "pyproject.toml"))) return "python";
3319
+ return "unknown";
3320
+ }
3321
+ //#endregion
3322
+ //#region src/runtime/lifecycle/memory/agent-lesson.ts
3323
+ /**
3324
+ * SubagentStop memory handler. Ports `capture-agent-lesson.py`: log a finished
3325
+ * agent's conclusion and, when salient enough, store it as a Graphiti episode.
3326
+ * Skips explore-codebase/websearch agents and errored exits.
3327
+ */
3328
+ /** Agents whose conclusions are never captured. */
3329
+ const SKIP = /* @__PURE__ */ new Set(["explore-codebase", "websearch"]);
3330
+ /**
3331
+ * Handle SubagentStop: log + maybe store the agent's conclusion. Side-effect
3332
+ * only (no stdout).
3333
+ * @param payload - The raw hook payload.
3334
+ * @param now - Clock.
3335
+ */
3336
+ async function captureAgentLesson(payload, now) {
3337
+ const name = typeof payload.agent_name === "string" ? payload.agent_name : "unknown";
3338
+ const lastMsg = typeof payload.last_assistant_message === "string" ? payload.last_assistant_message : "";
3339
+ const exitReason = typeof payload.exit_reason === "string" ? payload.exit_reason : "unknown";
3340
+ if (!lastMsg || exitReason === "error" || SKIP.has(name)) return;
3341
+ const lesson = lastMsg.slice(0, 1e3);
3342
+ const ts = isoUtc(now);
3343
+ appendMemoryLog("agent-lessons.log", `[${ts}] ${name} | ${exitReason} | ${lesson.slice(0, 80)}...`);
3344
+ if (salience(agentSeverity(name)) <= .3) return;
3345
+ await postEpisode({
3346
+ name: "agent_lesson",
3347
+ episode_body: `Agent ${name} conclusion: ${lesson}`,
3348
+ source_description: `agent-stop-${name}`,
3349
+ reference_time: ts
3350
+ });
3351
+ }
3352
+ //#endregion
3353
+ //#region src/runtime/lifecycle/memory/capture-error.ts
3354
+ /**
3355
+ * PostToolUse (Bash) memory handler. Ports `auto-capture-error.py`: on a
3356
+ * non-zero Bash exit with stderr, store an episode in Graphiti and surface a
3357
+ * `<memory-capture>` hint to search past errors / store the eventual solution.
3358
+ */
3359
+ /** Extract exit code + stderr from a PostToolUse Bash payload (either field). */
3360
+ function bashResult(payload) {
3361
+ const r = payload.tool_result ?? payload.tool_response;
3362
+ return {
3363
+ exit: String(r?.exit_code ?? "0"),
3364
+ stderr: typeof r?.stderr === "string" ? r.stderr : ""
3365
+ };
3366
+ }
3367
+ /**
3368
+ * Handle a Bash PostToolUse: capture a failed command's error in neural memory
3369
+ * and return the native additionalContext stdout (or "" when nothing to emit).
3370
+ * @param payload - The raw hook payload.
3371
+ * @param now - Clock.
3372
+ * @returns The native stdout (possibly empty).
3373
+ */
3374
+ async function captureBashError(payload, now) {
3375
+ const { exit, stderr } = bashResult(payload);
3376
+ if (exit === "0" || !stderr) return "";
3377
+ if (salience(bashSeverity(stderr)) <= .3) return "";
3378
+ const errorMsg = stderr.slice(0, 500);
3379
+ await postEpisode({
3380
+ name: "bash_error",
3381
+ episode_body: `Bash error (exit ${exit}): ${errorMsg}`,
3382
+ source_description: "auto-capture",
3383
+ reference_time: isoUtc(now)
3384
+ });
3385
+ return contextResponse("PostToolUse", `Error captured in neural memory (Graphiti).\nSearch for similar past errors: use mcp__qdrant__qdrant-find with query "${errorMsg}"\nIf you solve this, store the solution: use mcp__qdrant__qdrant-store`);
3386
+ }
3387
+ //#endregion
3388
+ //#region src/runtime/lifecycle/memory/track-ops.ts
3389
+ /**
3390
+ * PostToolUse (mcp__graphiti|mcp__qdrant) memory handler. Ports
3391
+ * `track-memory-ops.py`: append `[ts] <tool> | ok|error` to
3392
+ * `operations.log`, rotating at 1000 lines (keeping the newest 500).
3393
+ */
3394
+ /** Append a memory-operation log line for a graphiti/qdrant tool call. */
3395
+ function trackMemoryOp(payload, now) {
3396
+ const tool = typeof payload.tool_name === "string" ? payload.tool_name : "unknown";
3397
+ const status = (payload.tool_result ?? payload.tool_response)?.error ? "error" : "ok";
3398
+ appendMemoryLog("operations.log", `[${isoUtc(now)}] ${tool} | ${status}`, 1e3, 500);
3399
+ }
3400
+ //#endregion
3401
+ //#region src/runtime/lifecycle/memory/recall.ts
3402
+ /**
3403
+ * SessionStart memory handler. Ports `recall-on-session.py`: detect the project
3404
+ * type, recall relevant lessons from Graphiti, log the recall, and inject a
3405
+ * neural-memory-recall additionalContext block.
3406
+ */
3407
+ /**
3408
+ * Handle SessionStart: recall past lessons for this project and return the
3409
+ * native additionalContext stdout (or "" when there is nothing to recall).
3410
+ * @param cwd - Project root.
3411
+ * @param now - Clock.
3412
+ * @returns The native stdout (possibly empty).
3413
+ */
3414
+ async function recallOnSession(cwd, now) {
3415
+ const projectType = detectProjectType(cwd);
3416
+ const projectName = basename(cwd);
3417
+ const hits = await searchMemory(`${projectType} ${projectName} common errors`, 5);
3418
+ appendMemoryLog("recalls.log", `[${isoUtc(now)}] session_recall | ${projectType} | ${projectName}`);
3419
+ if (hits.length === 0) return "";
3420
+ return contextResponse("SessionStart", `Relevant lessons from past sessions:\n${hits.slice(0, 5).map((r) => `- ${r.content || r.name || "unknown"}`).join("\n")}\nFor deeper search: use mcp__qdrant__qdrant-find with project-specific queries.`);
3421
+ }
3422
+ //#endregion
3423
+ //#region src/runtime/lifecycle/memory/dispatch.ts
3424
+ /**
3425
+ * fuse-memory-neural scope dispatcher (async; the handlers hit Graphiti over
3426
+ * HTTP, best-effort). Routes by event: SessionStart recalls past lessons,
3427
+ * PostToolUse captures Bash errors / tracks graphiti+qdrant ops, SubagentStop
3428
+ * captures agent conclusions. Returns the native stdout when handled, or `null`
3429
+ * to fall through to the generic pipeline.
3430
+ */
3431
+ /** Is this a graphiti/qdrant MCP tool call? */
3432
+ function isMemoryTool(tool) {
3433
+ return tool.startsWith("mcp__graphiti") || tool.startsWith("mcp__qdrant");
3434
+ }
3435
+ /**
3436
+ * Dispatch a memory-scope lifecycle event to its ported handler.
3437
+ * @param event - Raw hook event name.
3438
+ * @param payload - Raw hook payload.
3439
+ * @param cwd - Project root.
3440
+ * @param now - Clock.
3441
+ * @returns The native stdout, or `null` when unhandled.
3442
+ */
3443
+ async function dispatchMemory(event, payload, cwd, now) {
3444
+ if (event === "SessionStart") return recallOnSession(cwd, now);
3445
+ if (event === "SubagentStop") {
3446
+ await captureAgentLesson(payload, now);
3447
+ return "";
3448
+ }
3449
+ if (event === "PostToolUse") {
3450
+ const tool = typeof payload.tool_name === "string" ? payload.tool_name : "";
3451
+ if (tool === "Bash") return captureBashError(payload, now);
3452
+ if (isMemoryTool(tool)) {
3453
+ trackMemoryOp(payload, now);
3454
+ return "";
3455
+ }
3456
+ return null;
3457
+ }
3458
+ return null;
3459
+ }
3460
+ //#endregion
3207
3461
  //#region src/runtime/lifecycle/seo/post-tool-use.ts
3208
3462
  /**
3209
3463
  * SEO PostToolUse handler (fs effects). Ports `seo/hooks/validate-seo.ts`: on an
@@ -3819,6 +4073,27 @@ async function handlePre(ctx) {
3819
4073
  };
3820
4074
  }
3821
4075
  //#endregion
4076
+ //#region src/runtime/handle-scope-async.ts
4077
+ /**
4078
+ * Pre-pipeline async scope interception for {@link handleHook}. The aipilot and
4079
+ * memory scopes reach external resources (cache files / Graphiti HTTP) and may
4080
+ * emit stdout for lifecycle events, so they run before the sync gate pipeline.
4081
+ */
4082
+ /**
4083
+ * Run the async per-scope dispatcher for the invoking scope, if any.
4084
+ * @param scope - The invoking plugin scope.
4085
+ * @param event - The raw hook event name.
4086
+ * @param payload - The raw hook payload.
4087
+ * @param cwd - Project root.
4088
+ * @param now - Clock.
4089
+ * @returns The native stdout when intercepted, or `null` to fall through.
4090
+ */
4091
+ async function asyncScopeStdout(scope, event, payload, cwd, now) {
4092
+ if (scope === "aipilot") return dispatchAipilot(event, payload, cwd, now);
4093
+ if (scope === "memory") return dispatchMemory(event, payload, cwd, now);
4094
+ return null;
4095
+ }
4096
+ //#endregion
3822
4097
  //#region src/runtime/handle.ts
3823
4098
  /** Raw Claude hook event name from a payload (empty when absent). */
3824
4099
  function rawEventName(payload) {
@@ -3840,13 +4115,11 @@ async function handleHook(id, payload, opts) {
3840
4115
  stdout: "",
3841
4116
  exit: 0
3842
4117
  };
3843
- if (opts.scope === "aipilot") {
3844
- const ai = await dispatchAipilot(rawEventName(payload), payload, opts.cwd, opts.now);
3845
- if (ai !== null) return {
3846
- stdout: ai,
3847
- exit: 0
3848
- };
3849
- }
4118
+ const asyncOut = await asyncScopeStdout(opts.scope, rawEventName(payload), payload, opts.cwd, opts.now);
4119
+ if (asyncOut !== null) return {
4120
+ stdout: asyncOut,
4121
+ exit: 0
4122
+ };
3850
4123
  const life = lifecycleStdout(payload, opts.cwd, opts.scope ?? "core", opts.now);
3851
4124
  if (life !== null) return {
3852
4125
  stdout: life,
@@ -3903,4 +4176,4 @@ async function handleHook(id, payload, opts) {
3903
4176
  });
3904
4177
  }
3905
4178
  //#endregion
3906
- export { detectSolidProfile as $, dispatchLessons as A, mcpPreIntercept as At, mergeLines as B, securityStateDir as C, defaultStateDir as Ct, dispatchLifecycle as D, MCP_TTL_MS as Dt, trackEnrichment as E, normalizeEvent as Et, writePluginMap as F, trackSessionChanges as G, getFileDesc as H, generateProjectMap as I, saveApexState as J, validateRulesLoaded as K, isProject as L, lessonsStateFileFor as M, activityFor as Mt, cartoSessionStart as N, aipilotPostToolUse as O, isMcpTool as Ot, generateEcosystemMap as P, subagentCacheContext as Q, writeTree as R, saveSecurityState as S, recordActivity as St, todayUtc as T, trackFile as Tt, listChildren as U, countFiles as V, postEditTypescript as W, validateTeammateOutput as X, logToolFailure as Y, trackAgentMemory as Z, trackWatchResearch as _, sessionStatePath as _t, TRIVIAL_BUDGET as a, pruneEmptyDirs as at, isoUtc as b, taskContext as bt, detectDuplication as c, trimLogFile as ct, lifecycleStdout as d, projectContext as dt, solidDetectStart as et, postEditContext as f, claudeHome as ft, postTrackingSideEffects as g, saveSessionState as gt, securityAdvisory as h, sanitizeSessionId as ht, REQUIRED_AGENTS as i, sessionStartCore as it, lessonsFileFor as j, queryOf as jt, dispatchAipilot as k, mcpPostStore as kt, dryGate as l, devContext as lt, seoPostToolUseResponse as m, loadSessionState as mt, handlePre as n, readRules as nt, gate as o, purgeTtlTree as ot, seoPostToolUse as p, fusengineCache as pt, cleanupSession as q, DEFAULT_WINDOW_MS as r, runSessionStartCleanups as rt, preCommitGate as s, removeOldFiles as st, handleHook as t, injectRules as tt, extractSymbols as u, gitContext as ut, trackMcpResearch as v, sessionsDir as vt, securityStatePath as w, projectHash$1 as wt, loadSecurityState as x, respond as xt, trackSkillRead as y, promptSubmitContext as yt, loadEnriched as z };
4179
+ export { sessionStartCore as $, writePluginMap as A, sanitizeSessionId as At, trackSessionChanges as B, aipilotPostToolUse as C, saveSecurityState as Ct, lessonsStateFileFor as D, claudeHome as Dt, lessonsFileFor as E, todayUtc as Et, mergeLines as F, validateTeammateOutput as G, cleanupSession as H, countFiles as I, detectSolidProfile as J, trackAgentMemory as K, getFileDesc as L, isProject as M, sessionStatePath as Mt, writeTree as N, sessionsDir as Nt, cartoSessionStart as O, fusengineCache as Ot, loadEnriched as P, runSessionStartCleanups as Q, listChildren as R, dispatchLifecycle as S, loadSecurityState as St, dispatchLessons as T, securityStatePath as Tt, saveApexState as U, validateRulesLoaded as V, logToolFailure as W, injectRules as X, solidDetectStart as Y, readRules as Z, postTrackingSideEffects as _, mcpPostStore as _t, TRIVIAL_BUDGET as a, gitContext as at, trackSkillRead as b, activityFor as bt, detectDuplication as c, taskContext as ct, lifecycleStdout as d, defaultStateDir as dt, pruneEmptyDirs as et, postEditContext as f, projectHash$1 as ft, securityAdvisory as g, isMcpTool as gt, dispatchMemory as h, MCP_TTL_MS as ht, REQUIRED_AGENTS as i, devContext as it, generateProjectMap as j, saveSessionState as jt, generateEcosystemMap as k, loadSessionState as kt, dryGate as l, respond as lt, seoPostToolUseResponse as m, normalizeEvent as mt, handlePre as n, removeOldFiles as nt, gate as o, projectContext as ot, seoPostToolUse as p, trackFile as pt, subagentCacheContext as q, DEFAULT_WINDOW_MS as r, trimLogFile as rt, preCommitGate as s, promptSubmitContext as st, handleHook as t, purgeTtlTree as tt, extractSymbols as u, recordActivity as ut, trackWatchResearch as v, mcpPreIntercept as vt, dispatchAipilot as w, securityStateDir as wt, trackEnrichment as x, isoUtc as xt, trackMcpResearch as y, queryOf as yt, postEditTypescript as z };
@@ -393,7 +393,7 @@ declare function aipilotPostToolUse(payload: Record<string, unknown>, cwd: strin
393
393
  //#endregion
394
394
  //#region src/runtime/lifecycle/dispatch.d.ts
395
395
  /** Which plugin's hooks.json invoked the harness (selects SessionStart behavior). */
396
- type PluginScope = "core" | "solid" | "rules" | "carto" | "security" | "changelog" | "aipilot" | "lessons" | "seo";
396
+ type PluginScope = "core" | "solid" | "rules" | "carto" | "security" | "changelog" | "aipilot" | "lessons" | "seo" | "memory";
397
397
  /** Inputs the lifecycle dispatcher needs (clock + roots injected). */
398
398
  interface LifecycleInput {
399
399
  event: string;
@@ -589,6 +589,17 @@ declare function lessonsFileFor(root: string): string;
589
589
  /** Absolute `<root>/MEMORY/state.json` — machine-local throttle counter. */
590
590
  declare function lessonsStateFileFor(root: string): string;
591
591
  //#endregion
592
+ //#region src/runtime/lifecycle/memory/dispatch.d.ts
593
+ /**
594
+ * Dispatch a memory-scope lifecycle event to its ported handler.
595
+ * @param event - Raw hook event name.
596
+ * @param payload - Raw hook payload.
597
+ * @param cwd - Project root.
598
+ * @param now - Clock.
599
+ * @returns The native stdout, or `null` when unhandled.
600
+ */
601
+ declare function dispatchMemory(event: string, payload: Record<string, unknown>, cwd: string, now: number): Promise<string | null>;
602
+ //#endregion
592
603
  //#region src/runtime/lifecycle/seo/post-tool-use.d.ts
593
604
  /**
594
605
  * Validate the edited file's SEO completeness. Returns a deny message (for a
@@ -705,4 +716,4 @@ interface PreContext {
705
716
  */
706
717
  declare function handlePre(ctx: PreContext): Promise<HandleOutcome>;
707
718
  //#endregion
708
- export { Activity, DEFAULT_WINDOW_MS, DuplicationVerdict, type GateInput, HandleOptions, HandleOutcome, LifecycleInput, MCP_TTL_MS, McpIntercept, NormalizedEvent, PluginScope, PreContext, REQUIRED_AGENTS, SolidProfile, TRIVIAL_BUDGET, ToolEvent, activityFor, aipilotPostToolUse, cartoSessionStart, claudeHome, cleanupSession, countFiles, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dryGate, extractSymbols, 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, validateTeammateOutput, writePluginMap, writeTree };
719
+ export { Activity, DEFAULT_WINDOW_MS, DuplicationVerdict, type GateInput, HandleOptions, HandleOutcome, LifecycleInput, MCP_TTL_MS, McpIntercept, NormalizedEvent, PluginScope, PreContext, REQUIRED_AGENTS, SolidProfile, TRIVIAL_BUDGET, ToolEvent, activityFor, aipilotPostToolUse, cartoSessionStart, claudeHome, cleanupSession, countFiles, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, 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, validateTeammateOutput, writePluginMap, writeTree };
@@ -1,5 +1,5 @@
1
1
  import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
2
- import { $ as detectSolidProfile, A as dispatchLessons, At as mcpPreIntercept, B as mergeLines, C as securityStateDir, Ct as defaultStateDir, D as dispatchLifecycle, Dt as MCP_TTL_MS, E as trackEnrichment, Et as normalizeEvent, F as writePluginMap, G as trackSessionChanges, H as getFileDesc, I as generateProjectMap, J as saveApexState, K as validateRulesLoaded, L as isProject, M as lessonsStateFileFor, Mt as activityFor, N as cartoSessionStart, O as aipilotPostToolUse, Ot as isMcpTool, P as generateEcosystemMap, Q as subagentCacheContext, R as writeTree, S as saveSecurityState, St as recordActivity, T as todayUtc, Tt as trackFile, U as listChildren, V as countFiles, W as postEditTypescript, X as validateTeammateOutput, Y as logToolFailure, Z as trackAgentMemory, _ as trackWatchResearch, _t as sessionStatePath, a as TRIVIAL_BUDGET, at as pruneEmptyDirs, b as isoUtc, bt as taskContext, c as detectDuplication, ct as trimLogFile, d as lifecycleStdout, dt as projectContext, et as solidDetectStart, f as postEditContext, ft as claudeHome, g as postTrackingSideEffects, gt as saveSessionState, h as securityAdvisory, ht as sanitizeSessionId, i as REQUIRED_AGENTS, it as sessionStartCore, j as lessonsFileFor, jt as queryOf, k as dispatchAipilot, kt as mcpPostStore, l as dryGate, lt as devContext, m as seoPostToolUseResponse, mt as loadSessionState, n as handlePre, nt as readRules, o as gate, ot as purgeTtlTree, p as seoPostToolUse, pt as fusengineCache, q as cleanupSession, r as DEFAULT_WINDOW_MS, rt as runSessionStartCleanups, s as preCommitGate, st as removeOldFiles, t as handleHook, tt as injectRules, u as extractSymbols, ut as gitContext, v as trackMcpResearch, vt as sessionsDir, w as securityStatePath, wt as projectHash, x as loadSecurityState, xt as respond, y as trackSkillRead, yt as promptSubmitContext, z as loadEnriched } from "../handle-DMHjTY1E.mjs";
2
+ import { $ as sessionStartCore, A as writePluginMap, At as sanitizeSessionId, B as trackSessionChanges, C as aipilotPostToolUse, Ct as saveSecurityState, D as lessonsStateFileFor, Dt as claudeHome, E as lessonsFileFor, Et as todayUtc, F as mergeLines, G as validateTeammateOutput, H as cleanupSession, I as countFiles, J as detectSolidProfile, K as trackAgentMemory, L as getFileDesc, M as isProject, Mt as sessionStatePath, N as writeTree, Nt as sessionsDir, O as cartoSessionStart, Ot as fusengineCache, P as loadEnriched, Q as runSessionStartCleanups, R as listChildren, S as dispatchLifecycle, St as loadSecurityState, T as dispatchLessons, Tt as securityStatePath, U as saveApexState, V as validateRulesLoaded, W as logToolFailure, X as injectRules, Y as solidDetectStart, Z as readRules, _ as postTrackingSideEffects, _t as mcpPostStore, a as TRIVIAL_BUDGET, at as gitContext, b as trackSkillRead, bt as activityFor, c as detectDuplication, ct as taskContext, d as lifecycleStdout, dt as defaultStateDir, et as pruneEmptyDirs, f as postEditContext, ft as projectHash, g as securityAdvisory, gt as isMcpTool, h as dispatchMemory, ht as MCP_TTL_MS, i as REQUIRED_AGENTS, it as devContext, j as generateProjectMap, jt as saveSessionState, k as generateEcosystemMap, kt as loadSessionState, l as dryGate, lt as respond, m as seoPostToolUseResponse, mt as normalizeEvent, n as handlePre, nt as removeOldFiles, o as gate, ot as projectContext, p as seoPostToolUse, pt as trackFile, q as subagentCacheContext, r as DEFAULT_WINDOW_MS, rt as trimLogFile, s as preCommitGate, st as promptSubmitContext, t as handleHook, tt as purgeTtlTree, u as extractSymbols, ut as recordActivity, v as trackWatchResearch, vt as mcpPreIntercept, w as dispatchAipilot, wt as securityStateDir, x as trackEnrichment, xt as isoUtc, y as trackMcpResearch, yt as queryOf, z as postEditTypescript } from "../handle-DL2sZiWH.mjs";
3
3
  //#region src/runtime/storage.ts
4
4
  /**
5
5
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -9,4 +9,4 @@ function harnessStateDir(root) {
9
9
  return projectLayout(root).stateDir;
10
10
  }
11
11
  //#endregion
12
- export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, activityFor, aipilotPostToolUse, cartoSessionStart, claudeHome, cleanupSession, countFiles, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dryGate, extractSymbols, 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, validateTeammateOutput, writePluginMap, writeTree };
12
+ export { DEFAULT_WINDOW_MS, MCP_TTL_MS, REQUIRED_AGENTS, TRIVIAL_BUDGET, activityFor, aipilotPostToolUse, cartoSessionStart, claudeHome, cleanupSession, countFiles, defaultStateDir, detectDuplication, detectSolidProfile, devContext, dispatchAipilot, dispatchLessons, dispatchLifecycle, dispatchMemory, dryGate, extractSymbols, 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, validateTeammateOutput, writePluginMap, writeTree };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
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",