@fusengine/harness 0.1.15 → 0.1.16

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,7 +3,7 @@ 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-CsWHdBJp.mjs";
5
5
  import { n as writeInitFile, t as initFor } from "../run-D91N4ul1.mjs";
6
- import { t as handleHook } from "../handle-D_cJGRjJ.mjs";
6
+ import { t as handleHook } from "../handle-Sq-byRft.mjs";
7
7
  //#region src/cli/bin.ts
8
8
  /**
9
9
  * harness — CLI for @fusengine/harness.
@@ -1,13 +1,15 @@
1
1
  import { r as projectLayout } from "./layout-C0jaaCQC.mjs";
2
2
  import { E as detectFramework, t as evaluate } from "./evaluate-urGmc24B.mjs";
3
- import { n as capVerbosity, o as evaluateApex } from "./verbosity-ZYT7tLCw.mjs";
3
+ import { n as capVerbosity, r as detectCreationIntent, s as evaluateApex } from "./verbosity-BqM4C7_V.mjs";
4
4
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
5
5
  import { a as extractText, r as cacheStore, t as cacheLookup } from "./store-DeIsfMg5.mjs";
6
6
  import { t as loadRefs } from "./loader-BephwI8n.mjs";
7
- import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-BnHpq2ZB.mjs";
7
+ import { a as recordAgent, c as recordRefRead, l as recordTrivialEdit, n as saveTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as loadTrack, u as trivialCount } from "./store-BnHpq2ZB.mjs";
8
8
  import { join } from "node:path";
9
9
  import { tmpdir } from "node:os";
10
10
  //#region src/runtime/activity.ts
11
+ /** Min response length (chars) for a lead agent call to count as `sufficient`. */
12
+ const AGENT_QUALITY_MIN = 500;
11
13
  /** Read tools across harnesses (Claude `Read`, Gemini/Cline `read_file`, …). */
