@fusengine/harness 0.1.76 → 0.1.77

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.
@@ -1,2 +1,2 @@
1
- import { a as denyResponse, c as informResponse, d as toClaudeResponse, i as contextResponse, l as readClaudeInput, n as attachSystemMessage, o as fileSizeGuard, r as blockResponse, s as guard, t as ClaudeHookInput, u as systemMessage } from "../../index-a6kvHEPa.mjs";
1
+ import { a as denyResponse, c as informResponse, d as toClaudeResponse, i as contextResponse, l as readClaudeInput, n as attachSystemMessage, o as fileSizeGuard, r as blockResponse, s as guard, t as ClaudeHookInput, u as systemMessage } from "../../index-DGxNjXW7.mjs";
2
2
  export { ClaudeHookInput, attachSystemMessage, blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, informResponse, readClaudeInput, systemMessage, toClaudeResponse };
@@ -1,2 +1,2 @@
1
- import { a as fileSizeGuard, c as readClaudeInput, i as denyResponse, l as systemMessage, n as blockResponse, o as guard, r as contextResponse, s as informResponse, t as attachSystemMessage, u as toClaudeResponse } from "../../claude-BxQwhdZB.mjs";
1
+ import { a as fileSizeGuard, c as readClaudeInput, i as denyResponse, l as systemMessage, n as blockResponse, o as guard, r as contextResponse, s as informResponse, t as attachSystemMessage, u as toClaudeResponse } from "../../claude-51RSspJ1.mjs";
2
2
  export { attachSystemMessage, blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, informResponse, readClaudeInput, systemMessage, toClaudeResponse };
@@ -1,5 +1,5 @@
1
1
  import { t as Prompt } from "../../types-DVbIl9md.mjs";
2
- import { a as denyResponse, c as informResponse, i as contextResponse, l as readClaudeInput, t as ClaudeHookInput } from "../../index-a6kvHEPa.mjs";
2
+ import { a as denyResponse, c as informResponse, i as contextResponse, l as readClaudeInput, t as ClaudeHookInput } from "../../index-DGxNjXW7.mjs";
3
3
 
4
4
  //#region src/adapters/codex/index.d.ts
5
5
  /**
@@ -2,7 +2,7 @@ import { t as evaluate } from "../../evaluate-ClfbCDiY.mjs";
2
2
  import { f as countLines } from "../../home-state-BXf38Zi1.mjs";
3
3
  import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
4
4
  import { n as parseApplyPatch, t as isBypassPermissions } from "../../permission-mode-BN3MNgbm.mjs";
5
- import { c as readClaudeInput, i as denyResponse, r as contextResponse, s as informResponse, v as commandToString } from "../../claude-BxQwhdZB.mjs";
5
+ import { c as readClaudeInput, i as denyResponse, r as contextResponse, s as informResponse, v as commandToString } from "../../claude-51RSspJ1.mjs";
6
6
  //#region src/adapters/codex/index.ts
7
7
  /**
8
8
  * OpenAI Codex CLI adapter (hook-mode). Codex's `PreToolUse` hook (since 2026)
@@ -1,2 +1,2 @@
1
- import { n as readHermesInput, r as toHermesResponse, t as guard } from "../../hermes-OVoZtUzT.mjs";
1
+ import { n as readHermesInput, r as toHermesResponse, t as guard } from "../../hermes-rTmVdhUL.mjs";
2
2
  export { guard, readHermesInput, toHermesResponse };
@@ -178,23 +178,19 @@ function informResponse(event, notice, context) {
178
178
  }
179
179
  });
180
180
  }
181
- /** Attach a user-visible `systemMessage` onto an already-rendered hook stdout JSON ({@link systemMessage} alone when empty/unparseable). */
181
+ /** Attach a `systemMessage`, MERGING it with any already present (`\n`-joined) instead of overwriting — several producers may each call this on the same stdout in one hook invocation. */
182
182
  function attachSystemMessage(stdout, notice) {
183
183
  try {
184
+ const parsed = JSON.parse(stdout), prev = typeof parsed.systemMessage === "string" ? parsed.systemMessage : "";
184
185
  return JSON.stringify({
185
- ...JSON.parse(stdout),
186
- systemMessage: notice
186
+ ...parsed,
187
+ systemMessage: prev ? `${prev}\n${notice}` : notice
187
188
  });
188
189
  } catch {
189
190
  return systemMessage(notice);
190
191
  }
191
192
  }
