@fusengine/harness 0.1.1 → 0.1.3
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/adapters/claude/index.d.mts +1 -35
- package/dist/adapters/claude/index.mjs +1 -65
- package/dist/adapters/cline/index.mjs +1 -24
- package/dist/adapters/codex/index.d.mts +2 -0
- package/dist/adapters/codex/index.mjs +2 -0
- package/dist/adapters/cursor/index.mjs +1 -36
- package/dist/adapters/gemini/index.mjs +1 -24
- package/dist/cache/index.d.mts +1 -29
- package/dist/cache/index.mjs +1 -109
- package/dist/cache-DbPSJ9bC.mjs +110 -0
- package/dist/claude-DbzjbxmO.mjs +66 -0
- package/dist/cli/bin.mjs +84 -10
- package/dist/cline-BxslHtBG.mjs +25 -0
- package/dist/config/index.d.mts +1 -36
- package/dist/config/index.mjs +1 -18
- package/dist/config-BG55s6HZ.mjs +20 -0
- package/dist/cursor-Bh7eh9y_.mjs +37 -0
- package/dist/detect/index.d.mts +1 -30
- package/dist/detect/index.mjs +2 -81
- package/dist/detect-la_KkjCS.mjs +1 -0
- package/dist/doc-helpers-Dd_x1-tZ.mjs +42 -0
- package/dist/freshness/index.d.mts +1 -12
- package/dist/freshness/index.mjs +2 -63
- package/dist/freshness-BK9Xg6oG.mjs +23 -0
- package/dist/gemini-SrK_fFAr.mjs +25 -0
- package/dist/harness-C8Nxxyn_.mjs +83 -0
- package/dist/harness-DwJskkz_.d.mts +31 -0
- package/dist/index-B-z0CCiU.d.mts +37 -0
- package/dist/index-B3Ve_bBu.d.mts +30 -0
- package/dist/index-BEM-mQMC.d.mts +30 -0
- package/dist/index-BKZ67WMa.d.mts +1 -0
- package/dist/index-BOBXQ91y.d.mts +12 -0
- package/dist/index-BVVgDSdq.d.mts +1 -0
- package/dist/index-BWK8slRi.d.mts +84 -0
- package/dist/index-BXBWKbL8.d.mts +114 -0
- package/dist/index-C1vLIMwN.d.mts +20 -0
- package/dist/index-C2Lz-cwJ.d.mts +21 -0
- package/dist/index-CPoF_hLP.d.mts +59 -0
- package/dist/index-FrWgmkbP.d.mts +36 -0
- package/dist/index.d.mts +12 -11
- package/dist/index.mjs +11 -12
- package/dist/init/index.d.mts +34 -0
- package/dist/init/index.mjs +2 -0
- package/dist/memory/index.d.mts +1 -29
- package/dist/memory/index.mjs +1 -93
- package/dist/memory-BVNt4Ary.mjs +94 -0
- package/dist/policy/index.d.mts +2 -80
- package/dist/policy/index.mjs +2 -33
- package/dist/policy-DMpyaPZ_.mjs +68 -0
- package/dist/prompt/index.mjs +1 -0
- package/dist/prompt-la_KkjCS.mjs +1 -0
- package/dist/refs/index.d.mts +2 -43
- package/dist/refs/index.mjs +2 -69
- package/dist/refs-la_KkjCS.mjs +1 -0
- package/dist/router-Dj3AfgBE.mjs +70 -0
- package/dist/run-Cc98348q.mjs +94 -0
- package/dist/state/index.d.mts +1 -58
- package/dist/state/index.mjs +1 -103
- package/dist/state-Bc4wdnCG.mjs +104 -0
- package/dist/statusline/index.d.mts +1 -83
- package/dist/statusline/index.mjs +1 -147
- package/dist/statusline-D87eUNXl.mjs +148 -0
- package/dist/types-CY5qT2X1.d.mts +26 -0
- package/dist/util/index.d.mts +1 -19
- package/dist/util/index.mjs +1 -0
- package/dist/util-la_KkjCS.mjs +1 -0
- package/package.json +5 -3
package/dist/config/index.d.mts
CHANGED
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Robust integer-from-env parser.
|
|
4
|
-
* undefined / empty / whitespace / NaN / float / <= 0 all fall back to `fallback`.
|
|
5
|
-
* `Number("")` is 0, so the empty guard is required before `Number()`.
|
|
6
|
-
*/
|
|
7
|
-
declare function parseEnvInt(raw: string | undefined, fallback: number): number;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/config/ttl.d.ts
|
|
10
|
-
/** Default enforcement-freshness window, in seconds (2 minutes). */
|
|
11
|
-
declare const DEFAULT_TTL_SEC = 120;
|
|
12
|
-
/** Default env var name carrying the TTL override. */
|
|
13
|
-
declare const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
|
|
14
|
-
/**
|
|
15
|
-
* Resolve the enforcement TTL (seconds) from an env map.
|
|
16
|
-
* @param env - environment map (defaults to `process.env`)
|
|
17
|
-
* @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
|
|
18
|
-
*/
|
|
19
|
-
declare function resolveTtlSec(env?: Record<string, string | undefined>, key?: string): number;
|
|
20
|
-
/** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
|
|
21
|
-
declare function ttlLabel(sec: number): string;
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region src/config/limits.d.ts
|
|
24
|
-
/** Default SOLID max lines per file. */
|
|
25
|
-
declare const DEFAULT_MAX_LINES = 100;
|
|
26
|
-
/** Default env var name carrying the max-lines override. */
|
|
27
|
-
declare const MAX_LINES_ENV_KEY = "FUSE_SOLID_MAX_LINES";
|
|
28
|
-
/**
|
|
29
|
-
* Resolve the SOLID max-lines limit from an env map.
|
|
30
|
-
* @param env - environment map (defaults to `process.env`)
|
|
31
|
-
* @param key - env var name (defaults to `FUSE_SOLID_MAX_LINES`)
|
|
32
|
-
*/
|
|
33
|
-
declare function resolveMaxLines(env?: Record<string, string | undefined>, key?: string): number;
|
|
34
|
-
/** Advisory module-split headroom = `maxLines - 10` (never below 1). */
|
|
35
|
-
declare function splitTarget(maxLines: number): number;
|
|
36
|
-
//#endregion
|
|
1
|
+
import { a as DEFAULT_TTL_SEC, c as ttlLabel, i as splitTarget, l as parseEnvInt, n as MAX_LINES_ENV_KEY, o as TTL_ENV_KEY, r as resolveMaxLines, s as resolveTtlSec, t as DEFAULT_MAX_LINES } from "../index-B-z0CCiU.mjs";
|
|
37
2
|
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, TTL_ENV_KEY, parseEnvInt, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
package/dist/config/index.mjs
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
1
|
import { a as parseEnvInt, i as splitTarget, n as MAX_LINES_ENV_KEY, r as resolveMaxLines, t as DEFAULT_MAX_LINES } from "../limits-CHn8AIL1.mjs";
|
|
2
|
-
|
|
3
|
-
/** Default enforcement-freshness window, in seconds (2 minutes). */
|
|
4
|
-
const DEFAULT_TTL_SEC = 120;
|
|
5
|
-
/** Default env var name carrying the TTL override. */
|
|
6
|
-
const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
|
|
7
|
-
/**
|
|
8
|
-
* Resolve the enforcement TTL (seconds) from an env map.
|
|
9
|
-
* @param env - environment map (defaults to `process.env`)
|
|
10
|
-
* @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
|
|
11
|
-
*/
|
|
12
|
-
function resolveTtlSec(env = process.env, key = TTL_ENV_KEY) {
|
|
13
|
-
return parseEnvInt(env[key], 120);
|
|
14
|
-
}
|
|
15
|
-
/** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
|
|
16
|
-
function ttlLabel(sec) {
|
|
17
|
-
return sec % 60 === 0 ? `${sec / 60}min` : `${sec}s`;
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
2
|
+
import { i as ttlLabel, n as TTL_ENV_KEY, r as resolveTtlSec, t as DEFAULT_TTL_SEC } from "../config-BG55s6HZ.mjs";
|
|
20
3
|
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, TTL_ENV_KEY, parseEnvInt, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as parseEnvInt } from "./limits-CHn8AIL1.mjs";
|
|
2
|
+
//#region src/config/ttl.ts
|
|
3
|
+
/** Default enforcement-freshness window, in seconds (2 minutes). */
|
|
4
|
+
const DEFAULT_TTL_SEC = 120;
|
|
5
|
+
/** Default env var name carrying the TTL override. */
|
|
6
|
+
const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the enforcement TTL (seconds) from an env map.
|
|
9
|
+
* @param env - environment map (defaults to `process.env`)
|
|
10
|
+
* @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
|
|
11
|
+
*/
|
|
12
|
+
function resolveTtlSec(env = process.env, key = TTL_ENV_KEY) {
|
|
13
|
+
return parseEnvInt(env[key], 120);
|
|
14
|
+
}
|
|
15
|
+
/** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
|
|
16
|
+
function ttlLabel(sec) {
|
|
17
|
+
return sec % 60 === 0 ? `${sec / 60}min` : `${sec}s`;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ttlLabel as i, TTL_ENV_KEY as n, resolveTtlSec as r, DEFAULT_TTL_SEC as t };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t as evaluate } from "./evaluate-CsYyUucy.mjs";
|
|
2
|
+
import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
|
|
3
|
+
//#region src/adapters/cursor/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cursor adapter (hook-mode). Schemas per cursor.com/docs/hooks (2026):
|
|
6
|
+
* `beforeShellExecution` can block; `afterFileEdit` is observe-only.
|
|
7
|
+
*/
|
|
8
|
+
function toPermission(kind) {
|
|
9
|
+
return kind === "block" ? "deny" : kind === "ask" ? "ask" : "allow";
|
|
10
|
+
}
|
|
11
|
+
/** Guard a shell command (git/install policies). */
|
|
12
|
+
function beforeShellExecution(payload) {
|
|
13
|
+
const r = evaluate({
|
|
14
|
+
tool: "Bash",
|
|
15
|
+
command: payload.command
|
|
16
|
+
});
|
|
17
|
+
if (r.decision === "allow" || !r.prompt) return { permission: "allow" };
|
|
18
|
+
const msg = formatPrompt(r.prompt);
|
|
19
|
+
return {
|
|
20
|
+
permission: toPermission(r.prompt.kind),
|
|
21
|
+
continue: false,
|
|
22
|
+
userMessage: msg,
|
|
23
|
+
agentMessage: msg
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Observe a file edit (Cursor cannot block here). Returns the verdict for logging. */
|
|
27
|
+
function afterFileEdit(payload) {
|
|
28
|
+
const content = payload.edits?.map((e) => e.new_string).join("\n") ?? "";
|
|
29
|
+
const r = evaluate({
|
|
30
|
+
tool: "Edit",
|
|
31
|
+
filePath: payload.file_path,
|
|
32
|
+
content
|
|
33
|
+
});
|
|
34
|
+
return { violation: r.decision === "deny" ? r.message : null };
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { beforeShellExecution as n, afterFileEdit as t };
|
package/dist/detect/index.d.mts
CHANGED
|
@@ -1,31 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Runtime detection of the AI coding harness, and its integration mode.
|
|
4
|
-
* Env-signal names verified 2026 (agentx, agents.md#136, @vercel/detect-agent,
|
|
5
|
-
* official Claude Code / Cursor / Gemini / Codex docs). Presence-based: the
|
|
6
|
-
* value is ignored except for the `AGENT` / `AI_AGENT` standards.
|
|
7
|
-
*/
|
|
8
|
-
/** Known AI coding harnesses detectable at runtime. */
|
|
9
|
-
type HarnessId = "claude-code" | "codex" | "cursor" | "cline" | "gemini-cli" | "opencode" | "windsurf" | "copilot" | "aider" | "kiro" | "goose" | "amp" | "unknown";
|
|
10
|
-
/** Integration mode: `hook` = native lifecycle hooks; `cli` = run as an external step. */
|
|
11
|
-
type HarnessMode = "hook" | "cli";
|
|
12
|
-
/** How the harness was identified. */
|
|
13
|
-
type HarnessVia = "agent-std" | "ai-agent-std" | "env" | "fallback";
|
|
14
|
-
/** Result of {@link detectHarness}. */
|
|
15
|
-
interface HarnessInfo {
|
|
16
|
-
id: HarnessId;
|
|
17
|
-
mode: HarnessMode;
|
|
18
|
-
via: HarnessVia;
|
|
19
|
-
}
|
|
20
|
-
/** Integration mode for a harness id. */
|
|
21
|
-
declare function modeFor(id: HarnessId): HarnessMode;
|
|
22
|
-
/**
|
|
23
|
-
* Detect the current AI coding harness from environment signals.
|
|
24
|
-
* Priority: `AGENT` standard -> `AI_AGENT` standard -> tool-specific vars -> unknown.
|
|
25
|
-
* @param env - environment map (defaults to `process.env`)
|
|
26
|
-
*/
|
|
27
|
-
declare function detectHarness(env?: Record<string, string | undefined>): HarnessInfo;
|
|
28
|
-
/** Convenience: the integration mode of the current harness. */
|
|
29
|
-
declare function detectMode(env?: Record<string, string | undefined>): HarnessMode;
|
|
30
|
-
//#endregion
|
|
1
|
+
import { a as detectHarness, i as HarnessVia, n as HarnessInfo, o as detectMode, r as HarnessMode, s as modeFor, t as HarnessId } from "../harness-DwJskkz_.mjs";
|
|
31
2
|
export { HarnessId, HarnessInfo, HarnessMode, HarnessVia, detectHarness, detectMode, modeFor };
|
package/dist/detect/index.mjs
CHANGED
|
@@ -1,82 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const ENV_SIGNALS = [
|
|
4
|
-
["CLAUDECODE", "claude-code"],
|
|
5
|
-
["CODEX_SANDBOX", "codex"],
|
|
6
|
-
["CURSOR_AGENT", "cursor"],
|
|
7
|
-
["CLINE", "cline"],
|
|
8
|
-
["CLINE_AGENT", "cline"],
|
|
9
|
-
["GEMINI_CLI", "gemini-cli"],
|
|
10
|
-
["OPENCODE", "opencode"],
|
|
11
|
-
["WINDSURF_AGENT", "windsurf"],
|
|
12
|
-
["CODEIUM_AGENT", "windsurf"],
|
|
13
|
-
["COPILOT_AGENT", "copilot"],
|
|
14
|
-
["AIDER", "aider"],
|
|
15
|
-
["KIRO", "kiro"],
|
|
16
|
-
["GOOSE", "goose"],
|
|
17
|
-
["AMP", "amp"]
|
|
18
|
-
];
|
|
19
|
-
/** `AGENT=<name>` / `AI_AGENT=<name>` standard value -> harness id. */
|
|
20
|
-
const STD_NAMES = {
|
|
21
|
-
goose: "goose",
|
|
22
|
-
amp: "amp",
|
|
23
|
-
claude: "claude-code",
|
|
24
|
-
"claude-code": "claude-code",
|
|
25
|
-
cursor: "cursor",
|
|
26
|
-
codex: "codex",
|
|
27
|
-
cline: "cline",
|
|
28
|
-
aider: "aider",
|
|
29
|
-
opencode: "opencode",
|
|
30
|
-
gemini: "gemini-cli",
|
|
31
|
-
copilot: "copilot",
|
|
32
|
-
kiro: "kiro"
|
|
33
|
-
};
|
|
34
|
-
/** Harnesses exposing a native hook system (vs CLI-only integration). */
|
|
35
|
-
const HOOK_CAPABLE = /* @__PURE__ */ new Set([
|
|
36
|
-
"claude-code",
|
|
37
|
-
"cursor",
|
|
38
|
-
"cline",
|
|
39
|
-
"gemini-cli",
|
|
40
|
-
"opencode"
|
|
41
|
-
]);
|
|
42
|
-
/** Integration mode for a harness id. */
|
|
43
|
-
function modeFor(id) {
|
|
44
|
-
return HOOK_CAPABLE.has(id) ? "hook" : "cli";
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Detect the current AI coding harness from environment signals.
|
|
48
|
-
* Priority: `AGENT` standard -> `AI_AGENT` standard -> tool-specific vars -> unknown.
|
|
49
|
-
* @param env - environment map (defaults to `process.env`)
|
|
50
|
-
*/
|
|
51
|
-
function detectHarness(env = process.env) {
|
|
52
|
-
const agent = env.AGENT?.trim().toLowerCase();
|
|
53
|
-
const agentId = agent ? STD_NAMES[agent] : void 0;
|
|
54
|
-
if (agentId) return {
|
|
55
|
-
id: agentId,
|
|
56
|
-
mode: modeFor(agentId),
|
|
57
|
-
via: "agent-std"
|
|
58
|
-
};
|
|
59
|
-
const ai = env.AI_AGENT?.trim().toLowerCase();
|
|
60
|
-
const aiId = ai ? STD_NAMES[ai] : void 0;
|
|
61
|
-
if (aiId) return {
|
|
62
|
-
id: aiId,
|
|
63
|
-
mode: modeFor(aiId),
|
|
64
|
-
via: "ai-agent-std"
|
|
65
|
-
};
|
|
66
|
-
for (const [key, id] of ENV_SIGNALS) if (env[key]?.trim()) return {
|
|
67
|
-
id,
|
|
68
|
-
mode: modeFor(id),
|
|
69
|
-
via: "env"
|
|
70
|
-
};
|
|
71
|
-
return {
|
|
72
|
-
id: "unknown",
|
|
73
|
-
mode: "cli",
|
|
74
|
-
via: "fallback"
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/** Convenience: the integration mode of the current harness. */
|
|
78
|
-
function detectMode(env = process.env) {
|
|
79
|
-
return detectHarness(env).mode;
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
1
|
+
import { n as detectMode, r as modeFor, t as detectHarness } from "../harness-C8Nxxyn_.mjs";
|
|
2
|
+
import "../detect-la_KkjCS.mjs";
|
|
82
3
|
export { detectHarness, detectMode, modeFor };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/freshness/doc-helpers.ts
|
|
2
|
+
/** Resolve the sessions array from an auth entry (legacy fallback). */
|
|
3
|
+
function resolveSessions(auth) {
|
|
4
|
+
if (!auth) return [];
|
|
5
|
+
return auth.sessions ?? (auth.session ? [auth.session] : []);
|
|
6
|
+
}
|
|
7
|
+
function sessionAuthsFor(authorizations, sessionId) {
|
|
8
|
+
if (!authorizations) return [];
|
|
9
|
+
return Object.values(authorizations).filter((a) => a.doc_sessions?.includes(sessionId));
|
|
10
|
+
}
|
|
11
|
+
function evaluateDoc(auths) {
|
|
12
|
+
const sources = auths.flatMap((a) => a.sources ?? [a.source ?? ""]);
|
|
13
|
+
const readPaths = auths.flatMap((a) => a.read_paths ?? []);
|
|
14
|
+
const liveC7 = sources.some((s) => /context7/.test(s));
|
|
15
|
+
const liveExa = sources.some((s) => /exa/.test(s));
|
|
16
|
+
const cacheC7 = readPaths.some((p) => /\/context\/mcp\/context7-/.test(p));
|
|
17
|
+
const cacheExa = readPaths.some((p) => /\/context\/mcp\/(exa-search|exa-code-context)-/.test(p));
|
|
18
|
+
return {
|
|
19
|
+
context7: liveC7 || cacheC7,
|
|
20
|
+
exa: liveExa || cacheExa,
|
|
21
|
+
viaCache: !liveC7 && cacheC7 || !liveExa && cacheExa
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** True when BOTH Context7 and Exa were satisfied for the session. */
|
|
25
|
+
function isDocConsulted(authorizations, sessionId) {
|
|
26
|
+
const s = evaluateDoc(sessionAuthsFor(authorizations, sessionId));
|
|
27
|
+
return s.context7 && s.exa;
|
|
28
|
+
}
|
|
29
|
+
/** Report how each doc source was satisfied for a session. */
|
|
30
|
+
function formatDocSatisfactionStatus(authorizations, sessionId) {
|
|
31
|
+
return evaluateDoc(sessionAuthsFor(authorizations, sessionId));
|
|
32
|
+
}
|
|
33
|
+
/** Deny message when online documentation has not been consulted. */
|
|
34
|
+
function formatDocDeny(framework) {
|
|
35
|
+
return [
|
|
36
|
+
`APEX: Online documentation not consulted for ${framework}!`,
|
|
37
|
+
"Use BOTH: 1) mcp__context7__query-docs AND 2) mcp__exa__web_search_exa.",
|
|
38
|
+
"This check is once per session — after consulting both, Write/Edit will be allowed."
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { resolveSessions as i, formatDocSatisfactionStatus as n, isDocConsulted as r, formatDocDeny as t };
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "../doc-helpers-CG1nuf-c.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/freshness/trivial-edit-counter.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Increment a session's trivial-edit counter, evicting timestamps older than
|
|
6
|
-
* `windowMs`. Decoupled + injectable `now` for testability.
|
|
7
|
-
* @param filePath - session state file path
|
|
8
|
-
* @param windowMs - sliding window in ms
|
|
9
|
-
* @param now - current epoch ms (defaults to `Date.now()`)
|
|
10
|
-
* @returns number of trivial edits within the window (including this one)
|
|
11
|
-
*/
|
|
12
|
-
declare function incrementTrivialEditCounter(filePath: string, windowMs: number, now?: number): Promise<number>;
|
|
13
|
-
//#endregion
|
|
2
|
+
import { t as incrementTrivialEditCounter } from "../index-BOBXQ91y.mjs";
|
|
14
3
|
export { AuthEntry, DocSatisfactionStatus, formatDocDeny, formatDocSatisfactionStatus, incrementTrivialEditCounter, isDocConsulted, resolveSessions };
|
package/dist/freshness/index.mjs
CHANGED
|
@@ -1,64 +1,3 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
import {
|
|
3
|
-
//#region src/freshness/doc-helpers.ts
|
|
4
|
-
/** Resolve the sessions array from an auth entry (legacy fallback). */
|
|
5
|
-
function resolveSessions(auth) {
|
|
6
|
-
if (!auth) return [];
|
|
7
|
-
return auth.sessions ?? (auth.session ? [auth.session] : []);
|
|
8
|
-
}
|
|
9
|
-
function sessionAuthsFor(authorizations, sessionId) {
|
|
10
|
-
if (!authorizations) return [];
|
|
11
|
-
return Object.values(authorizations).filter((a) => a.doc_sessions?.includes(sessionId));
|
|
12
|
-
}
|
|
13
|
-
function evaluateDoc(auths) {
|
|
14
|
-
const sources = auths.flatMap((a) => a.sources ?? [a.source ?? ""]);
|
|
15
|
-
const readPaths = auths.flatMap((a) => a.read_paths ?? []);
|
|
16
|
-
const liveC7 = sources.some((s) => /context7/.test(s));
|
|
17
|
-
const liveExa = sources.some((s) => /exa/.test(s));
|
|
18
|
-
const cacheC7 = readPaths.some((p) => /\/context\/mcp\/context7-/.test(p));
|
|
19
|
-
const cacheExa = readPaths.some((p) => /\/context\/mcp\/(exa-search|exa-code-context)-/.test(p));
|
|
20
|
-
return {
|
|
21
|
-
context7: liveC7 || cacheC7,
|
|
22
|
-
exa: liveExa || cacheExa,
|
|
23
|
-
viaCache: !liveC7 && cacheC7 || !liveExa && cacheExa
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/** True when BOTH Context7 and Exa were satisfied for the session. */
|
|
27
|
-
function isDocConsulted(authorizations, sessionId) {
|
|
28
|
-
const s = evaluateDoc(sessionAuthsFor(authorizations, sessionId));
|
|
29
|
-
return s.context7 && s.exa;
|
|
30
|
-
}
|
|
31
|
-
/** Report how each doc source was satisfied for a session. */
|
|
32
|
-
function formatDocSatisfactionStatus(authorizations, sessionId) {
|
|
33
|
-
return evaluateDoc(sessionAuthsFor(authorizations, sessionId));
|
|
34
|
-
}
|
|
35
|
-
/** Deny message when online documentation has not been consulted. */
|
|
36
|
-
function formatDocDeny(framework) {
|
|
37
|
-
return [
|
|
38
|
-
`APEX: Online documentation not consulted for ${framework}!`,
|
|
39
|
-
"Use BOTH: 1) mcp__context7__query-docs AND 2) mcp__exa__web_search_exa.",
|
|
40
|
-
"This check is once per session — after consulting both, Write/Edit will be allowed."
|
|
41
|
-
].join("\n");
|
|
42
|
-
}
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region src/freshness/trivial-edit-counter.ts
|
|
45
|
-
/**
|
|
46
|
-
* Increment a session's trivial-edit counter, evicting timestamps older than
|
|
47
|
-
* `windowMs`. Decoupled + injectable `now` for testability.
|
|
48
|
-
* @param filePath - session state file path
|
|
49
|
-
* @param windowMs - sliding window in ms
|
|
50
|
-
* @param now - current epoch ms (defaults to `Date.now()`)
|
|
51
|
-
* @returns number of trivial edits within the window (including this one)
|
|
52
|
-
*/
|
|
53
|
-
async function incrementTrivialEditCounter(filePath, windowMs, now = Date.now()) {
|
|
54
|
-
await ensureDir(dirname(filePath));
|
|
55
|
-
const state = await readJsonFile(filePath) ?? {};
|
|
56
|
-
const cutoff = now - windowMs;
|
|
57
|
-
const edits = (state.trivial_edits ?? []).filter((ts) => ts > cutoff);
|
|
58
|
-
edits.push(now);
|
|
59
|
-
state.trivial_edits = edits;
|
|
60
|
-
await writeJsonFile(filePath, state);
|
|
61
|
-
return edits.length;
|
|
62
|
-
}
|
|
63
|
-
//#endregion
|
|
1
|
+
import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "../doc-helpers-Dd_x1-tZ.mjs";
|
|
2
|
+
import { t as incrementTrivialEditCounter } from "../freshness-BK9Xg6oG.mjs";
|
|
64
3
|
export { formatDocDeny, formatDocSatisfactionStatus, incrementTrivialEditCounter, isDocConsulted, resolveSessions };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { n as readJsonFile, r as writeJsonFile, t as ensureDir } from "./json-io-RH82El2J.mjs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
//#region src/freshness/trivial-edit-counter.ts
|
|
4
|
+
/**
|
|
5
|
+
* Increment a session's trivial-edit counter, evicting timestamps older than
|
|
6
|
+
* `windowMs`. Decoupled + injectable `now` for testability.
|
|
7
|
+
* @param filePath - session state file path
|
|
8
|
+
* @param windowMs - sliding window in ms
|
|
9
|
+
* @param now - current epoch ms (defaults to `Date.now()`)
|
|
10
|
+
* @returns number of trivial edits within the window (including this one)
|
|
11
|
+
*/
|
|
12
|
+
async function incrementTrivialEditCounter(filePath, windowMs, now = Date.now()) {
|
|
13
|
+
await ensureDir(dirname(filePath));
|
|
14
|
+
const state = await readJsonFile(filePath) ?? {};
|
|
15
|
+
const cutoff = now - windowMs;
|
|
16
|
+
const edits = (state.trivial_edits ?? []).filter((ts) => ts > cutoff);
|
|
17
|
+
edits.push(now);
|
|
18
|
+
state.trivial_edits = edits;
|
|
19
|
+
await writeJsonFile(filePath, state);
|
|
20
|
+
return edits.length;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { incrementTrivialEditCounter as t };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { t as evaluate } from "./evaluate-CsYyUucy.mjs";
|
|
2
|
+
import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
|
|
3
|
+
//#region src/adapters/gemini/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Gemini CLI adapter (hook-mode). Schema per google-gemini/gemini-cli docs/hooks (2026):
|
|
6
|
+
* `BeforeTool` blocks via `{ decision: "deny", reason }` (or exit 2).
|
|
7
|
+
*/
|
|
8
|
+
/** Evaluate a tool use; deny on a hard block, otherwise inject context. */
|
|
9
|
+
function beforeTool(input) {
|
|
10
|
+
const i = input.tool_input;
|
|
11
|
+
const r = evaluate({
|
|
12
|
+
tool: input.tool_name ?? "write_file",
|
|
13
|
+
filePath: i?.path,
|
|
14
|
+
content: i?.content,
|
|
15
|
+
command: i?.command
|
|
16
|
+
});
|
|
17
|
+
if (r.decision === "allow" || !r.prompt) return {};
|
|
18
|
+
const msg = formatPrompt(r.prompt);
|
|
19
|
+
return r.prompt.kind === "block" ? {
|
|
20
|
+
decision: "deny",
|
|
21
|
+
reason: msg
|
|
22
|
+
} : { hookSpecificOutput: { additionalContext: msg } };
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { beforeTool as t };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/detect/harness.ts
|
|
2
|
+
/** Tool-specific env var (presence) -> harness id. Order = detection priority. */
|
|
3
|
+
const ENV_SIGNALS = [
|
|
4
|
+
["CLAUDECODE", "claude-code"],
|
|
5
|
+
["CODEX_SANDBOX", "codex"],
|
|
6
|
+
["CURSOR_AGENT", "cursor"],
|
|
7
|
+
["CLINE", "cline"],
|
|
8
|
+
["CLINE_AGENT", "cline"],
|
|
9
|
+
["GEMINI_CLI", "gemini-cli"],
|
|
10
|
+
["OPENCODE", "opencode"],
|
|
11
|
+
["WINDSURF_AGENT", "windsurf"],
|
|
12
|
+
["CODEIUM_AGENT", "windsurf"],
|
|
13
|
+
["COPILOT_AGENT", "copilot"],
|
|
14
|
+
["AIDER", "aider"],
|
|
15
|
+
["KIRO", "kiro"],
|
|
16
|
+
["GOOSE", "goose"],
|
|
17
|
+
["AMP", "amp"]
|
|
18
|
+
];
|
|
19
|
+
/** `AGENT=<name>` / `AI_AGENT=<name>` standard value -> harness id. */
|
|
20
|
+
const STD_NAMES = {
|
|
21
|
+
goose: "goose",
|
|
22
|
+
amp: "amp",
|
|
23
|
+
claude: "claude-code",
|
|
24
|
+
"claude-code": "claude-code",
|
|
25
|
+
cursor: "cursor",
|
|
26
|
+
codex: "codex",
|
|
27
|
+
cline: "cline",
|
|
28
|
+
aider: "aider",
|
|
29
|
+
opencode: "opencode",
|
|
30
|
+
gemini: "gemini-cli",
|
|
31
|
+
copilot: "copilot",
|
|
32
|
+
kiro: "kiro"
|
|
33
|
+
};
|
|
34
|
+
/** Harnesses exposing a native hook system (vs CLI-only integration). */
|
|
35
|
+
const HOOK_CAPABLE = /* @__PURE__ */ new Set([
|
|
36
|
+
"claude-code",
|
|
37
|
+
"codex",
|
|
38
|
+
"cursor",
|
|
39
|
+
"cline",
|
|
40
|
+
"gemini-cli",
|
|
41
|
+
"opencode"
|
|
42
|
+
]);
|
|
43
|
+
/** Integration mode for a harness id. */
|
|
44
|
+
function modeFor(id) {
|
|
45
|
+
return HOOK_CAPABLE.has(id) ? "hook" : "cli";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Detect the current AI coding harness from environment signals.
|
|
49
|
+
* Priority: `AGENT` standard -> `AI_AGENT` standard -> tool-specific vars -> unknown.
|
|
50
|
+
* @param env - environment map (defaults to `process.env`)
|
|
51
|
+
*/
|
|
52
|
+
function detectHarness(env = process.env) {
|
|
53
|
+
const agent = env.AGENT?.trim().toLowerCase();
|
|
54
|
+
const agentId = agent ? STD_NAMES[agent] : void 0;
|
|
55
|
+
if (agentId) return {
|
|
56
|
+
id: agentId,
|
|
57
|
+
mode: modeFor(agentId),
|
|
58
|
+
via: "agent-std"
|
|
59
|
+
};
|
|
60
|
+
const ai = env.AI_AGENT?.trim().toLowerCase();
|
|
61
|
+
const aiId = ai ? STD_NAMES[ai] : void 0;
|
|
62
|
+
if (aiId) return {
|
|
63
|
+
id: aiId,
|
|
64
|
+
mode: modeFor(aiId),
|
|
65
|
+
via: "ai-agent-std"
|
|
66
|
+
};
|
|
67
|
+
for (const [key, id] of ENV_SIGNALS) if (env[key]?.trim()) return {
|
|
68
|
+
id,
|
|
69
|
+
mode: modeFor(id),
|
|
70
|
+
via: "env"
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
id: "unknown",
|
|
74
|
+
mode: "cli",
|
|
75
|
+
via: "fallback"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Convenience: the integration mode of the current harness. */
|
|
79
|
+
function detectMode(env = process.env) {
|
|
80
|
+
return detectHarness(env).mode;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { detectMode as n, modeFor as r, detectHarness as t };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/detect/harness.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Runtime detection of the AI coding harness, and its integration mode.
|
|
4
|
+
* Env-signal names verified 2026 (agentx, agents.md#136, @vercel/detect-agent,
|
|
5
|
+
* official Claude Code / Cursor / Gemini / Codex docs). Presence-based: the
|
|
6
|
+
* value is ignored except for the `AGENT` / `AI_AGENT` standards.
|
|
7
|
+
*/
|
|
8
|
+
/** Known AI coding harnesses detectable at runtime. */
|
|
9
|
+
type HarnessId = "claude-code" | "codex" | "cursor" | "cline" | "gemini-cli" | "opencode" | "windsurf" | "copilot" | "aider" | "kiro" | "goose" | "amp" | "unknown";
|
|
10
|
+
/** Integration mode: `hook` = native lifecycle hooks; `cli` = run as an external step. */
|
|
11
|
+
type HarnessMode = "hook" | "cli";
|
|
12
|
+
/** How the harness was identified. */
|
|
13
|
+
type HarnessVia = "agent-std" | "ai-agent-std" | "env" | "fallback";
|
|
14
|
+
/** Result of {@link detectHarness}. */
|
|
15
|
+
interface HarnessInfo {
|
|
16
|
+
id: HarnessId;
|
|
17
|
+
mode: HarnessMode;
|
|
18
|
+
via: HarnessVia;
|
|
19
|
+
}
|
|
20
|
+
/** Integration mode for a harness id. */
|
|
21
|
+
declare function modeFor(id: HarnessId): HarnessMode;
|
|
22
|
+
/**
|
|
23
|
+
* Detect the current AI coding harness from environment signals.
|
|
24
|
+
* Priority: `AGENT` standard -> `AI_AGENT` standard -> tool-specific vars -> unknown.
|
|
25
|
+
* @param env - environment map (defaults to `process.env`)
|
|
26
|
+
*/
|
|
27
|
+
declare function detectHarness(env?: Record<string, string | undefined>): HarnessInfo;
|
|
28
|
+
/** Convenience: the integration mode of the current harness. */
|
|
29
|
+
declare function detectMode(env?: Record<string, string | undefined>): HarnessMode;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { detectHarness as a, HarnessVia as i, HarnessInfo as n, detectMode as o, HarnessMode as r, modeFor as s, HarnessId as t };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/config/env.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Robust integer-from-env parser.
|
|
4
|
+
* undefined / empty / whitespace / NaN / float / <= 0 all fall back to `fallback`.
|
|
5
|
+
* `Number("")` is 0, so the empty guard is required before `Number()`.
|
|
6
|
+
*/
|
|
7
|
+
declare function parseEnvInt(raw: string | undefined, fallback: number): number;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/config/ttl.d.ts
|
|
10
|
+
/** Default enforcement-freshness window, in seconds (2 minutes). */
|
|
11
|
+
declare const DEFAULT_TTL_SEC = 120;
|
|
12
|
+
/** Default env var name carrying the TTL override. */
|
|
13
|
+
declare const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the enforcement TTL (seconds) from an env map.
|
|
16
|
+
* @param env - environment map (defaults to `process.env`)
|
|
17
|
+
* @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
|
|
18
|
+
*/
|
|
19
|
+
declare function resolveTtlSec(env?: Record<string, string | undefined>, key?: string): number;
|
|
20
|
+
/** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
|
|
21
|
+
declare function ttlLabel(sec: number): string;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/config/limits.d.ts
|
|
24
|
+
/** Default SOLID max lines per file. */
|
|
25
|
+
declare const DEFAULT_MAX_LINES = 100;
|
|
26
|
+
/** Default env var name carrying the max-lines override. */
|
|
27
|
+
declare const MAX_LINES_ENV_KEY = "FUSE_SOLID_MAX_LINES";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the SOLID max-lines limit from an env map.
|
|
30
|
+
* @param env - environment map (defaults to `process.env`)
|
|
31
|
+
* @param key - env var name (defaults to `FUSE_SOLID_MAX_LINES`)
|
|
32
|
+
*/
|
|
33
|
+
declare function resolveMaxLines(env?: Record<string, string | undefined>, key?: string): number;
|
|
34
|
+
/** Advisory module-split headroom = `maxLines - 10` (never below 1). */
|
|
35
|
+
declare function splitTarget(maxLines: number): number;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { DEFAULT_TTL_SEC as a, ttlLabel as c, splitTarget as i, parseEnvInt as l, MAX_LINES_ENV_KEY as n, TTL_ENV_KEY as o, resolveMaxLines as r, resolveTtlSec as s, DEFAULT_MAX_LINES as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/cache/compact.d.ts
|
|
2
|
+
/** Strip HTML entities + boilerplate, normalize blank lines, truncate to ~5KB. */
|
|
3
|
+
declare function compactMarkdown(content: string): string;
|
|
4
|
+
/** 8-char MD5 of `${toolName}::${query}`. */
|
|
5
|
+
declare function queryHash(toolName: string, query: string): string;
|
|
6
|
+
/** Bag-of-words Jaccard similarity strictly greater than `threshold`. */
|
|
7
|
+
declare function jaccardSimilar(a: string, b: string, threshold?: number): boolean;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/cache/io.d.ts
|
|
10
|
+
/** Read a JSON array from `path`; [] on missing/corrupt/non-array. */
|
|
11
|
+
declare function loadIndex(path: string): unknown[];
|
|
12
|
+
/** Summary of a cache index. */
|
|
13
|
+
interface IndexSummary {
|
|
14
|
+
total: number;
|
|
15
|
+
byTool: Record<string, number>;
|
|
16
|
+
oldestTs: string | null;
|
|
17
|
+
newestTs: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** Summarize an index of `{ tool?, ts? }` entries. */
|
|
20
|
+
declare function summarizeIndex(index: unknown[]): IndexSummary;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/cache/mcp-response.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Extract usable markdown from an MCP `tool_response`: a string, a list of
|
|
25
|
+
* content blocks (non-text blocks skipped), or any JSON structure (fallback).
|
|
26
|
+
* Recurses up to depth 5 to guard against pathological/cyclic structures.
|
|
27
|
+
*/
|
|
28
|
+
declare function extractText(resp: unknown, depth?: number): string;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { compactMarkdown as a, summarizeIndex as i, IndexSummary as n, jaccardSimilar as o, loadIndex as r, queryHash as s, extractText as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/memory/state.d.ts
|
|
2
|
+
/** Per-project reminder throttle state, stored at `<root>/MEMORY/state.json`. */
|
|
3
|
+
interface ReminderState {
|
|
4
|
+
lastRemindedAt: number;
|
|
5
|
+
lastCodeEditAt: number;
|
|
6
|
+
}
|
|
7
|
+
/** Absolute path of the per-project throttle state file. */
|
|
8
|
+
declare function stateFileFor(root: string): string;
|
|
9
|
+
/** Read both throttle timestamps; missing/corrupt fields default to 0. */
|
|
10
|
+
declare function readState(file: string): ReminderState;
|
|
11
|
+
/** Persist one field without clobbering the other (read-modify-write). */
|
|
12
|
+
declare function setStateField(file: string, key: keyof ReminderState, value: number): void;
|
|
13
|
+
/** Local wall-clock timestamp for a lesson bullet: `YYYY-MM-DD HH:MM`. */
|
|
14
|
+
declare function nowStamp(): string;
|
|
15
|
+
/** Throttle window (ms) from `FUSE_LESSONS_THROTTLE_MIN` (default 5 min). */
|
|
16
|
+
declare function throttleMs(env?: Record<string, string | undefined>): number;
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/memory/registry.d.ts
|
|
19
|
+
/** Absolute path of the global roots registry, or null when home is unusable. */
|
|
20
|
+
declare function registryFile(home?: string | undefined): string | null;
|
|
21
|
+
/** Read the registered project roots (deduplicated string entries only). */
|
|
22
|
+
declare function readRoots(home?: string): string[];
|
|
23
|
+
/** Register a project root once (deduplicated, read-modify-write, non-throwing). */
|
|
24
|
+
declare function addRoot(root: string, home?: string): void;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/memory/gitignore.d.ts
|
|
27
|
+
/** Ensure `<memoryDir>/.gitignore` ignores the machine-local `state.json`. */
|
|
28
|
+
declare function ensureMemoryGitignore(memoryDir: string): void;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ReminderState as a, setStateField as c, registryFile as i, stateFileFor as l, addRoot as n, nowStamp as o, readRoots as r, readState as s, ensureMemoryGitignore as t, throttleMs as u };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/freshness/trivial-edit-counter.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Increment a session's trivial-edit counter, evicting timestamps older than
|
|
4
|
+
* `windowMs`. Decoupled + injectable `now` for testability.
|
|
5
|
+
* @param filePath - session state file path
|
|
6
|
+
* @param windowMs - sliding window in ms
|
|
7
|
+
* @param now - current epoch ms (defaults to `Date.now()`)
|
|
8
|
+
* @returns number of trivial edits within the window (including this one)
|
|
9
|
+
*/
|
|
10
|
+
declare function incrementTrivialEditCounter(filePath: string, windowMs: number, now?: number): Promise<number>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { incrementTrivialEditCounter as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|