12
14
  const READ_TOOLS = /* @__PURE__ */ new Set([
13
15
  "Read",
@@ -30,11 +32,18 @@ function activityFor(event) {
30
32
  };
31
33
  if (event.tool === "Task") {
32
34
  const name = String(event.input?.subagent_type ?? "").split(":").pop() ?? "";
33
- return name ? {
35
+ if (!name) return null;
36
+ const quality = event.responseLength === void 0 ? void 0 : event.responseLength > AGENT_QUALITY_MIN ? "sufficient" : "insufficient";
37
+ return quality ? {
38
+ kind: "agent",
39
+ name,
40
+ ts: event.now,
41
+ quality
42
+ } : {
34
43
  kind: "agent",
35
44
  name,
36
45
  ts: event.now
37
- } : null;
46
+ };
38
47
  }
39
48
  if (READ_TOOLS.has(event.tool)) {
40
49
  const path = String(event.input?.file_path ?? event.input?.path ?? "");
@@ -193,7 +202,7 @@ function trackFile(sessionId, baseDir = join(tmpdir(), "fuse-harness")) {
193
202
  /** Apply an activity to a session's track and persist it (PostToolUse path). */
194
203
  async function recordActivity(file, activity) {
195
204
  const track = await loadTrack(file);
196
- await saveTrack(file, activity.kind === "agent" ? recordAgent(track, activity.name, activity.ts) : activity.kind === "doc" ? recordDoc(track, activity.framework, activity.sessionId, activity.source) : recordRefRead(track, activity.path));
205
+ await saveTrack(file, activity.kind === "agent" ? recordAgent(track, activity.name, activity.ts, activity.quality) : activity.kind === "doc" ? recordDoc(track, activity.framework, activity.sessionId, activity.source) : recordRefRead(track, activity.path));
197
206
  }
198
207
  //#endregion
199
208
  //#region src/runtime/respond.ts
@@ -243,14 +252,24 @@ async function handleHook(id, payload, opts) {
243
252
  const file = trackFile(event.sessionId, layout.trackDir);
244
253
  const mcpDir = layout.cacheDir;
245
254
  const framework = detectFramework(event.filePath ?? "", event.content ?? "");
255
+ const userPrompt = typeof payload.prompt === "string" ? payload.prompt : void 0;
256
+ if (userPrompt !== void 0) {
257
+ await saveTrack(file, recordBrainstormRequired(await loadTrack(file), detectCreationIntent(userPrompt)));
258
+ return {
259
+ stdout: "",
260
+ exit: 0
261
+ };
262
+ }
246
263
  if (event.phase === "post") {
247
- mcpPostStore(event.tool, event.input, payload.tool_response ?? payload.tool_output, mcpDir);
264
+ const response = payload.tool_response ?? payload.tool_output;
265
+ mcpPostStore(event.tool, event.input, response, mcpDir);
248
266
  const activity = activityFor({
249
267
  tool: event.tool,
250
268
  input: event.input,
251
269
  sessionId: event.sessionId,
252
270
  framework,
253
- now: opts.now
271
+ now: opts.now,
272
+ responseLength: extractText(response).length
254
273
  });
255
274
  if (activity) await recordActivity(file, activity);
256
275
  return {
package/dist/index.mjs CHANGED
@@ -4,11 +4,11 @@ import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "./lay
4
4
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
5
5
  import { n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-ff0_poWU.mjs";
6
6
  import { n as detectMode, r as modeFor, t as detectHarness } from "./harness-C8Nxxyn_.mjs";
7
- import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "./policy-Cpa5aWek.mjs";
7
+ import { n as detectProjectType, r as isApexCommand, t as DEV_KEYWORDS } from "./policy-EuVJ_5hS.mjs";
8
8
  import { C as matchPatterns, E as detectFramework, S as SYSTEM_INSTALL, T as evaluateFileSize, _ as CRITICAL_PATTERNS, a as PHP_DECL_RE, b as GIT_BLOCKED, c as TS_DECL_RE, d as CODE_MUTATORS, f as CODE_REDIRECT, g as ASK_PATTERNS, h as protectedPathGuard, i as installGuard, l as interfaceSeparationGuard, m as PROTECTED_FRAGMENTS, n as GUARDS, o as PY_MODEL_RE, p as bashWriteGuard, r as runGuards, s as SWIFT_PROTO_RE, t as evaluate, u as ASK_WRITERS, v as securityGuard, w as countLines, x as PROJECT_INSTALL, y as GIT_ASK } from "./evaluate-urGmc24B.mjs";
9
9
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-Dd_x1-tZ.mjs";
10
10
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-Dj3AfgBE.mjs";
11
- import { a as docConsultedGate, c as solidReadGate, i as brainstormGate, n as capVerbosity, o as evaluateApex, r as APEX_GATES, s as freshnessGate, t as MAX_EXA_RESULTS } from "./verbosity-ZYT7tLCw.mjs";
11
+ import { a as brainstormGate, c as freshnessGate, i as APEX_GATES, l as solidReadGate, n as capVerbosity, o as docConsultedGate, r as detectCreationIntent, s as evaluateApex, t as MAX_EXA_RESULTS } from "./verbosity-BqM4C7_V.mjs";
12
12
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
13
13
  import { a as nowStamp, c as stateFileFor, i as lessonsFileFor, l as throttleMs, n as readRoots, o as readState, r as registryFile, s as setStateField, t as addRoot, u as ensureMemoryGitignore } from "./memory-BkoEbdec.mjs";
14
14
  import { a as queryHash, i as jaccardSimilar, n as summarizeIndex, r as compactMarkdown, t as loadIndex } from "./cache-BzbX-ztL.mjs";
@@ -1,4 +1,4 @@
1
- import { i as isApexCommand, n as DEV_KEYWORDS, r as detectProjectType, t as detectCreationIntent } from "../policy-Cpa5aWek.mjs";
1
+ import { n as detectProjectType, r as isApexCommand, t as DEV_KEYWORDS } from "../policy-EuVJ_5hS.mjs";
2
2
  import { C as matchPatterns, E as detectFramework, S as SYSTEM_INSTALL, T as evaluateFileSize, _ as CRITICAL_PATTERNS, a as PHP_DECL_RE, b as GIT_BLOCKED, c as TS_DECL_RE, d as CODE_MUTATORS, f as CODE_REDIRECT, g as ASK_PATTERNS, h as protectedPathGuard, i as installGuard, l as interfaceSeparationGuard, m as PROTECTED_FRAGMENTS, n as GUARDS, o as PY_MODEL_RE, p as bashWriteGuard, r as runGuards, s as SWIFT_PROTO_RE, t as evaluate, u as ASK_WRITERS, v as securityGuard, w as countLines, x as PROJECT_INSTALL, y as GIT_ASK } from "../evaluate-urGmc24B.mjs";
3
- import { a as docConsultedGate, c as solidReadGate, i as brainstormGate, n as capVerbosity, o as evaluateApex, r as APEX_GATES, s as freshnessGate, t as MAX_EXA_RESULTS } from "../verbosity-ZYT7tLCw.mjs";
3
+ import { a as brainstormGate, c as freshnessGate, i as APEX_GATES, l as solidReadGate, n as capVerbosity, o as docConsultedGate, r as detectCreationIntent, s as evaluateApex, t as MAX_EXA_RESULTS } from "../verbosity-BqM4C7_V.mjs";
4
4
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GUARDS, MAX_EXA_RESULTS, PHP_DECL_RE, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PY_MODEL_RE, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, bashWriteGuard, brainstormGate, capVerbosity, countLines, detectCreationIntent, detectFramework, detectProjectType, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, matchPatterns, protectedPathGuard, runGuards, securityGuard, solidReadGate };
@@ -30,16 +30,4 @@ function detectProjectType(dir) {
30
30
  return "generic";
31
31
  }
32
32
  //#endregion
33
- //#region src/policy/creation-intent.ts
34
- const CREATE_RE = /\b(?:create|implement|add|build|new|feature|component|generate|make|develop|scaffold)\b/i;
35
- const SKIP_RE = /\b(?:fix|bug|debug|update|refactor|rename|move|delete|remove|commit|push|edit|modify|change)\b/i;
36
- /**
37
- * True when a prompt expresses creation intent (a new feature/component) and is
38
- * not a fix/refactor — the signal that brainstorming should precede creation.
39
- * The harness calls this on UserPromptSubmit, then `recordBrainstormRequired`.
40
- */
41
- function detectCreationIntent(prompt) {
42
- return CREATE_RE.test(prompt) && !SKIP_RE.test(prompt);
43
- }
44
- //#endregion
45
- export { isApexCommand as i, DEV_KEYWORDS as n, detectProjectType as r, detectCreationIntent as t };
33
+ export { detectProjectType as n, isApexCommand as r, DEV_KEYWORDS as t };
@@ -1,5 +1,6 @@
1
1
  import { t as Prompt } from "../types-D56jSgD9.mjs";
2
2
  import { t as RefMeta } from "../types-CY5qT2X1.mjs";
3
+ import { t as AgentQuality } from "../session-state-DxNkIBZ2.mjs";
3
4
 
4
5
  //#region src/runtime/paths.d.ts
5
6
  /** Path to a session's track file (under a per-tool base dir). */
@@ -18,6 +19,7 @@ type Activity = {
18
19
  kind: "agent";
19
20
  name: string;
20
21
  ts: number;
22
+ quality?: AgentQuality;
21
23
  } | {
22
24
  kind: "doc";
23
25
  framework: string;
@@ -39,6 +41,8 @@ interface ToolEvent {
39
41
  sessionId: string;
40
42
  framework: string;
41
43
  now: number;
44
+ /** Length of the tool response (POST events) — drives agent `quality`. */
45
+ responseLength?: number;
42
46
  }
43
47
  /**
44
48
  * Map a live tool-use to the activity to record, or null when nothing is
@@ -1,5 +1,5 @@
1
1
  import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
2
- import { a as normalizeEvent, c as mcpPostStore, d as DEFAULT_WINDOW_MS, f as REQUIRED_AGENTS, h as activityFor, i as trackFile, l as mcpPreIntercept, m as gate, n as respond, o as MCP_TTL_MS, p as TRIVIAL_BUDGET, r as recordActivity, s as isMcpTool, t as handleHook, u as queryOf } from "../handle-D_cJGRjJ.mjs";
2
+ import { a as normalizeEvent, c as mcpPostStore, d as DEFAULT_WINDOW_MS, f as REQUIRED_AGENTS, h as activityFor, i as trackFile, l as mcpPreIntercept, m as gate, n as respond, o as MCP_TTL_MS, p as TRIVIAL_BUDGET, r as recordActivity, s as isMcpTool, t as handleHook, u as queryOf } from "../handle-Sq-byRft.mjs";
3
3
  //#region src/runtime/storage.ts
4
4
  /**
5
5
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -0,0 +1,35 @@
1
+ import { t as AuthEntry } from "./doc-helpers-CG1nuf-c.mjs";
2
+
3
+ //#region src/tracking/session-state.d.ts
4
+ /** Quality of a recorded agent call (the freshness gate ignores insufficient ones). */
5
+ type AgentQuality = "sufficient" | "insufficient";
6
+ /** Recorded session activity that feeds the APEX gates. */
7
+ interface SessionTrack {
8
+ authorizations: Record<string, AuthEntry>;
9
+ refsRead: string[];
10
+ agents: {
11
+ name: string;
12
+ ts: number;
13
+ quality?: AgentQuality;
14
+ }[];
15
+ trivialEdits: number[];
16
+ brainstormRequired?: boolean;
17
+ }
18
+ /** A fresh, empty track. */
19
+ declare function emptyTrack(): SessionTrack;
20
+ /** Record a doc consultation (Context7/Exa) for a framework in this session. Immutable. */
21
+ declare function recordDoc(track: SessionTrack, framework: string, sessionId: string, source: string): SessionTrack;
22
+ /** Record that a SOLID reference file was read (deduped). Immutable. */
23
+ declare function recordRefRead(track: SessionTrack, path: string): SessionTrack;
24
+ /** Record an agent/tool call with a timestamp + optional quality. Immutable. */
25
+ declare function recordAgent(track: SessionTrack, name: string, ts: number, quality?: AgentQuality): SessionTrack;
26
+ /** True when ALL of `names` ran within `windowMs` with non-insufficient quality. */
27
+ declare function agentsFresh(track: SessionTrack, names: string[], windowMs: number, now: number): boolean;
28
+ /** Record a trivial edit timestamp (sliding window; old evicted). Immutable. */
29
+ declare function recordTrivialEdit(track: SessionTrack, ts: number, windowMs: number, now: number): SessionTrack;
30
+ /** Count trivial edits within the sliding window. */
31
+ declare function trivialCount(track: SessionTrack, windowMs: number, now: number): number;
32
+ /** Set the brainstorm-required flag (from creation-intent detection). Immutable. */
33
+ declare function recordBrainstormRequired(track: SessionTrack, required: boolean): SessionTrack;
34
+ //#endregion
35
+ export { recordAgent as a, recordRefRead as c, emptyTrack as i, recordTrivialEdit as l, SessionTrack as n, recordBrainstormRequired as o, agentsFresh as r, recordDoc as s, AgentQuality as t, trivialCount as u };
@@ -1,37 +1,5 @@
1
- import { t as AuthEntry } from "../doc-helpers-CG1nuf-c.mjs";
1
+ import { a as recordAgent, c as recordRefRead, i as emptyTrack, l as recordTrivialEdit, n as SessionTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as AgentQuality, u as trivialCount } from "../session-state-DxNkIBZ2.mjs";
2
2
 
3
- //#region src/tracking/session-state.d.ts
4
- /** Quality of a recorded agent call (the freshness gate ignores insufficient ones). */
5
- type AgentQuality = "sufficient" | "insufficient";
6
- /** Recorded session activity that feeds the APEX gates. */
7
- interface SessionTrack {
8
- authorizations: Record<string, AuthEntry>;
9
- refsRead: string[];
10
- agents: {
11
- name: string;
12
- ts: number;
13
- quality?: AgentQuality;
14
- }[];
15
- trivialEdits: number[];
16
- brainstormRequired?: boolean;
17
- }
18
- /** A fresh, empty track. */
19
- declare function emptyTrack(): SessionTrack;
20
- /** Record a doc consultation (Context7/Exa) for a framework in this session. Immutable. */
21
- declare function recordDoc(track: SessionTrack, framework: string, sessionId: string, source: string): SessionTrack;
22
- /** Record that a SOLID reference file was read (deduped). Immutable. */
23
- declare function recordRefRead(track: SessionTrack, path: string): SessionTrack;
24
- /** Record an agent/tool call with a timestamp + optional quality. Immutable. */
25
- declare function recordAgent(track: SessionTrack, name: string, ts: number, quality?: AgentQuality): SessionTrack;
26
- /** True when ALL of `names` ran within `windowMs` with non-insufficient quality. */
27
- declare function agentsFresh(track: SessionTrack, names: string[], windowMs: number, now: number): boolean;
28
- /** Record a trivial edit timestamp (sliding window; old evicted). Immutable. */
29
- declare function recordTrivialEdit(track: SessionTrack, ts: number, windowMs: number, now: number): SessionTrack;
30
- /** Count trivial edits within the sliding window. */
31
- declare function trivialCount(track: SessionTrack, windowMs: number, now: number): number;
32
- /** Set the brainstorm-required flag (from creation-intent detection). Immutable. */
33
- declare function recordBrainstormRequired(track: SessionTrack, required: boolean): SessionTrack;
34
- //#endregion
35
3
  //#region src/tracking/store.d.ts
36
4
  /** Load a session track from a file (an empty track if absent/corrupt). */
37
5
  declare function loadTrack(file: string): Promise<SessionTrack>;
@@ -52,6 +52,18 @@ function evaluateApex(ctx, gates = APEX_GATES) {
52
52
  return gates.reduce((hit, gate) => hit ?? gate(ctx), null);
53
53
  }
54
54
  //#endregion
55
+ //#region src/policy/creation-intent.ts
56
+ const CREATE_RE = /\b(?:create|implement|add|build|new|feature|component|generate|make|develop|scaffold)\b/i;
57
+ const SKIP_RE = /\b(?:fix|bug|debug|update|refactor|rename|move|delete|remove|commit|push|edit|modify|change)\b/i;
58
+ /**
59
+ * True when a prompt expresses creation intent (a new feature/component) and is
60
+ * not a fix/refactor — the signal that brainstorming should precede creation.
61
+ * The harness calls this on UserPromptSubmit, then `recordBrainstormRequired`.
62
+ */
63
+ function detectCreationIntent(prompt) {
64
+ return CREATE_RE.test(prompt) && !SKIP_RE.test(prompt);
65
+ }
66
+ //#endregion
55
67
  //#region src/policy/verbosity.ts
56
68
  /** Exa MCP tools whose result count is capped (Context7 has no verbosity knob). */
57
69
  const EXA_TOOLS = /exa__web_search|exa__get_code_context|exa_web_search|exa_get_code_context/i;
@@ -71,4 +83,4 @@ function capVerbosity(tool, input) {
71
83
  };
72
84
  }
73
85
  //#endregion
74
- export { docConsultedGate as a, solidReadGate as c, brainstormGate as i, capVerbosity as n, evaluateApex as o, APEX_GATES as r, freshnessGate as s, MAX_EXA_RESULTS as t };
86
+ export { brainstormGate as a, freshnessGate as c, APEX_GATES as i, solidReadGate as l, capVerbosity as n, docConsultedGate as o, detectCreationIntent as r, evaluateApex as s, MAX_EXA_RESULTS as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
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",