192
- /**
193
- * Render a portable {@link Prompt} as a Claude Code hook response:
194
- * `block` → `permissionDecision: deny`, `ask` → `permissionDecision: ask`
195
- * (interactive confirm), `inform` → `additionalContext` — plus the user-visible
196
- * `systemMessage` channel when the prompt carries a `userMessage`.
197
- */
193
+ /** Render a portable {@link Prompt} as a Claude Code hook response: `block` → deny, `ask` → interactive `permissionDecision`, `inform` → `additionalContext` (+ `systemMessage` when `userMessage` is set). */
198
194
  function toClaudeResponse(event, prompt) {
199
195
  const reason = formatPrompt(prompt);
200
196
  if (prompt.kind === "block") return denyResponse(event, reason);
@@ -206,10 +202,7 @@ function toClaudeResponse(event, prompt) {
206
202
  if (prompt.userMessage) return informResponse(event, prompt.userMessage, prompt.reason ? reason : "");
207
203
  return contextResponse(event, reason);
208
204
  }
209
- /**
210
- * Run the bundled policy over a Claude payload and return the native response
211
- * string (deny/ask/additionalContext), or null to allow.
212
- */
205
+ /** Run the bundled policy over a Claude payload and return the native response string (deny/ask/additionalContext), or null to allow. */
213
206
  function guard(input) {
214
207
  const result = evaluate({
215
208
  tool: input.tool_name ?? "Write",
package/dist/cli/bin.mjs CHANGED
@@ -4,8 +4,8 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
4
4
  import { t as claudeHome } from "../home-state-BXf38Zi1.mjs";
5
5
  import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-3tyTW9Ud.mjs";
6
6
  import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
7
- import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-BNWlWpKX.mjs";
8
- import { p as readStdin$1 } from "../claude-BxQwhdZB.mjs";
7
+ import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-BDlk2utY.mjs";
8
+ import { p as readStdin$1 } from "../claude-51RSspJ1.mjs";
9
9
  import { delimiter, join } from "node:path";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
@@ -14,8 +14,8 @@ import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cac
14
14
  import { t as loadRefs } from "./loader-AGz4nK7d.mjs";
15
15
  import { a as writeLastNonce, c as recordAgent, d as recordRefRead, f as recordTarget, h as apexAuthorizationGate, i as verifyTrack, l as recordBrainstormRequired, m as trivialCount, n as saveTrack, o as agentsFresh, p as recordTrivialEdit, r as signTrack, s as emptyTrack, t as loadTrack, u as recordDoc } from "./store-Bcn08y9m.mjs";
16
16
  import { n as parseApplyPatch, t as isBypassPermissions } from "./permission-mode-BN3MNgbm.mjs";
17
- import { _ as writeText, d as collectFiles, f as pathExists, g as spawnCapture, h as sleep, i as denyResponse, l as systemMessage, m as readText, n as blockResponse, r as contextResponse, s as informResponse, t as attachSystemMessage, v as commandToString } from "./claude-BxQwhdZB.mjs";
18
- import { r as toHermesResponse } from "./hermes-OVoZtUzT.mjs";
17
+ import { _ as writeText, d as collectFiles, f as pathExists, g as spawnCapture, h as sleep, i as denyResponse, l as systemMessage, m as readText, n as blockResponse, r as contextResponse, s as informResponse, t as attachSystemMessage, v as commandToString } from "./claude-51RSspJ1.mjs";
18
+ import { r as toHermesResponse } from "./hermes-rTmVdhUL.mjs";
19
19
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
20
20
  import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
21
21
  import { homedir } from "node:os";
@@ -597,7 +597,7 @@ function promptSubmitContext(prompt, cwd) {
597
597
  const ctx = buildClaudeMdContext(prompt, cwd);
598
598
  if (!ctx) return "";
599
599
  if (!oncePerWindow(claudeMdKey(prompt, ctx), 3e3)) return "";
600
- return contextResponse("UserPromptSubmit", ctx);
600
+ return attachSystemMessage(contextResponse("UserPromptSubmit", ctx), "CLAUDE.md injected");
601
601
  }
602
602
  /**
603
603
  * PreToolUse Task context injection: render the APEX sub-agent context as a
@@ -770,7 +770,7 @@ function sessionStartCore(cwd, home = homedir(), now = Date.now()) {
770
770
  const dev = devContext(cwd);
771
771
  runSessionStartCleanups(home, now);
772
772
  const ctx = [md, dev].filter(Boolean).join("\n");
773
- return ctx ? contextResponse("SessionStart", ctx) : "";
773
+ return ctx ? attachSystemMessage(contextResponse("SessionStart", ctx), "CLAUDE.md injected") : "";
774
774
  }
775
775
  //#endregion
776
776
  //#region src/runtime/lifecycle/inject-rules.ts
@@ -801,7 +801,7 @@ function readRules(rulesDir) {
801
801
  */
802
802
  function injectRules(pluginRoot, event) {
803
803
  const content = readRules(join(pluginRoot, "rules"));
804
- return content ? contextResponse(event, content) : "";
804
+ return content ? attachSystemMessage(contextResponse(event, content), "rules 00-08 injected") : "";
805
805
  }
806
806
  //#endregion
807
807
  //#region src/runtime/lifecycle/solid-detect.ts
@@ -1393,18 +1393,20 @@ function trackAgentMemory(data, home = homedir(), now = Date.now()) {
1393
1393
  //#region src/runtime/lifecycle/teammate-idle.ts
1394
1394
  /**
1395
1395
  * Handle TeammateIdle: when the teammate's session-changes file shows code was
1396
- * modified, suggest sniper validation as `additionalContext`. Ports
1397
- * `teammate-idle/validate-teammate-output.py`.
1396
+ * modified, suggest sniper validation as plain text — TeammateIdle has no
1397
+ * `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
1398
+ * ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
1399
+ * the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
1398
1400
  * @param data - The raw hook payload.
1399
1401
  * @param home - Home dir (defaults to `~`).
1400
- * @returns The native hook stdout (possibly empty).
1402
+ * @returns The plain notice text, or "" when no code file was modified.
1401
1403
  */
1402
1404
  function validateTeammateOutput(data, home = homedir()) {
1403
1405
  const teammate = String(data.teammate_name ?? "unknown");
1404
1406
  const changes = loadSessionState(sanitizeSessionId(data.session_id) ?? "unknown", home).changes;
1405
1407
  const count = changes?.cumulativeCodeFiles ?? 0;
1406
- if (count > 0) return contextResponse("TeammateIdle", `Teammate '${teammate}' going idle after modifying ${count} code file(s): ${(changes?.modifiedFiles ?? []).slice(0, 5).join(", ")}. Consider running sniper validation.`);
1407
- return "";
1408
+ if (count === 0) return "";
1409
+ return `Teammate '${teammate}' going idle after modifying ${count} code file(s): ${(changes?.modifiedFiles ?? []).slice(0, 5).join(", ")}. Consider running sniper validation.`;
1408
1410
  }
1409
1411
  //#endregion
1410
1412
  //#region src/runtime/lifecycle/tool-failure.ts
@@ -2210,19 +2212,12 @@ const IDLE_DEDUP_MS = 3e4;
2210
2212
  function missingDeliverables(sessionId, home) {
2211
2213
  return (loadSessionState(sessionId, home).changes?.modifiedFiles ?? []).filter((f) => typeof f === "string" && f !== "" && !existsSync(f));
2212
2214
  }
2213
- /** Pull the `additionalContext` body out of a `contextResponse` stdout ("" when empty/unparseable). */
2214
- function bodyOf(stdout) {
2215
- if (!stdout) return "";
2216
- try {
2217
- return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
2218
- } catch {
2219
- return "";
2220
- }
2221
- }
2222
2215
  /**
2223
2216
  * Handle TeammateIdle: merge the existing sniper suggestion with a missing-
2224
- * deliverable warning (deduped) into one `additionalContext` response, or "" when
2225
- * neither fires.
2217
+ * deliverable warning (deduped) into one advisory `systemMessage`, or "" when
2218
+ * neither fires. TeammateIdle rejects `hookSpecificOutput` (no `additionalContext`
2219
+ * channel) — `systemMessage` is the only user-visible, non-blocking channel it
2220
+ * accepts.
2226
2221
  * @param data - The raw TeammateIdle payload (`teammate_name`, `session_id`).
2227
2222
  * @param cwd - Project root (state dir for the dedup sidecar).
2228
2223
  * @param home - Home dir (defaults to `~`).
@@ -2230,7 +2225,7 @@ function bodyOf(stdout) {
2230
2225
  * @returns The native hook stdout, or "".
2231
2226
  */
2232
2227
  function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
2233
- const sniper = bodyOf(validateTeammateOutput(data, home));
2228
+ const sniper = validateTeammateOutput(data, home);
2234
2229
  const sessionId = sanitizeSessionId(data.session_id);
2235
2230
  const teammate = String(data.teammate_name ?? data.team_name ?? "unknown");
2236
2231
  let notice = "";
@@ -2244,7 +2239,7 @@ function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
2244
2239
  const merged = [sniper, notice].filter(Boolean).join("\n\n");
2245
2240
  if (!merged) return "";
2246
2241
  notify("human");
2247
- return contextResponse("TeammateIdle", merged);
2242
+ return systemMessage(merged);
2248
2243
  }
2249
2244
  //#endregion
2250
2245
  //#region src/policy/lessons/trigger-index.ts
@@ -3148,17 +3143,10 @@ function collectViolations(files, max) {
3148
3143
  return violations;
3149
3144
  }
3150
3145
  /**
3151
- * Handle TaskCompleted (ports `task-completed/validate-task-solid.py`, plus the
3152
- * receipt gate). SOLID violations surface first as `SOLID VIOLATION`
3153
- * additionalContext; once the files comply, {@link receiptGate} refuses a "done"
3154
- * that has no fresh passing tsc/test receipt. Also reused verbatim from `Stop`
3155
- * for Codex's core scope ({@link stopCore}): Codex never emits `TaskCompleted`
3156
- * (codex-plugins/docs/reference/hooks.md) so this is its only completion check;
3157
- * `event` lets that caller stamp the real hook name instead of a false one.
3146
+ * Handle TaskCompleted (ports `task-completed/validate-task-solid.py` + the
3147
+ * receipt gate); also reused verbatim for Codex's `Stop` via {@link stopCore}
3148
+ * (Codex never emits `TaskCompleted`) `event` stamps the real hook name.
3158
3149
  * @param payload - The TaskCompleted/Stop payload (`task_id`, `task_subject`, `session_id`).
3159
- * @param home - Home dir (defaults to `~`).
3160
- * @param now - Clock (defaults to `Date.now()`).
3161
- * @param stateDir - Track base dir (defaults to the cwd-derived state dir; matches `handleHook`).
3162
3150
  * @param event - The hook event name to stamp on the SOLID-violation response (default `TaskCompleted`).
3163
3151
  * @returns The native hook stdout, or `""` when the session is clean.
3164
3152
  */
@@ -3171,7 +3159,8 @@ function validateTaskSolid(payload, home = homedir(), now = Date.now(), stateDir
3171
3159
  const violations = collectViolations(files, max);
3172
3160
  if (violations.length === 0) return receiptGate(sid, files, now, stateDir) ?? "";
3173
3161
  const taskId = String(payload.task_id ?? "");
3174
- return contextResponse(event, `SOLID VIOLATION in task '${String(payload.task_subject ?? "")}' (${taskId}): ${violations.length} file(s) exceed ${max} lines: ` + violations.slice(0, 5).join("; "));
3162
+ const msg = `SOLID VIOLATION in task '${String(payload.task_subject ?? "")}' (${taskId}): ${violations.length} file(s) exceed ${max} lines: ` + violations.slice(0, 5).join("; ");
3163
+ return event === "TaskCompleted" ? systemMessage(msg) : contextResponse(event, msg);
3175
3164
  }
3176
3165
  //#endregion
3177
3166
  //#region src/runtime/lifecycle/cartographer/fs-util.ts
@@ -4003,13 +3992,13 @@ function injectMemory(cwd, event, now) {
4003
3992
  if (!content) return "";
4004
3993
  const { content: curated, archive, report } = curateLessons(content, now, root);
4005
3994
  if (curated !== content) content = persistCuration(file, root, curated, archive, content);
4006
- const ctx = `Project lessons — never reproduce these:\n${capFragment("lessons", compressInjection(content))}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`;
4007
- return report ? attachSystemMessage(contextResponse(event, ctx), `LESSON.md curation:\n${report}`) : contextResponse(event, ctx);
3995
+ const withNotice = attachSystemMessage(contextResponse(event, `Project lessons — never reproduce these:\n${capFragment("lessons", compressInjection(content))}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`), "lessons injected");
3996
+ return report ? attachSystemMessage(withNotice, `LESSON.md curation:\n${report}`) : withNotice;
4008
3997
  }
4009
3998
  /**
4010
3999
  * Route a fuse-lessons event to its handler. Returns the native stdout for
4011
- * context-injecting events (SessionStart/SubagentStart/Stop) or "" for the
4012
- * side-effect-only PostToolUse mark.
4000
+ * context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
4001
+ * MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
4013
4002
  * @param event - The raw hook event name.
4014
4003
  * @param payload - The raw hook payload.
4015
4004
  * @param cwd - Project root for memory injection.
@@ -4019,7 +4008,8 @@ function injectMemory(cwd, event, now) {
4019
4008
  function dispatchLessons(event, payload, cwd, now) {
4020
4009
  switch (event) {
4021
4010
  case "SessionStart":
4022
- case "SubagentStart": return injectMemory(cwd, event, now);
4011
+ case "SubagentStart":
4012
+ case "UserPromptSubmit": return injectMemory(cwd, event, now);
4023
4013
  case "Stop": return remindWrite(payload, now);
4024
4014
  case "PostToolUse":
4025
4015
  markWrite(payload, now);
@@ -5332,7 +5322,10 @@ function sessionStart(input) {
5332
5322
  function dispatchLifecycle(input) {
5333
5323
  switch (input.event) {
5334
5324
  case "SessionStart": return sessionStart(input);
5335
- case "UserPromptSubmit": return input.scope === "rules" ? injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event) : null;
5325
+ case "UserPromptSubmit":
5326
+ if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
5327
+ if (input.scope === "lessons") return dispatchLessons("UserPromptSubmit", input.payload, input.cwd, input.now);
5328
+ return null;
5336
5329
  case "SubagentStart":
5337
5330
  if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
5338
5331
  if (input.scope === "aipilot") return "";
@@ -7192,18 +7185,25 @@ async function recordActivity(file, activity) {
7192
7185
  * - cline: NO user-visible channel (`cancel`/`contextModification`/
7193
7186
  * `errorMessage`-on-cancel only) — the notice is dropped silently (a pure
7194
7187
  * notice returns "" = plain allow).
7195
- */
7196
- function respond(id, prompt) {
7188
+ * @param id - Harness id.
7189
+ * @param prompt - The portable prompt to render.
7190
+ * @param event - The firing hook event name for the claude-code/codex branch
7191
+ * (defaults to `"PreToolUse"`, the only phase every existing caller but
7192
+ * `handle-post.ts` renders) — a POST-phase caller MUST pass `"PostToolUse"`
7193
+ * (or its real raw event name) so `hookEventName` matches the event that
7194
+ * actually fired instead of a hardcoded, wrong `"PreToolUse"`.
7195
+ */
7196
+ function respond(id, prompt, event = "PreToolUse") {
7197
7197
  const message = formatPrompt(prompt);
7198
7198
  const { kind, userMessage, reason } = prompt;
7199
7199
  switch (id) {
7200
7200
  case "claude-code":
7201
7201
  case "codex":
7202
- if (kind === "block") return denyResponse("PreToolUse", message);
7203
- if (kind === "inform") return userMessage ? informResponse("PreToolUse", userMessage, reason ? message : "") : contextResponse("PreToolUse", message);
7204
- if (id === "codex") return denyResponse("PreToolUse", `[downgraded from ask — Codex has no interactive approval]\n${message}`);
7202
+ if (kind === "block") return denyResponse(event, message);
7203
+ if (kind === "inform") return userMessage ? informResponse(event, userMessage, reason ? message : "") : contextResponse(event, message);
7204
+ if (id === "codex") return denyResponse(event, `[downgraded from ask — Codex has no interactive approval]\n${message}`);
7205
7205
  return JSON.stringify({ hookSpecificOutput: {
7206
- hookEventName: "PreToolUse",
7206
+ hookEventName: event,
7207
7207
  permissionDecision: "ask",
7208
7208
  permissionDecisionReason: message
7209
7209
  } });
@@ -8465,7 +8465,7 @@ async function handlePost(ctx) {
8465
8465
  stdout: respond(id, userMessage ? {
8466
8466
  ...designWarn,
8467
8467
  userMessage
8468
- } : designWarn),
8468
+ } : designWarn, "PostToolUse"),
8469
8469
  exit: 0
8470
8470
  };
8471
8471
  if (!userMessage) return {
@@ -8482,7 +8482,7 @@ async function handlePost(ctx) {
8482
8482
  userMessage
8483
8483
  };
8484
8484
  if (!extra) return {
8485
- stdout: respond(id, withUserMessage),
8485
+ stdout: respond(id, withUserMessage, "PostToolUse"),
8486
8486
  exit: 0
8487
8487
  };
8488
8488
  if (id === "claude-code" || id === "codex") return {
@@ -8490,7 +8490,7 @@ async function handlePost(ctx) {
8490
8490
  exit: 0
8491
8491
  };
8492
8492
  return {
8493
- stdout: respond(id, withUserMessage) || extra,
8493
+ stdout: respond(id, withUserMessage, "PostToolUse") || extra,
8494
8494
  exit: 0
8495
8495
  };
8496
8496
  }
@@ -1,6 +1,6 @@
1
1
  import { t as evaluate } from "./evaluate-ClfbCDiY.mjs";
2
2
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
3
- import { c as readClaudeInput, v as commandToString } from "./claude-BxQwhdZB.mjs";
3
+ import { c as readClaudeInput, v as commandToString } from "./claude-51RSspJ1.mjs";
4
4
  //#region src/adapters/hermes/index.ts
5
5
  /**
6
6
  * Hermes Agent adapter (hook-mode). Nous Research's hermes-agent (2026) pipes a
@@ -31,19 +31,11 @@ declare function contextResponse(event: string, text: string): string;
31
31
  declare function systemMessage(text: string): string;
32
32
  /** An `inform` response with a user-visible notice (Python `allow_pass`/`post_pass` parity): a pure pass reuses {@link systemMessage} alone; with agent-facing `context` both channels merge in one JSON. */
33
33
  declare function informResponse(event: string, notice: string, context: string): string;
34
- /** Attach a user-visible `systemMessage` onto an already-rendered hook stdout JSON ({@link systemMessage} alone when empty/unparseable). */
34
+ /** Attach a `systemMessage`, MERGING it with any already present (`\n`-joined) instead of overwriting — several producers may each call this on the same stdout in one hook invocation. */
35
35
  declare function attachSystemMessage(stdout: string, notice: string): string;
36
- /**
37
- * Render a portable {@link Prompt} as a Claude Code hook response:
38
- * `block` → `permissionDecision: deny`, `ask` → `permissionDecision: ask`
39
- * (interactive confirm), `inform` → `additionalContext` — plus the user-visible
40
- * `systemMessage` channel when the prompt carries a `userMessage`.
41
- */
36
+ /** Render a portable {@link Prompt} as a Claude Code hook response: `block` → deny, `ask` → interactive `permissionDecision`, `inform` → `additionalContext` (+ `systemMessage` when `userMessage` is set). */
42
37
  declare function toClaudeResponse(event: string, prompt: Prompt): string;
43
- /**
44
- * Run the bundled policy over a Claude payload and return the native response
45
- * string (deny/ask/additionalContext), or null to allow.
46
- */
38
+ /** Run the bundled policy over a Claude payload and return the native response string (deny/ask/additionalContext), or null to allow. */
47
39
  declare function guard(input: ClaudeHookInput): string | null;
48
40
  /** @deprecated use {@link guard}. Kept for back-compat. */
49
41
  declare const fileSizeGuard: typeof guard;
@@ -219,8 +219,15 @@ declare function normalizeEvent(id: string, payload: Record<string, unknown>): N
219
219
  * - cline: NO user-visible channel (`cancel`/`contextModification`/
220
220
  * `errorMessage`-on-cancel only) — the notice is dropped silently (a pure
221
221
  * notice returns "" = plain allow).
222
+ * @param id - Harness id.
223
+ * @param prompt - The portable prompt to render.
224
+ * @param event - The firing hook event name for the claude-code/codex branch
225
+ * (defaults to `"PreToolUse"`, the only phase every existing caller but
226
+ * `handle-post.ts` renders) — a POST-phase caller MUST pass `"PostToolUse"`
227
+ * (or its real raw event name) so `hookEventName` matches the event that
228
+ * actually fired instead of a hardcoded, wrong `"PreToolUse"`.
222
229
  */
223
- declare function respond(id: string, prompt: Prompt): string;
230
+ declare function respond(id: string, prompt: Prompt, event?: string): string;
224
231
  //#endregion
225
232
  //#region src/runtime/mcp-key.d.ts
226
233
  /** Cached-MCP freshness (ms) from `FUSE_MCP_TTL_SEC` (default 48h). */
@@ -442,11 +449,13 @@ declare function trackAgentMemory(data: Record<string, unknown>, home?: string,
442
449
  //#region src/runtime/lifecycle/teammate-idle.d.ts
443
450
  /**
444
451
  * Handle TeammateIdle: when the teammate's session-changes file shows code was
445
- * modified, suggest sniper validation as `additionalContext`. Ports
446
- * `teammate-idle/validate-teammate-output.py`.
452
+ * modified, suggest sniper validation as plain text — TeammateIdle has no
453
+ * `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
454
+ * ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
455
+ * the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
447
456
  * @param data - The raw hook payload.
448
457
  * @param home - Home dir (defaults to `~`).
449
- * @returns The native hook stdout (possibly empty).
458
+ * @returns The plain notice text, or "" when no code file was modified.
450
459
  */
451
460
  declare function validateTeammateOutput(data: Record<string, unknown>, home?: string): string;
452
461
  //#endregion
@@ -719,8 +728,8 @@ declare function saveSecurityState(state: Record<string, unknown>, now?: number,
719
728
  //#region src/runtime/lifecycle/lessons/dispatch.d.ts
720
729
  /**
721
730
  * Route a fuse-lessons event to its handler. Returns the native stdout for
722
- * context-injecting events (SessionStart/SubagentStart/Stop) or "" for the
723
- * side-effect-only PostToolUse mark.
731
+ * context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
732
+ * MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
724
733
  * @param event - The raw hook event name.
725
734
  * @param payload - The raw hook payload.
726
735
  * @param cwd - Project root for memory injection.
@@ -1,6 +1,6 @@
1
1
  import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
2
2
  import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-BXf38Zi1.mjs";
3
- import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-BNWlWpKX.mjs";
3
+ import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-BDlk2utY.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.76",
3
+ "version": "0.1.77",
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",