@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-
|
|
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$
|
|
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$
|
|
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
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
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 {
|
|
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 };
|
package/dist/runtime/index.d.mts
CHANGED
|
@@ -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 };
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
|
|
2
|
-
import { $ as
|
|
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.
|
|
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",
|