@fusengine/harness 0.1.76 → 0.1.78

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-Dthtlp7Z.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";
@@ -6,7 +6,7 @@ import { B as detectProjectType$1, H as requiredArchSkill, L as detectFramework,
6
6
  import { a as sanitizeSessionId, c as sessionsDir, d as countFrameworkCodeLines, f as countLines, i as loadSessionState, l as PLUGINS_DIR, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, t as claudeHome } from "./home-state-BXf38Zi1.mjs";
7
7
  import { a as writeJsonFile, i as readJsonFile, r as hashText, t as atomicWrite } from "./json-io-DisYd2fb.mjs";
8
8
  import { r as isDocConsulted } from "./doc-helpers-CWZegVdR.mjs";
9
- import { A as detectCreationIntent, E as isExcludedSwiftPath, F as docConsultedGate, I as evaluateApex, M as POST_AUTH_GATES, N as PRE_AUTH_GATES, S as usesTailwindUtilities, T as isExcludedJsPath, _ as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, h as buildClaudeMdContext, k as capVerbosity, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, w as frameworkSolidGate, x as skillTriggerGate, y as parseField } from "./validate-DnLU4IHy.mjs";
9
+ import { A as capVerbosity, C as usesTailwindUtilities, D as isExcludedSwiftPath, E as isExcludedJsPath, I as docConsultedGate, L as evaluateApex, N as POST_AUTH_GATES, P as PRE_AUTH_GATES, S as skillTriggerGate, T as frameworkSolidGate, _ as harnessHomeSegment, b as parseField, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, h as buildClaudeMdContext, j as detectCreationIntent, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, v as scanPlugin } from "./validate-DhOX5hDK.mjs";
10
10
  import { n as findMarketplacePlugins, r as readPluginMeta, t as resolveSkillPath } from "./skill-path-BO2N0XvB.mjs";
11
11
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
12
12
  import { a as nowStamp, l as throttleMs, n as readRoots, o as readState, s as setStateField, t as addRoot } from "./registry-CymilZiZ.mjs";
@@ -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";
@@ -591,13 +591,14 @@ function claudeMdKey(prompt, ctx) {
591
591
  * is emitted on EVERY message" is thus preserved.
592
592
  * @param prompt - The raw user prompt.
593
593
  * @param cwd - Project root (for project-type detection).
594
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
594
595
  * @returns The native hook stdout (possibly empty).
595
596
  */
596
- function promptSubmitContext(prompt, cwd) {
597
- const ctx = buildClaudeMdContext(prompt, cwd);
597
+ function promptSubmitContext(prompt, cwd, id = "claude-code") {
598
+ const ctx = buildClaudeMdContext(prompt, cwd, id);
598
599
  if (!ctx) return "";
599
600
  if (!oncePerWindow(claudeMdKey(prompt, ctx), 3e3)) return "";
600
- return contextResponse("UserPromptSubmit", ctx);
601
+ return attachSystemMessage(contextResponse("UserPromptSubmit", ctx), "CLAUDE.md injected");
601
602
  }
602
603
  /**
603
604
  * PreToolUse Task context injection: render the APEX sub-agent context as a
@@ -605,10 +606,11 @@ function promptSubmitContext(prompt, cwd) {
605
606
  * Harness-produced (not owner CLAUDE.md content), so it is subject to the
606
607
  * per-fragment {@link capFragment} budget — unlike {@link promptSubmitContext}.
607
608
  * @param cwd - Fallback project root when `CLAUDE_PROJECT_DIR` is unset.
609
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
608
610
  * @returns The native hook stdout (possibly empty).
609
611
  */
610
- function taskContext(cwd) {
611
- const ctx = buildApexTaskInjection(process.env.CLAUDE_PROJECT_DIR ?? cwd);
612
+ function taskContext(cwd, id = "claude-code") {
613
+ const ctx = buildApexTaskInjection(process.env.CLAUDE_PROJECT_DIR ?? cwd, id);
612
614
  return ctx ? contextResponse("PreToolUse", capFragment("apex-task", ctx)) : "";
613
615
  }
614
616
  //#endregion
@@ -770,7 +772,7 @@ function sessionStartCore(cwd, home = homedir(), now = Date.now()) {
770
772
  const dev = devContext(cwd);
771
773
  runSessionStartCleanups(home, now);
772
774
  const ctx = [md, dev].filter(Boolean).join("\n");
773
- return ctx ? contextResponse("SessionStart", ctx) : "";
775
+ return ctx ? attachSystemMessage(contextResponse("SessionStart", ctx), "CLAUDE.md injected") : "";
774
776
  }
775
777
  //#endregion
776
778
  //#region src/runtime/lifecycle/inject-rules.ts
@@ -801,7 +803,7 @@ function readRules(rulesDir) {
801
803
  */
802
804
  function injectRules(pluginRoot, event) {
803
805
  const content = readRules(join(pluginRoot, "rules"));
804
- return content ? contextResponse(event, content) : "";
806
+ return content ? attachSystemMessage(contextResponse(event, content), "rules 00-08 injected") : "";
805
807
  }
806
808
  //#endregion
807
809
  //#region src/runtime/lifecycle/solid-detect.ts
@@ -1393,18 +1395,20 @@ function trackAgentMemory(data, home = homedir(), now = Date.now()) {
1393
1395
  //#region src/runtime/lifecycle/teammate-idle.ts
1394
1396
  /**
1395
1397
  * 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`.
1398
+ * modified, suggest sniper validation as plain text — TeammateIdle has no
1399
+ * `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
1400
+ * ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
1401
+ * the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
1398
1402
  * @param data - The raw hook payload.
1399
1403
  * @param home - Home dir (defaults to `~`).
1400
- * @returns The native hook stdout (possibly empty).
1404
+ * @returns The plain notice text, or "" when no code file was modified.
1401
1405
  */
1402
1406
  function validateTeammateOutput(data, home = homedir()) {
1403
1407
  const teammate = String(data.teammate_name ?? "unknown");
1404
1408
  const changes = loadSessionState(sanitizeSessionId(data.session_id) ?? "unknown", home).changes;
1405
1409
  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 "";
1410
+ if (count === 0) return "";
1411
+ return `Teammate '${teammate}' going idle after modifying ${count} code file(s): ${(changes?.modifiedFiles ?? []).slice(0, 5).join(", ")}. Consider running sniper validation.`;
1408
1412
  }
1409
1413
  //#endregion
1410
1414
  //#region src/runtime/lifecycle/tool-failure.ts
@@ -1440,14 +1444,17 @@ function stamp$1(now) {
1440
1444
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1441
1445
  }
1442
1446
  /**
1443
- * Handle PreCompact: back up `.claude/apex/task.json` to `backups/`, keep only
1444
- * the 5 newest, and emit a confirmation. Ports `pre-compact/save-apex-state.py`.
1447
+ * Handle PreCompact: back up the target apex `task.json` (`.claude/apex/`,
1448
+ * `.codex/apex/`, ...) to `backups/`, keep only the 5 newest, and emit a
1449
+ * confirmation. Ports `pre-compact/save-apex-state.py`.
1445
1450
  * @param cwd - Project root.
1446
1451
  * @param now - Clock (defaults to `Date.now()`).
1452
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1447
1453
  * @returns The native hook stdout (possibly empty when no task.json).
1448
1454
  */
1449
- function saveApexState(cwd, now = Date.now()) {
1450
- const apexDir = join(cwd, ".claude", "apex");
1455
+ function saveApexState(cwd, now = Date.now(), id = "claude-code") {
1456
+ const seg = harnessHomeSegment(id);
1457
+ const apexDir = join(cwd, seg, "apex");
1451
1458
  const stateFile = join(apexDir, "task.json");
1452
1459
  if (!existsSync(stateFile)) return "";
1453
1460
  const backupDir = join(apexDir, "backups");
@@ -1457,7 +1464,7 @@ function saveApexState(cwd, now = Date.now()) {
1457
1464
  for (const old of backups.slice(5)) try {
1458
1465
  rmSync(join(backupDir, old), { force: true });
1459
1466
  } catch {}
1460
- return JSON.stringify({ additionalContext: "APEX state saved before compaction. Previous task state preserved in .claude/apex/backups/" });
1467
+ return JSON.stringify({ additionalContext: `APEX state saved before compaction. Previous task state preserved in ${seg}/apex/backups/` });
1461
1468
  }
1462
1469
  //#endregion
1463
1470
  //#region src/runtime/lifecycle/session-end.ts
@@ -2210,19 +2217,12 @@ const IDLE_DEDUP_MS = 3e4;
2210
2217
  function missingDeliverables(sessionId, home) {
2211
2218
  return (loadSessionState(sessionId, home).changes?.modifiedFiles ?? []).filter((f) => typeof f === "string" && f !== "" && !existsSync(f));
2212
2219
  }
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
2220
  /**
2223
2221
  * Handle TeammateIdle: merge the existing sniper suggestion with a missing-
2224
- * deliverable warning (deduped) into one `additionalContext` response, or "" when
2225
- * neither fires.
2222
+ * deliverable warning (deduped) into one advisory `systemMessage`, or "" when
2223
+ * neither fires. TeammateIdle rejects `hookSpecificOutput` (no `additionalContext`
2224
+ * channel) — `systemMessage` is the only user-visible, non-blocking channel it
2225
+ * accepts.
2226
2226
  * @param data - The raw TeammateIdle payload (`teammate_name`, `session_id`).
2227
2227
  * @param cwd - Project root (state dir for the dedup sidecar).
2228
2228
  * @param home - Home dir (defaults to `~`).
@@ -2230,7 +2230,7 @@ function bodyOf(stdout) {
2230
2230
  * @returns The native hook stdout, or "".
2231
2231
  */
2232
2232
  function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
2233
- const sniper = bodyOf(validateTeammateOutput(data, home));
2233
+ const sniper = validateTeammateOutput(data, home);
2234
2234
  const sessionId = sanitizeSessionId(data.session_id);
2235
2235
  const teammate = String(data.teammate_name ?? data.team_name ?? "unknown");
2236
2236
  let notice = "";
@@ -2244,7 +2244,7 @@ function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
2244
2244
  const merged = [sniper, notice].filter(Boolean).join("\n\n");
2245
2245
  if (!merged) return "";
2246
2246
  notify("human");
2247
- return contextResponse("TeammateIdle", merged);
2247
+ return systemMessage(merged);
2248
2248
  }
2249
2249
  //#endregion
2250
2250
  //#region src/policy/lessons/trigger-index.ts
@@ -3148,17 +3148,10 @@ function collectViolations(files, max) {
3148
3148
  return violations;
3149
3149
  }
3150
3150
  /**
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.
3151
+ * Handle TaskCompleted (ports `task-completed/validate-task-solid.py` + the
3152
+ * receipt gate); also reused verbatim for Codex's `Stop` via {@link stopCore}
3153
+ * (Codex never emits `TaskCompleted`) `event` stamps the real hook name.
3158
3154
  * @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
3155
  * @param event - The hook event name to stamp on the SOLID-violation response (default `TaskCompleted`).
3163
3156
  * @returns The native hook stdout, or `""` when the session is clean.
3164
3157
  */
@@ -3171,7 +3164,8 @@ function validateTaskSolid(payload, home = homedir(), now = Date.now(), stateDir
3171
3164
  const violations = collectViolations(files, max);
3172
3165
  if (violations.length === 0) return receiptGate(sid, files, now, stateDir) ?? "";
3173
3166
  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("; "));
3167
+ const msg = `SOLID VIOLATION in task '${String(payload.task_subject ?? "")}' (${taskId}): ${violations.length} file(s) exceed ${max} lines: ` + violations.slice(0, 5).join("; ");
3168
+ return event === "TaskCompleted" ? systemMessage(msg) : contextResponse(event, msg);
3175
3169
  }
3176
3170
  //#endregion
3177
3171
  //#region src/runtime/lifecycle/cartographer/fs-util.ts
@@ -4003,13 +3997,13 @@ function injectMemory(cwd, event, now) {
4003
3997
  if (!content) return "";
4004
3998
  const { content: curated, archive, report } = curateLessons(content, now, root);
4005
3999
  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);
4000
+ 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");
4001
+ return report ? attachSystemMessage(withNotice, `LESSON.md curation:\n${report}`) : withNotice;
4008
4002
  }
4009
4003
  /**
4010
4004
  * 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.
4005
+ * context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
4006
+ * MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
4013
4007
  * @param event - The raw hook event name.
4014
4008
  * @param payload - The raw hook payload.
4015
4009
  * @param cwd - Project root for memory injection.
@@ -4019,7 +4013,8 @@ function injectMemory(cwd, event, now) {
4019
4013
  function dispatchLessons(event, payload, cwd, now) {
4020
4014
  switch (event) {
4021
4015
  case "SessionStart":
4022
- case "SubagentStart": return injectMemory(cwd, event, now);
4016
+ case "SubagentStart":
4017
+ case "UserPromptSubmit": return injectMemory(cwd, event, now);
4023
4018
  case "Stop": return remindWrite(payload, now);
4024
4019
  case "PostToolUse":
4025
4020
  markWrite(payload, now);
@@ -4084,18 +4079,25 @@ function cartographerContext() {
4084
4079
  return `\n### 7. Cartographer Maps\nNavigate branches (index.md) -> leaves link to real files:\n- Plugin skills: ${pluginsMap}\n- Project files: .cartographer/project/index.md`;
4085
4080
  }
4086
4081
  /**
4087
- * Build the APEX sub-agent injection for SubagentStart, or "" when the project
4088
- * has no `.claude/apex/` dir. Reads AGENTS.md (first 4KB) + task.json.
4082
+ * Build the APEX sub-agent injection for SubagentStart, or "" when the project has no target apex dir (`.claude/apex/`, `.codex/apex/`, ...). Reads AGENTS.md (first 4KB) + task.json.
4089
4083
  * @param cwd - Fallback project root when `CLAUDE_PROJECT_DIR` is unset.
4090
4084
  * @param home - Home dir (unused placeholder; kept for symmetry/testing).
4085
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
4091
4086
  * @returns The native hook stdout (possibly empty).
4092
4087
  */
4093
- async function injectApexSubagentContext(cwd, home = homedir()) {
4094
- const apexDir = join(process.env.CLAUDE_PROJECT_DIR ?? cwd, ".claude", "apex");
4088
+ async function injectApexSubagentContext(cwd, home = homedir(), id = "claude-code") {
4089
+ const projectRoot = process.env.CLAUDE_PROJECT_DIR ?? cwd;
4090
+ const seg = harnessHomeSegment(id);
4091
+ const apexDir = join(projectRoot, seg, "apex");
4095
4092
  if (!existsSync(apexDir)) return "";
4096
4093
  const agentsPath = join(apexDir, "AGENTS.md");
4097
4094
  const agents = existsSync(agentsPath) ? readText(agentsPath).slice(0, 4e3) : "";
4098
4095
  const taskData = await readJsonFile(join(apexDir, "task.json"));
4096
+ const completed = taskData ? completedTasks(taskData.tasks) : "none";
4097
+ const pending = taskData ? pendingTasks(taskData.tasks) : "none";
4098
+ const isCodex = id === "codex";
4099
+ const beforeStart = isCodex ? "Use update_plan → mark the active step in_progress before starting" : "Use TaskUpdate(taskId, status: in_progress) before starting";
4100
+ const whenDone = isCodex ? "update_plan → mark the step completed when done" : "TaskUpdate(taskId, status: completed) triggers auto-commit";
4099
4101
  return contextResponse("SubagentStart", capFragment("apex-subagent", `## APEX Sub-Agent Instructions
4100
4102
 
4101
4103
  You are a sub-agent in APEX workflow. Follow these rules:
@@ -4104,24 +4106,24 @@ You are a sub-agent in APEX workflow. Follow these rules:
4104
4106
  ${agents}
4105
4107
 
4106
4108
  ### 2. Task Context
4107
- - Last completed: ${taskData ? completedTasks(taskData.tasks) : "none"}
4108
- - Pending: ${taskData ? pendingTasks(taskData.tasks) : "none"}
4109
+ - Last completed: ${completed}
4110
+ - Pending: ${pending}
4109
4111
 
4110
4112
  ### 3. Before Starting Work
4111
- - Use TaskUpdate(taskId, status: in_progress) before starting
4113
+ - ${beforeStart}
4112
4114
 
4113
4115
  ### 4. SOLID Rules
4114
4116
  - Files < ${resolveMaxLines()} lines | Interfaces in src/interfaces/ | JSDoc/PHPDoc required
4115
4117
 
4116
4118
  ### 5. Research Before Code
4117
- - Use Context7/Exa for docs | Write notes to .claude/apex/docs/
4119
+ - Use Context7/Exa for docs | Write notes to ${seg}/apex/docs/
4118
4120
 
4119
4121
  ### 6. Before Done (NEVER skip)
4120
4122
  - eLicit: self-review with a NAMED elicitation technique; fix findings first
4121
4123
  - Verify: run/functional-check your changes (references⇔declarations)
4122
4124
 
4123
4125
  ### 7. When Done
4124
- - TaskUpdate(taskId, status: completed) triggers auto-commit${cartographerContext()}`));
4126
+ - ${whenDone}${cartographerContext()}`));
4125
4127
  }
4126
4128
  /** 16-char hex SHA-256 of `text` (project hash / doc topic key). */
4127
4129
  function hashText16(text) {
@@ -5057,18 +5059,22 @@ async function onComplete(taskFile, taskId, projectRoot) {
5057
5059
  return contextResponse("PostToolUse", `Task #${taskId} completed: ${(await readJsonFile(taskFile))?.tasks[taskId]?.subject ?? "Task"}\n\nChanges detected. MANDATORY: Run /fuse-commit-pro:commit to commit with smart detection.`);
5058
5060
  }
5059
5061
  /**
5060
- * PostToolUse TaskCreate/TaskUpdate handler.
5062
+ * PostToolUse TaskCreate/TaskUpdate handler. Claude-only by design: Codex never
5063
+ * emits these native task tools, so the `toolName` matcher below intentionally
5064
+ * stays as-is — only the target apex-dir segment is harness-aware.
5061
5065
  * @param payload - The raw hook payload (`tool_name`, `tool_input`, `tool_response`).
5062
5066
  * @param cwd - Fallback project root (uses `CLAUDE_PROJECT_DIR` first).
5067
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5063
5068
  * @returns The native hook stdout (possibly empty).
5064
5069
  */
5065
- async function syncTaskTracking(payload, cwd) {
5070
+ async function syncTaskTracking(payload, cwd, id = "claude-code") {
5066
5071
  const toolName = String(payload.tool_name ?? "");
5067
5072
  if (toolName !== "TaskCreate" && toolName !== "TaskUpdate") return "";
5068
5073
  const projectRoot = process.env.CLAUDE_PROJECT_DIR ?? cwd;
5069
- const taskFile = join(projectRoot, ".claude", "apex", "task.json");
5074
+ const seg = harnessHomeSegment(id);
5075
+ const taskFile = join(projectRoot, seg, "apex", "task.json");
5070
5076
  if (!existsSync(taskFile)) return "";
5071
- const unlock = await acquireLock(join(projectRoot, ".claude", "apex", ".task.lock"), 1e4);
5077
+ const unlock = await acquireLock(join(projectRoot, seg, "apex", ".task.lock"), 1e4);
5072
5078
  if (!unlock) return "";
5073
5079
  try {
5074
5080
  const ti = payload.tool_input ?? {};
@@ -5277,9 +5283,9 @@ async function typeSpecificCache(agent, cwd, now, home) {
5277
5283
  * entries (APEX context + lessons) fire for EVERY sub-agent, then the type-specific
5278
5284
  * cache is concatenated on top — sniper too, hence no early return.
5279
5285
  */
5280
- async function onSubagentStart(payload, cwd, now, home) {
5286
+ async function onSubagentStart(payload, cwd, now, home, id) {
5281
5287
  const agent = agentTypeOf(payload);
5282
- return combineContext(await injectApexSubagentContext(cwd, home), await injectLessonsCache(cwd, home, now), await typeSpecificCache(agent, cwd, now, home));
5288
+ return combineContext(await injectApexSubagentContext(cwd, home, id), await injectLessonsCache(cwd, home, now), await typeSpecificCache(agent, cwd, now, home));
5283
5289
  }
5284
5290
  /** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. */
5285
5291
  async function onSubagentStop(payload, cwd, home) {
@@ -5295,10 +5301,10 @@ async function onSubagentStop(payload, cwd, home) {
5295
5301
  /**
5296
5302
  * Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
5297
5303
  * `null` when unhandled (caller falls through to the default pipeline).
5298
- * @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation).
5304
+ * @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation); `id` selects the harness target (defaults to "claude-code").
5299
5305
  */
5300
- async function dispatchAipilot(event, payload, cwd, now, home = homedir()) {
5301
- if (event === "SubagentStart") return onSubagentStart(payload, cwd, now, home);
5306
+ async function dispatchAipilot(event, payload, cwd, now, home = homedir(), id = "claude-code") {
5307
+ if (event === "SubagentStart") return onSubagentStart(payload, cwd, now, home, id);
5302
5308
  if (event === "SubagentStop") return onSubagentStop(payload, cwd, home);
5303
5309
  if (event === "SessionEnd" || event === "Stop") {
5304
5310
  await cacheAnalyticsSave(home, now);
@@ -5307,9 +5313,9 @@ async function dispatchAipilot(event, payload, cwd, now, home = homedir()) {
5307
5313
  if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home);
5308
5314
  return null;
5309
5315
  }
5310
- /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
5311
- async function aipilotPostToolUse(payload, cwd) {
5312
- return checkSolidCompliance(payload) || await syncTaskTracking(payload, cwd);
5316
+ /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope; `id` selects the harness target (defaults to "claude-code"). */
5317
+ async function aipilotPostToolUse(payload, cwd, id = "claude-code") {
5318
+ return checkSolidCompliance(payload) || await syncTaskTracking(payload, cwd, id);
5313
5319
  }
5314
5320
  //#endregion
5315
5321
  //#region src/runtime/lifecycle/dispatch.ts
@@ -5332,7 +5338,10 @@ function sessionStart(input) {
5332
5338
  function dispatchLifecycle(input) {
5333
5339
  switch (input.event) {
5334
5340
  case "SessionStart": return sessionStart(input);
5335
- case "UserPromptSubmit": return input.scope === "rules" ? injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event) : null;
5341
+ case "UserPromptSubmit":
5342
+ if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
5343
+ if (input.scope === "lessons") return dispatchLessons("UserPromptSubmit", input.payload, input.cwd, input.now);
5344
+ return null;
5336
5345
  case "SubagentStart":
5337
5346
  if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
5338
5347
  if (input.scope === "aipilot") return "";
@@ -5348,7 +5357,7 @@ function dispatchLifecycle(input) {
5348
5357
  case "TeammateIdle": return teammateIdleContext(input.payload, input.cwd, void 0, input.now);
5349
5358
  case "PostToolUseFailure": return failureLessonContext(input.payload, input.cwd, void 0, input.now);
5350
5359
  case "PostCompact": return input.scope === "core" ? postCompactContext(input.payload, input.cwd, import.meta.url, input.now) : "";
5351
- case "PreCompact": return saveApexState(input.cwd, input.now);
5360
+ case "PreCompact": return saveApexState(input.cwd, input.now, input.id ?? "claude-code");
5352
5361
  case "SessionEnd":
5353
5362
  if (input.scope !== "aipilot") cleanupSession(void 0, input.now);
5354
5363
  return "";
@@ -5890,19 +5899,22 @@ function docTypeOf(filePath) {
5890
5899
  }
5891
5900
  /**
5892
5901
  * Auto-document a Read of a SKILL.md/README/CLAUDE.md/docs/references file
5893
- * into `.claude/apex/docs/task-<current>-<framework>.md` (skipped when the
5894
- * file is already logged, or no project root is found from `filePath`).
5902
+ * into the target apex docs dir (`.claude/apex/docs/`, `.codex/apex/docs/`,
5903
+ * ...) as `task-<current>-<framework>.md` (skipped when the file is already
5904
+ * logged, or no project root is found from `filePath`).
5895
5905
  * @param filePath - The path passed to the Read tool.
5896
5906
  * @param now - Clock (defaults to `Date.now()`).
5907
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5897
5908
  * @returns The native `systemMessage` stdout, or "" when nothing was logged.
5898
5909
  */
5899
- async function autoDocumentRead(filePath, now = Date.now()) {
5910
+ async function autoDocumentRead(filePath, now = Date.now(), id = "claude-code") {
5900
5911
  if (!filePath || !DOC_PATTERNS.some((p) => p.test(filePath))) return "";
5901
5912
  const root = projectRootOrNull(dirname(filePath));
5902
5913
  if (!root) return "";
5914
+ const seg = harnessHomeSegment(id);
5903
5915
  const framework = detectProjectType$1(root);
5904
- const current = (await readJsonFile(join(root, ".claude", "apex", "task.json")))?.current_task ?? "1";
5905
- const docDir = join(root, ".claude", "apex", "docs");
5916
+ const current = (await readJsonFile(join(root, seg, "apex", "task.json")))?.current_task ?? "1";
5917
+ const docDir = join(root, seg, "apex", "docs");
5906
5918
  const docFile = join(docDir, `task-${current}-${framework}.md`);
5907
5919
  const ts = new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z");
5908
5920
  const fname = basename(filePath);
@@ -5941,15 +5953,17 @@ function additionalContextOf(stdout) {
5941
5953
  * @param cwd - Project root.
5942
5954
  * @param scope - The invoking plugin scope (defaults to `core`).
5943
5955
  * @param now - Clock.
5956
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5944
5957
  * @returns The native stdout, or `null` when unhandled.
5945
5958
  */
5946
- function lifecycleStdout(payload, cwd, scope, now) {
5959
+ function lifecycleStdout(payload, cwd, scope, now, id = "claude-code") {
5947
5960
  return dispatchLifecycle({
5948
5961
  event: rawEvent(payload),
5949
5962
  payload,
5950
5963
  cwd,
5951
5964
  scope,
5952
- now
5965
+ now,
5966
+ id
5953
5967
  });
5954
5968
  }
5955
5969
  /**
@@ -5962,11 +5976,12 @@ function lifecycleStdout(payload, cwd, scope, now) {
5962
5976
  * @param scope - The invoking plugin scope.
5963
5977
  * @param event - The normalized event.
5964
5978
  * @param now - Clock.
5979
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5965
5980
  * @returns The extra stdout (possibly empty).
5966
5981
  */
5967
- async function postEditContext(scope, event, now) {
5982
+ async function postEditContext(scope, event, now, id = "claude-code") {
5968
5983
  if (scope !== "core" || !event.filePath) return "";
5969
- if (event.tool === "Read") return autoDocumentRead(event.filePath, now);
5984
+ if (event.tool === "Read") return autoDocumentRead(event.filePath, now, id);
5970
5985
  if (event.tool !== "Write" && event.tool !== "Edit") return "";
5971
5986
  const sniper = trackSessionChanges(event.sessionId, event.filePath, void 0, now);
5972
5987
  const lint = postEditTypescript(event.filePath);
@@ -6026,7 +6041,7 @@ const EXEMPT_PATTERNS = [
6026
6041
  /\.claude-plugin\//,
6027
6042
  /CHANGELOG\.md$/,
6028
6043
  /marketplace\.json$/,
6029
- /\/\.claude\/(apex|memory|logs|fusengine-cache)\//,
6044
+ /\/\.(claude|codex)\/(apex|memory|logs|fusengine-cache)\//,
6030
6045
  /\/\.fuse-harness\//
6031
6046
  ];
6032
6047
  /**
@@ -7192,18 +7207,25 @@ async function recordActivity(file, activity) {
7192
7207
  * - cline: NO user-visible channel (`cancel`/`contextModification`/
7193
7208
  * `errorMessage`-on-cancel only) — the notice is dropped silently (a pure
7194
7209
  * notice returns "" = plain allow).
7195
- */
7196
- function respond(id, prompt) {
7210
+ * @param id - Harness id.
7211
+ * @param prompt - The portable prompt to render.
7212
+ * @param event - The firing hook event name for the claude-code/codex branch
7213
+ * (defaults to `"PreToolUse"`, the only phase every existing caller but
7214
+ * `handle-post.ts` renders) — a POST-phase caller MUST pass `"PostToolUse"`
7215
+ * (or its real raw event name) so `hookEventName` matches the event that
7216
+ * actually fired instead of a hardcoded, wrong `"PreToolUse"`.
7217
+ */
7218
+ function respond(id, prompt, event = "PreToolUse") {
7197
7219
  const message = formatPrompt(prompt);
7198
7220
  const { kind, userMessage, reason } = prompt;
7199
7221
  switch (id) {
7200
7222
  case "claude-code":
7201
7223
  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}`);
7224
+ if (kind === "block") return denyResponse(event, message);
7225
+ if (kind === "inform") return userMessage ? informResponse(event, userMessage, reason ? message : "") : contextResponse(event, message);
7226
+ if (id === "codex") return denyResponse(event, `[downgraded from ask — Codex has no interactive approval]\n${message}`);
7205
7227
  return JSON.stringify({ hookSpecificOutput: {
7206
- hookEventName: "PreToolUse",
7228
+ hookEventName: event,
7207
7229
  permissionDecision: "ask",
7208
7230
  permissionDecisionReason: message
7209
7231
  } });
@@ -7873,7 +7895,7 @@ async function handlePre(ctx) {
7873
7895
  exit: 0
7874
7896
  };
7875
7897
  if (event.tool === "Task") {
7876
- const taskCtx = taskContext(opts.cwd);
7898
+ const taskCtx = taskContext(opts.cwd, id);
7877
7899
  if (taskCtx) return {
7878
7900
  stdout: taskCtx,
7879
7901
  exit: 0
@@ -8440,7 +8462,7 @@ async function handlePost(ctx) {
8440
8462
  };
8441
8463
  }
8442
8464
  if (opts.scope === "aipilot" && (event.tool === "TaskCreate" || event.tool === "TaskUpdate" || event.tool === "Write" || event.tool === "Edit")) {
8443
- const out = await aipilotPostToolUse(payload, opts.cwd);
8465
+ const out = await aipilotPostToolUse(payload, opts.cwd, id);
8444
8466
  if (out) return {
8445
8467
  stdout: out,
8446
8468
  exit: 0
@@ -8448,7 +8470,7 @@ async function handlePost(ctx) {
8448
8470
  }
8449
8471
  let extra = "";
8450
8472
  for (const f of files) {
8451
- extra = await postEditContext(opts.scope ?? "core", f, opts.now);
8473
+ extra = await postEditContext(opts.scope ?? "core", f, opts.now, id);
8452
8474
  if (extra) break;
8453
8475
  }
8454
8476
  const notice = designPassNotice({
@@ -8465,7 +8487,7 @@ async function handlePost(ctx) {
8465
8487
  stdout: respond(id, userMessage ? {
8466
8488
  ...designWarn,
8467
8489
  userMessage
8468
- } : designWarn),
8490
+ } : designWarn, "PostToolUse"),
8469
8491
  exit: 0
8470
8492
  };
8471
8493
  if (!userMessage) return {
@@ -8482,7 +8504,7 @@ async function handlePost(ctx) {
8482
8504
  userMessage
8483
8505
  };
8484
8506
  if (!extra) return {
8485
- stdout: respond(id, withUserMessage),
8507
+ stdout: respond(id, withUserMessage, "PostToolUse"),
8486
8508
  exit: 0
8487
8509
  };
8488
8510
  if (id === "claude-code" || id === "codex") return {
@@ -8490,7 +8512,7 @@ async function handlePost(ctx) {
8490
8512
  exit: 0
8491
8513
  };
8492
8514
  return {
8493
- stdout: respond(id, withUserMessage) || extra,
8515
+ stdout: respond(id, withUserMessage, "PostToolUse") || extra,
8494
8516
  exit: 0
8495
8517
  };
8496
8518
  }
@@ -8508,10 +8530,11 @@ async function handlePost(ctx) {
8508
8530
  * @param payload - The raw hook payload.
8509
8531
  * @param cwd - Project root.
8510
8532
  * @param now - Clock.
8533
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
8511
8534
  * @returns The native stdout when intercepted, or `null` to fall through.
8512
8535
  */
8513
- async function asyncScopeStdout(scope, event, payload, cwd, now) {
8514
- if (scope === "aipilot") return dispatchAipilot(event, payload, cwd, now);
8536
+ async function asyncScopeStdout(scope, event, payload, cwd, now, id = "claude-code") {
8537
+ if (scope === "aipilot") return dispatchAipilot(event, payload, cwd, now, void 0, id);
8515
8538
  if (scope === "memory") return dispatchMemory(event, payload, cwd, now);
8516
8539
  return null;
8517
8540
  }
@@ -8925,12 +8948,12 @@ async function handleHook(id, payload, opts) {
8925
8948
  exit: 0
8926
8949
  };
8927
8950
  if (id === "codex" && rawEventName(payload) === "SessionStart") resyncCodexAgents();
8928
- const asyncOut = await asyncScopeStdout(opts.scope, rawEventName(payload), payload, opts.cwd, opts.now);
8951
+ const asyncOut = await asyncScopeStdout(opts.scope, rawEventName(payload), payload, opts.cwd, opts.now, id);
8929
8952
  if (asyncOut !== null) return {
8930
8953
  stdout: asyncOut,
8931
8954
  exit: 0
8932
8955
  };
8933
- const life = lifecycleStdout(payload, opts.cwd, opts.scope ?? "core", opts.now);
8956
+ const life = lifecycleStdout(payload, opts.cwd, opts.scope ?? "core", opts.now, id);
8934
8957
  if (life !== null) return {
8935
8958
  stdout: id === "claude-code" ? attachBudgetRecap(life, rawEventName(payload), event.sessionId, opts.cwd, opts.now) : life,
8936
8959
  exit: 0
@@ -8939,7 +8962,7 @@ async function handleHook(id, payload, opts) {
8939
8962
  if (userPrompt !== void 0) {
8940
8963
  await saveTrack(file, recordBrainstormRequired(await loadTrack(file), detectCreationIntent(userPrompt)));
8941
8964
  return {
8942
- stdout: promptSubmitContext(userPrompt, opts.cwd),
8965
+ stdout: promptSubmitContext(userPrompt, opts.cwd, id),
8943
8966
  exit: 0
8944
8967
  };
8945
8968
  }
@@ -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
@@ -412,9 +412,7 @@ declare function usesTailwindUtilities(filePath: string, content: string): boole
412
412
  */
413
413
  declare function skillTriggerGate(framework: string, content: string, refsRead: readonly string[], forcedSkill?: string | null, cwd?: string, filePath?: string): Prompt | null;
414
414
  //#endregion
415
- //#region src/policy/claude-md-context.d.ts
416
- /** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
417
- declare const DEV_VERBS: RegExp;
415
+ //#region src/policy/detect-claude-md-project-type.d.ts
418
416
  /**
419
417
  * Detect the project type from the cwd, reproducing the legacy Python logic:
420
418
  * package.json containing "next" → nextjs, else "react" → react; else
@@ -424,22 +422,29 @@ declare const DEV_VERBS: RegExp;
424
422
  * @returns The detected project type label.
425
423
  */
426
424
  declare function detectClaudeMdProjectType(cwd: string): ProjectType;
425
+ //#endregion
426
+ //#region src/policy/claude-md-context.d.ts
427
+ /** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
428
+ declare const DEV_VERBS: RegExp;
427
429
  /**
428
430
  * Build the APEX instruction preamble for a development task.
429
431
  * @param projectType - Detected project type label.
430
432
  * @param maxLines - SOLID per-file line ceiling.
433
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
431
434
  * @returns The APEX instruction text.
432
435
  */
433
- declare function buildApexInstruction(projectType: ProjectType, maxLines: number): string;
436
+ declare function buildApexInstruction(projectType: ProjectType, maxLines: number, id?: string): string;
434
437
  /**
435
- * Build the UserPromptSubmit injection text: read `~/.claude/CLAUDE.md` and,
438
+ * Build the UserPromptSubmit injection text: read the target's root
439
+ * instructions doc (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, ...) and,
436
440
  * when the prompt matches a dev verb, prepend the APEX instruction. Returns
437
- * `null` when CLAUDE.md is absent/unreadable (the hook then emits nothing).
441
+ * `null` when the doc is absent/unreadable (the hook then emits nothing).
438
442
  * @param prompt - The raw user prompt.
439
443
  * @param cwd - Project root (for project-type detection).
444
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
440
445
  * @returns The injection text, or `null` to emit nothing.
441
446
  */
442
- declare function buildClaudeMdContext(prompt: string, cwd: string): string | null;
447
+ declare function buildClaudeMdContext(prompt: string, cwd: string, id?: string): string | null;
443
448
  //#endregion
444
449
  //#region src/policy/apex-task-context.d.ts
445
450
  /** Parsed task state injected into a Task sub-agent prompt. */
@@ -464,16 +469,19 @@ declare function loadApexTaskState(taskFile: string): ApexTaskState;
464
469
  * Build the APEX context string injected into a Task sub-agent prompt.
465
470
  * @param state - The parsed task state.
466
471
  * @param maxLines - SOLID per-file line ceiling.
472
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
467
473
  * @returns The injection text.
468
474
  */
469
- declare function buildApexTaskContext(state: ApexTaskState, maxLines: number): string;
475
+ declare function buildApexTaskContext(state: ApexTaskState, maxLines: number, id?: string): string;
470
476
  /**
471
- * Build the PreToolUse Task injection, gated on the existence of the project's
472
- * `.claude/apex/` directory. Returns `null` when APEX is not active (no dir).
477
+ * Build the PreToolUse Task injection, gated on the existence of the
478
+ * project's target apex dir (`.claude/apex/`, `.codex/apex/`, ...). Returns
479
+ * `null` when APEX is not active (no dir).
473
480
  * @param projectRoot - `CLAUDE_PROJECT_DIR` or cwd.
481
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
474
482
  * @returns The injection text, or `null` to emit nothing.
475
483
  */
476
- declare function buildApexTaskInjection(projectRoot: string): string | null;
484
+ declare function buildApexTaskInjection(projectRoot: string, id?: string): string | null;
477
485
  //#endregion
478
486
  //#region src/policy/cartographer/indicators.d.ts
479
487
  /**
@@ -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;
package/dist/index.d.mts CHANGED
@@ -5,7 +5,7 @@ import { a as HarnessInfo, i as HarnessId, n as detectMode, o as HarnessMode, r
5
5
  import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "./doc-helpers-CEKzGg2u.mjs";
6
6
  import { t as incrementTrivialEditCounter } from "./index-BOBXQ91y.mjs";
7
7
  import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./index-BEMumjOw.mjs";
8
- import { $ as protectedPathGuard, A as FAIL_CLOSED, B as RUST_DECL_RE, C as usesTailwindUtilities, Ct as DEV_KEYWORDS, D as MAX_TOKENS, Dt as detectProjectType, E as MAX_EXA_RESULTS, Et as detectModularArchitecture, F as installGuard, G as ASK_WRITERS, H as TS_DECL_RE, I as GO_DECL_RE, J as FILE_REDIRECT, K as CODE_MUTATORS, L as JAVA_DECL_RE, M as clearUserGuards, N as registerGuard, O as capVerbosity, Ot as isApexCommand, P as runGuards, Q as PROTECTED_GIT_RE, R as PHP_DECL_RE, S as skillTriggerGate, St as detectFramework, T as frameworkSolidGate, Tt as ProjectType, U as interfaceSeparationGuard, V as SWIFT_PROTO_RE, W as bashWriteGuard, X as SESSION_STATE_FRAGMENT, Y as SAFE_PREFIXES, Z as PROTECTED_FRAGMENTS, _ as DEV_VERBS, _t as PLUGINS_DIR, a as firstHeading, at as GuardContext, b as detectClaudeMdProjectType, bt as countLines, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as PROJECT_INSTALL, et as ASK_PATTERNS, f as PROJECT_INDICATORS, ft as RALPH_SAFE, g as loadApexTaskState, gt as FileSizeVerdict, h as buildApexTaskInjection, ht as matchPatterns, i as firstComment, it as Guard, j as GUARDS, k as detectCreationIntent, kt as requiredArchSkill, l as parseBodyDesc, lt as GIT_ASK, m as buildApexTaskContext, mt as isRalphMode, n as missingSeoElements, nt as LabeledPattern, o as TreeEntry, ot as evaluate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as CODE_REDIRECT, r as descFromText, rt as securityGuard, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as CRITICAL_PATTERNS, u as parseField, ut as GIT_BLOCKED, v as buildApexInstruction, vt as SOLID_REF, w as SKILL_TRIGGERS, wt as ModularArchitecture, x as detectRequiredSkills, xt as evaluateFileSize, y as buildClaudeMdContext, yt as countFrameworkCodeLines, z as PY_MODEL_RE } from "./index-DJw1OGwE.mjs";
8
+ import { $ as protectedPathGuard, A as FAIL_CLOSED, B as RUST_DECL_RE, C as usesTailwindUtilities, Ct as DEV_KEYWORDS, D as MAX_TOKENS, Dt as detectProjectType, E as MAX_EXA_RESULTS, Et as detectModularArchitecture, F as installGuard, G as ASK_WRITERS, H as TS_DECL_RE, I as GO_DECL_RE, J as FILE_REDIRECT, K as CODE_MUTATORS, L as JAVA_DECL_RE, M as clearUserGuards, N as registerGuard, O as capVerbosity, Ot as isApexCommand, P as runGuards, Q as PROTECTED_GIT_RE, R as PHP_DECL_RE, S as skillTriggerGate, St as detectFramework, T as frameworkSolidGate, Tt as ProjectType, U as interfaceSeparationGuard, V as SWIFT_PROTO_RE, W as bashWriteGuard, X as SESSION_STATE_FRAGMENT, Y as SAFE_PREFIXES, Z as PROTECTED_FRAGMENTS, _ as DEV_VERBS, _t as PLUGINS_DIR, a as firstHeading, at as GuardContext, b as detectClaudeMdProjectType, bt as countLines, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as PROJECT_INSTALL, et as ASK_PATTERNS, f as PROJECT_INDICATORS, ft as RALPH_SAFE, g as loadApexTaskState, gt as FileSizeVerdict, h as buildApexTaskInjection, ht as matchPatterns, i as firstComment, it as Guard, j as GUARDS, k as detectCreationIntent, kt as requiredArchSkill, l as parseBodyDesc, lt as GIT_ASK, m as buildApexTaskContext, mt as isRalphMode, n as missingSeoElements, nt as LabeledPattern, o as TreeEntry, ot as evaluate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as CODE_REDIRECT, r as descFromText, rt as securityGuard, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as CRITICAL_PATTERNS, u as parseField, ut as GIT_BLOCKED, v as buildApexInstruction, vt as SOLID_REF, w as SKILL_TRIGGERS, wt as ModularArchitecture, x as detectRequiredSkills, xt as evaluateFileSize, y as buildClaudeMdContext, yt as countFrameworkCodeLines, z as PY_MODEL_RE } from "./index-BIGVNQB8.mjs";
9
9
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "./types-CY5qT2X1.mjs";
10
10
  import { a as PRE_AUTH_GATES, c as evaluateApex, i as POST_AUTH_GATES, l as freshnessGate, n as ApexContext, o as brainstormGate, r as ApexGate, s as docConsultedGate, t as APEX_GATES, u as solidReadGate } from "./apex-Wdi1nq_w.mjs";
11
11
  import { a as ReminderState, c as readState, d as throttleMs, i as registryFile, l as setStateField, n as addRoot, o as lessonsFileFor, r as readRoots, s as nowStamp, t as ensureMemoryGitignore, u as stateFileFor } from "./index-DLYhervv.mjs";
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import { A as GIT_ASK, B as detectProjectType, C as SESSION_STATE_FRAGMENT, D as
8
8
  import { d as countFrameworkCodeLines, f as countLines, l as PLUGINS_DIR, p as evaluateFileSize, u as SOLID_REF } from "./home-state-BXf38Zi1.mjs";
9
9
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
10
10
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-PKVNBHge.mjs";
11
- import { A as detectCreationIntent, C as SKILL_TRIGGERS, D as MAX_EXA_RESULTS, F as docConsultedGate, I as evaluateApex, L as freshnessGate, M as POST_AUTH_GATES, N as PRE_AUTH_GATES, O as MAX_TOKENS, P as brainstormGate, R as solidReadGate, S as usesTailwindUtilities, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as APEX_GATES, k as capVerbosity, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, w as frameworkSolidGate, x as skillTriggerGate, y as parseField } from "./validate-DnLU4IHy.mjs";
11
+ import { A as capVerbosity, C as usesTailwindUtilities, F as brainstormGate, I as docConsultedGate, L as evaluateApex, M as APEX_GATES, N as POST_AUTH_GATES, O as MAX_EXA_RESULTS, P as PRE_AUTH_GATES, R as freshnessGate, S as skillTriggerGate, T as frameworkSolidGate, a as firstHeading, b as parseField, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as detectCreationIntent, k as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as SKILL_TRIGGERS, x as detectRequiredSkills, y as parseBodyDesc, z as solidReadGate } from "./validate-DhOX5hDK.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 "./registry-CymilZiZ.mjs";
14
14
  import { a as cacheLookupSubstring, c as cacheStore, d as loadIndex, f as summarizeIndex, h as queryHash, i as cacheLookupMeta, l as mcpCacheKey, m as jaccardSimilar, n as webfetchCacheWrite, o as cacheLookupSubstringMeta, p as compactMarkdown, r as cacheLookup, s as cachePath, t as mcpCacheWrite, u as extractText } from "./mcp-store-BkBDmuxN.mjs";
@@ -1,3 +1,3 @@
1
- import { $ as protectedPathGuard, A as FAIL_CLOSED, B as RUST_DECL_RE, C as usesTailwindUtilities, Ct as DEV_KEYWORDS, D as MAX_TOKENS, Dt as detectProjectType, E as MAX_EXA_RESULTS, Et as detectModularArchitecture, F as installGuard, G as ASK_WRITERS, H as TS_DECL_RE, I as GO_DECL_RE, J as FILE_REDIRECT, K as CODE_MUTATORS, L as JAVA_DECL_RE, M as clearUserGuards, N as registerGuard, O as capVerbosity, Ot as isApexCommand, P as runGuards, Q as PROTECTED_GIT_RE, R as PHP_DECL_RE, S as skillTriggerGate, St as detectFramework, T as frameworkSolidGate, Tt as ProjectType, U as interfaceSeparationGuard, V as SWIFT_PROTO_RE, W as bashWriteGuard, X as SESSION_STATE_FRAGMENT, Y as SAFE_PREFIXES, Z as PROTECTED_FRAGMENTS, _ as DEV_VERBS, _t as PLUGINS_DIR, a as firstHeading, at as GuardContext, b as detectClaudeMdProjectType, bt as countLines, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as PROJECT_INSTALL, et as ASK_PATTERNS, f as PROJECT_INDICATORS, ft as RALPH_SAFE, g as loadApexTaskState, gt as FileSizeVerdict, h as buildApexTaskInjection, ht as matchPatterns, i as firstComment, it as Guard, j as GUARDS, k as detectCreationIntent, kt as requiredArchSkill, l as parseBodyDesc, lt as GIT_ASK, m as buildApexTaskContext, mt as isRalphMode, n as missingSeoElements, nt as LabeledPattern, o as TreeEntry, ot as evaluate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as CODE_REDIRECT, r as descFromText, rt as securityGuard, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as CRITICAL_PATTERNS, u as parseField, ut as GIT_BLOCKED, v as buildApexInstruction, vt as SOLID_REF, w as SKILL_TRIGGERS, wt as ModularArchitecture, x as detectRequiredSkills, xt as evaluateFileSize, y as buildClaudeMdContext, yt as countFrameworkCodeLines, z as PY_MODEL_RE } from "../index-DJw1OGwE.mjs";
1
+ import { $ as protectedPathGuard, A as FAIL_CLOSED, B as RUST_DECL_RE, C as usesTailwindUtilities, Ct as DEV_KEYWORDS, D as MAX_TOKENS, Dt as detectProjectType, E as MAX_EXA_RESULTS, Et as detectModularArchitecture, F as installGuard, G as ASK_WRITERS, H as TS_DECL_RE, I as GO_DECL_RE, J as FILE_REDIRECT, K as CODE_MUTATORS, L as JAVA_DECL_RE, M as clearUserGuards, N as registerGuard, O as capVerbosity, Ot as isApexCommand, P as runGuards, Q as PROTECTED_GIT_RE, R as PHP_DECL_RE, S as skillTriggerGate, St as detectFramework, T as frameworkSolidGate, Tt as ProjectType, U as interfaceSeparationGuard, V as SWIFT_PROTO_RE, W as bashWriteGuard, X as SESSION_STATE_FRAGMENT, Y as SAFE_PREFIXES, Z as PROTECTED_FRAGMENTS, _ as DEV_VERBS, _t as PLUGINS_DIR, a as firstHeading, at as GuardContext, b as detectClaudeMdProjectType, bt as countLines, c as parseEntry, ct as PolicyResult, d as EXCLUDE_DIRS, dt as PROJECT_INSTALL, et as ASK_PATTERNS, f as PROJECT_INDICATORS, ft as RALPH_SAFE, g as loadApexTaskState, gt as FileSizeVerdict, h as buildApexTaskInjection, ht as matchPatterns, i as firstComment, it as Guard, j as GUARDS, k as detectCreationIntent, kt as requiredArchSkill, l as parseBodyDesc, lt as GIT_ASK, m as buildApexTaskContext, mt as isRalphMode, n as missingSeoElements, nt as LabeledPattern, o as TreeEntry, ot as evaluate, p as ApexTaskState, pt as SYSTEM_INSTALL, q as CODE_REDIRECT, r as descFromText, rt as securityGuard, s as parseEnrichment, st as PolicyContext, t as isHtmlLike, tt as CRITICAL_PATTERNS, u as parseField, ut as GIT_BLOCKED, v as buildApexInstruction, vt as SOLID_REF, w as SKILL_TRIGGERS, wt as ModularArchitecture, x as detectRequiredSkills, xt as evaluateFileSize, y as buildClaudeMdContext, yt as countFrameworkCodeLines, z as PY_MODEL_RE } from "../index-BIGVNQB8.mjs";
2
2
  import { a as PRE_AUTH_GATES, c as evaluateApex, i as POST_AUTH_GATES, l as freshnessGate, n as ApexContext, o as brainstormGate, r as ApexGate, s as docConsultedGate, t as APEX_GATES, u as solidReadGate } from "../apex-Wdi1nq_w.mjs";
3
3
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, ApexContext, ApexGate, ApexTaskState, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, FILE_REDIRECT, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, Guard, GuardContext, JAVA_DECL_RE, LabeledPattern, MAX_EXA_RESULTS, MAX_TOKENS, ModularArchitecture, PHP_DECL_RE, PLUGINS_DIR, POST_AUTH_GATES, PRE_AUTH_GATES, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PROTECTED_GIT_RE, PY_MODEL_RE, type PolicyContext, type PolicyResult, ProjectType, RALPH_SAFE, RUST_DECL_RE, SAFE_PREFIXES, SESSION_STATE_FRAGMENT, SKILL_TRIGGERS, SOLID_REF, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, TreeEntry, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countFrameworkCodeLines, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, isRalphMode, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate, usesTailwindUtilities };
@@ -1,5 +1,5 @@
1
1
  import { A as GIT_ASK, B as detectProjectType, C as SESSION_STATE_FRAGMENT, D as ASK_PATTERNS, E as protectedPathGuard, F as isRalphMode, H as requiredArchSkill, I as matchPatterns, L as detectFramework, M as PROJECT_INSTALL, N as RALPH_SAFE, O as CRITICAL_PATTERNS, P as SYSTEM_INSTALL, R as DEV_KEYWORDS, S as SAFE_PREFIXES, T as PROTECTED_GIT_RE, V as isApexCommand, _ as bashWriteGuard, a as registerGuard, b as CODE_REDIRECT, c as GO_DECL_RE, d as PY_MODEL_RE, f as RUST_DECL_RE, h as interfaceSeparationGuard, i as clearUserGuards, j as GIT_BLOCKED, k as securityGuard, l as JAVA_DECL_RE, m as TS_DECL_RE, n as FAIL_CLOSED, o as runGuards, p as SWIFT_PROTO_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as ASK_WRITERS, w as PROTECTED_FRAGMENTS, x as FILE_REDIRECT, y as CODE_MUTATORS, z as detectModularArchitecture } from "../evaluate-ClfbCDiY.mjs";
2
2
  import { d as countFrameworkCodeLines, f as countLines, l as PLUGINS_DIR, p as evaluateFileSize, u as SOLID_REF } from "../home-state-BXf38Zi1.mjs";
3
- import { A as detectCreationIntent, C as SKILL_TRIGGERS, D as MAX_EXA_RESULTS, F as docConsultedGate, I as evaluateApex, L as freshnessGate, M as POST_AUTH_GATES, N as PRE_AUTH_GATES, O as MAX_TOKENS, P as brainstormGate, R as solidReadGate, S as usesTailwindUtilities, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as APEX_GATES, k as capVerbosity, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, w as frameworkSolidGate, x as skillTriggerGate, y as parseField } from "../validate-DnLU4IHy.mjs";
3
+ import { A as capVerbosity, C as usesTailwindUtilities, F as brainstormGate, I as docConsultedGate, L as evaluateApex, M as APEX_GATES, N as POST_AUTH_GATES, O as MAX_EXA_RESULTS, P as PRE_AUTH_GATES, R as freshnessGate, S as skillTriggerGate, T as frameworkSolidGate, a as firstHeading, b as parseField, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as detectCreationIntent, k as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as SKILL_TRIGGERS, x as detectRequiredSkills, y as parseBodyDesc, z as solidReadGate } from "../validate-DhOX5hDK.mjs";
4
4
  import "../policy-la_KkjCS.mjs";
5
5
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, FILE_REDIRECT, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, JAVA_DECL_RE, MAX_EXA_RESULTS, MAX_TOKENS, PHP_DECL_RE, PLUGINS_DIR, POST_AUTH_GATES, PRE_AUTH_GATES, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PROTECTED_GIT_RE, PY_MODEL_RE, RALPH_SAFE, RUST_DECL_RE, SAFE_PREFIXES, SESSION_STATE_FRAGMENT, SKILL_TRIGGERS, SOLID_REF, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countFrameworkCodeLines, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, isRalphMode, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate, usesTailwindUtilities };
@@ -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). */
@@ -280,18 +287,20 @@ declare function claudeMdKey(prompt: string, ctx: string): string;
280
287
  * is emitted on EVERY message" is thus preserved.
281
288
  * @param prompt - The raw user prompt.
282
289
  * @param cwd - Project root (for project-type detection).
290
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
283
291
  * @returns The native hook stdout (possibly empty).
284
292
  */
285
- declare function promptSubmitContext(prompt: string, cwd: string): string;
293
+ declare function promptSubmitContext(prompt: string, cwd: string, id?: string): string;
286
294
  /**
287
295
  * PreToolUse Task context injection: render the APEX sub-agent context as a
288
296
  * Claude `additionalContext` response when `.claude/apex/` exists, else "".
289
297
  * Harness-produced (not owner CLAUDE.md content), so it is subject to the
290
298
  * per-fragment {@link capFragment} budget — unlike {@link promptSubmitContext}.
291
299
  * @param cwd - Fallback project root when `CLAUDE_PROJECT_DIR` is unset.
300
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
292
301
  * @returns The native hook stdout (possibly empty).
293
302
  */
294
- declare function taskContext(cwd: string): string;
303
+ declare function taskContext(cwd: string, id?: string): string;
295
304
  //#endregion
296
305
  //#region src/runtime/home-state.d.ts
297
306
  /** Home `~/.claude` dir — per-harness config (CLAUDE.md, logs, plugins). */
@@ -442,11 +451,13 @@ declare function trackAgentMemory(data: Record<string, unknown>, home?: string,
442
451
  //#region src/runtime/lifecycle/teammate-idle.d.ts
443
452
  /**
444
453
  * 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`.
454
+ * modified, suggest sniper validation as plain text — TeammateIdle has no
455
+ * `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
456
+ * ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
457
+ * the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
447
458
  * @param data - The raw hook payload.
448
459
  * @param home - Home dir (defaults to `~`).
449
- * @returns The native hook stdout (possibly empty).
460
+ * @returns The plain notice text, or "" when no code file was modified.
450
461
  */
451
462
  declare function validateTeammateOutput(data: Record<string, unknown>, home?: string): string;
452
463
  //#endregion
@@ -463,13 +474,15 @@ declare function logToolFailure(data: Record<string, unknown>, home?: string, no
463
474
  //#endregion
464
475
  //#region src/runtime/lifecycle/pre-compact.d.ts
465
476
  /**
466
- * Handle PreCompact: back up `.claude/apex/task.json` to `backups/`, keep only
467
- * the 5 newest, and emit a confirmation. Ports `pre-compact/save-apex-state.py`.
477
+ * Handle PreCompact: back up the target apex `task.json` (`.claude/apex/`,
478
+ * `.codex/apex/`, ...) to `backups/`, keep only the 5 newest, and emit a
479
+ * confirmation. Ports `pre-compact/save-apex-state.py`.
468
480
  * @param cwd - Project root.
469
481
  * @param now - Clock (defaults to `Date.now()`).
482
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
470
483
  * @returns The native hook stdout (possibly empty when no task.json).
471
484
  */
472
- declare function saveApexState(cwd: string, now?: number): string;
485
+ declare function saveApexState(cwd: string, now?: number, id?: string): string;
473
486
  //#endregion
474
487
  //#region src/runtime/lifecycle/session-end.d.ts
475
488
  /**
@@ -517,11 +530,11 @@ declare function postEditTypescript(filePath: string): string;
517
530
  /**
518
531
  * Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
519
532
  * `null` when unhandled (caller falls through to the default pipeline).
520
- * @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation).
533
+ * @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation); `id` selects the harness target (defaults to "claude-code").
521
534
  */
522
- declare function dispatchAipilot(event: string, payload: Record<string, unknown>, cwd: string, now: number, home?: string): Promise<string | null>;
523
- /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
524
- declare function aipilotPostToolUse(payload: Record<string, unknown>, cwd: string): Promise<string>;
535
+ declare function dispatchAipilot(event: string, payload: Record<string, unknown>, cwd: string, now: number, home?: string, id?: string): Promise<string | null>;
536
+ /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope; `id` selects the harness target (defaults to "claude-code"). */
537
+ declare function aipilotPostToolUse(payload: Record<string, unknown>, cwd: string, id?: string): Promise<string>;
525
538
  //#endregion
526
539
  //#region src/runtime/lifecycle/dispatch.d.ts
527
540
  /** Which plugin's hooks.json invoked the harness (selects SessionStart behavior). */
@@ -533,6 +546,8 @@ interface LifecycleInput {
533
546
  cwd: string;
534
547
  scope: PluginScope;
535
548
  now: number;
549
+ /** Harness target id (defaults to "claude-code" — zero-regression default). */
550
+ id?: string;
536
551
  }
537
552
  /**
538
553
  * Route a lifecycle/session/context hook event to its ported handler. Returns
@@ -719,8 +734,8 @@ declare function saveSecurityState(state: Record<string, unknown>, now?: number,
719
734
  //#region src/runtime/lifecycle/lessons/dispatch.d.ts
720
735
  /**
721
736
  * 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.
737
+ * context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
738
+ * MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
724
739
  * @param event - The raw hook event name.
725
740
  * @param payload - The raw hook payload.
726
741
  * @param cwd - Project root for memory injection.
@@ -806,9 +821,10 @@ declare function writePluginMap(outputDir: string, pluginName: string, version:
806
821
  * @param cwd - Project root.
807
822
  * @param scope - The invoking plugin scope (defaults to `core`).
808
823
  * @param now - Clock.
824
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
809
825
  * @returns The native stdout, or `null` when unhandled.
810
826
  */
811
- declare function lifecycleStdout(payload: Record<string, unknown>, cwd: string, scope: PluginScope, now: number): string | null;
827
+ declare function lifecycleStdout(payload: Record<string, unknown>, cwd: string, scope: PluginScope, now: number, id?: string): string | null;
812
828
  /**
813
829
  * Post-edit additions for core-scope PostToolUse: auto-document a Read of a
814
830
  * SKILL.md/README/docs file; else (Write/Edit) track cumulative session
@@ -819,9 +835,10 @@ declare function lifecycleStdout(payload: Record<string, unknown>, cwd: string,
819
835
  * @param scope - The invoking plugin scope.
820
836
  * @param event - The normalized event.
821
837
  * @param now - Clock.
838
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
822
839
  * @returns The extra stdout (possibly empty).
823
840
  */
824
- declare function postEditContext(scope: PluginScope, event: NormalizedEvent, now: number): Promise<string>;
841
+ declare function postEditContext(scope: PluginScope, event: NormalizedEvent, now: number, id?: string): Promise<string>;
825
842
  //#endregion
826
843
  //#region src/runtime/handle-types.d.ts
827
844
  /** Options for {@link handleHook} (caller supplies the clock + project root). */
@@ -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-Dthtlp7Z.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -1,5 +1,5 @@
1
1
  import { r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
2
- import { c as ttlLabel } from "./dotenv-Jj8aL1FL.mjs";
2
+ import { c as ttlLabel, t as HOME_DIR } from "./dotenv-Jj8aL1FL.mjs";
3
3
  import { R as DEV_KEYWORDS, g as moduleAwarePath } from "./evaluate-ClfbCDiY.mjs";
4
4
  import { d as countFrameworkCodeLines, l as PLUGINS_DIR, u as SOLID_REF } from "./home-state-BXf38Zi1.mjs";
5
5
  import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
@@ -1187,9 +1187,48 @@ function getExpertAgent(type, pluginsDirOverride) {
1187
1187
  return "general-purpose";
1188
1188
  }
1189
1189
  //#endregion
1190
- //#region src/policy/claude-md-context.ts
1191
- /** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
1192
- const DEV_VERBS = /(cr[ée]er|impl[ée]menter|ajouter|d[ée]velopper|construire|build|refactor|migrer|implement|create|add|develop)/i;
1190
+ //#region src/policy/apex-target.ts
1191
+ /**
1192
+ * Harness-target helpers for the APEX context builders. Single source of
1193
+ * truth for the home-dir segment / root doc name / plan-tool idiom that
1194
+ * differ between Claude Code and Codex (and beyond), so the builders in
1195
+ * `claude-md-context.ts`, `apex-task-context.ts`, and `inject-apex.ts` never
1196
+ * hardcode `.claude`/`CLAUDE.md`/`TaskCreate` again. Runtime `id` values flow
1197
+ * through the pipeline as plain `string` (see `handleHook(id: string, ...)`),
1198
+ * so these helpers accept `string` and narrow internally — same idiom as the
1199
+ * existing `HOME_DIR[id] ?? ".claude"` precedent in `cartographer/detect.ts`.
1200
+ */
1201
+ /**
1202
+ * Home-dir segment for a harness target (".claude", ".codex", ...). Falls
1203
+ * back to ".claude" for any harness without a {@link HOME_DIR} entry.
1204
+ * @param id - Harness id (e.g. "claude-code", "codex").
1205
+ * @returns The home-dir segment, e.g. ".claude".
1206
+ */
1207
+ function harnessHomeSegment(id) {
1208
+ return HOME_DIR[id] ?? ".claude";
1209
+ }
1210
+ /**
1211
+ * Root instructions doc name for a harness target: "AGENTS.md" for Codex,
1212
+ * "CLAUDE.md" for every other target (including the default/unknown fallback).
1213
+ * @param id - Harness id.
1214
+ * @returns The doc file name.
1215
+ */
1216
+ function apexDocName(id) {
1217
+ return id === "codex" ? "AGENTS.md" : "CLAUDE.md";
1218
+ }
1219
+ /**
1220
+ * Native plan/track tool name for a harness target: Codex's `update_plan` vs
1221
+ * Claude's `TaskCreate`. Covers only the PLAN-step line — other Claude-only
1222
+ * tool references (`TaskList`, `TaskUpdate`) are branched inline by the
1223
+ * builders since their surrounding prose differs structurally, not just by token.
1224
+ * @param id - Harness id.
1225
+ * @returns The plan-tool name.
1226
+ */
1227
+ function apexPlanTool(id) {
1228
+ return id === "codex" ? "update_plan" : "TaskCreate";
1229
+ }
1230
+ //#endregion
1231
+ //#region src/policy/detect-claude-md-project-type.ts
1193
1232
  /**
1194
1233
  * Detect the project type from the cwd, reproducing the legacy Python logic:
1195
1234
  * package.json containing "next" → nextjs, else "react" → react; else
@@ -1212,31 +1251,35 @@ function detectClaudeMdProjectType(cwd) {
1212
1251
  } catch {}
1213
1252
  return "generic";
1214
1253
  }
1254
+ //#endregion
1255
+ //#region src/policy/claude-md-context.ts
1256
+ /** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
1257
+ const DEV_VERBS = /(cr[ée]er|impl[ée]menter|ajouter|d[ée]velopper|construire|build|refactor|migrer|implement|create|add|develop)/i;
1215
1258
  /**
1216
1259
  * Build the APEX instruction preamble for a development task.
1217
1260
  * @param projectType - Detected project type label.
1218
1261
  * @param maxLines - SOLID per-file line ceiling.
1262
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1219
1263
  * @returns The APEX instruction text.
1220
1264
  */
1221
- function buildApexInstruction(projectType, maxLines) {
1265
+ function buildApexInstruction(projectType, maxLines, id = "claude-code") {
1222
1266
  const expertAgent = getExpertAgent(projectType);
1223
- return `INSTRUCTION: This is a development task. Use APEX methodology:
1224
-
1225
- **TRACKING FILE**: [project]/.claude/apex/task.json — create it yourself via apex-methodology Step 0 (init-tracking) if missing
1226
-
1227
- 1. **ANALYZE** (MANDATORY - 3 AGENTS IN PARALLEL):
1228
- - explore-codebase + research-expert + ${expertAgent} (framework expertise)\n - Project type detected: ${projectType}\n\n2. **PLAN**: Use TaskCreate to break down tasks (<${maxLines} lines per file)\n\n3. **EXECUTE**: ${expertAgent}, follow SOLID principles, split at ${maxLines - 10} lines\n\n4. **eLICIT**: self-review with NAMED elicitation techniques (apex ref 03.5-elicit) — fix findings BEFORE validation\n\n5. **VERIFY**: functional check — run it, confirm references⇔declarations consistency\n\n6. **eXAMINE**: Run sniper agent after ANY modification\n\n**GATE**: eLicit + Verify BEFORE sniper — NEVER skip.\n\n**IMPORTANT**: Read .claude/apex/task.json to check documentation status before writing code.`;
1267
+ const seg = harnessHomeSegment(id);
1268
+ return `INSTRUCTION: This is a development task. Use APEX methodology:\n\n**TRACKING FILE**: [project]/${seg}/apex/task.json — create it yourself via apex-methodology Step 0 (init-tracking) if missing\n\n1. **ANALYZE** (MANDATORY - 3 AGENTS IN PARALLEL):\n - explore-codebase + research-expert + ${expertAgent} (framework expertise)\n - Project type detected: ${projectType}\n\n2. **PLAN**: Use ${apexPlanTool(id)} to break down tasks (<${maxLines} lines per file)\n\n3. **EXECUTE**: ${expertAgent}, follow SOLID principles, split at ${maxLines - 10} lines\n\n4. **eLICIT**: self-review with NAMED elicitation techniques (apex ref 03.5-elicit) — fix findings BEFORE validation\n\n5. **VERIFY**: functional check — run it, confirm references⇔declarations consistency\n\n6. **eXAMINE**: Run sniper agent after ANY modification\n\n**GATE**: eLicit + Verify BEFORE sniper — NEVER skip.\n\n**IMPORTANT**: Read ${seg}/apex/task.json to check documentation status before writing code.`;
1229
1269
  }
1230
1270
  /**
1231
- * Build the UserPromptSubmit injection text: read `~/.claude/CLAUDE.md` and,
1271
+ * Build the UserPromptSubmit injection text: read the target's root
1272
+ * instructions doc (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, ...) and,
1232
1273
  * when the prompt matches a dev verb, prepend the APEX instruction. Returns
1233
- * `null` when CLAUDE.md is absent/unreadable (the hook then emits nothing).
1274
+ * `null` when the doc is absent/unreadable (the hook then emits nothing).
1234
1275
  * @param prompt - The raw user prompt.
1235
1276
  * @param cwd - Project root (for project-type detection).
1277
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1236
1278
  * @returns The injection text, or `null` to emit nothing.
1237
1279
  */
1238
- function buildClaudeMdContext(prompt, cwd) {
1239
- const claudeMd = join(homedir(), ".claude", "CLAUDE.md");
1280
+ function buildClaudeMdContext(prompt, cwd, id = "claude-code") {
1281
+ const docName = apexDocName(id);
1282
+ const claudeMd = join(homedir(), harnessHomeSegment(id), docName);
1240
1283
  if (!existsSync(claudeMd)) return null;
1241
1284
  let claudeContent;
1242
1285
  try {
@@ -1244,8 +1287,8 @@ function buildClaudeMdContext(prompt, cwd) {
1244
1287
  } catch {
1245
1288
  return null;
1246
1289
  }
1247
- if (!DEV_VERBS.test(prompt) && !DEV_KEYWORDS.test(prompt)) return `# CLAUDE.md\n${claudeContent}`;
1248
- return `${buildApexInstruction(detectClaudeMdProjectType(cwd), resolveMaxLines())}\n\n# CLAUDE.md\n${claudeContent}`;
1290
+ if (!DEV_VERBS.test(prompt) && !DEV_KEYWORDS.test(prompt)) return `# ${docName}\n${claudeContent}`;
1291
+ return `${buildApexInstruction(detectClaudeMdProjectType(cwd), resolveMaxLines(), id)}\n\n# ${docName}\n${claudeContent}`;
1249
1292
  }
1250
1293
  //#endregion
1251
1294
  //#region src/policy/apex-task-context.ts
@@ -1282,21 +1325,29 @@ function loadApexTaskState(taskFile) {
1282
1325
  * Build the APEX context string injected into a Task sub-agent prompt.
1283
1326
  * @param state - The parsed task state.
1284
1327
  * @param maxLines - SOLID per-file line ceiling.
1328
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1285
1329
  * @returns The injection text.
1286
1330
  */
1287
- function buildApexTaskContext(state, maxLines) {
1288
- return `⚠️ APEX MODE - Read .claude/apex/AGENTS.md for rules\n\nCurrent: Task #${state.id} - ${state.subject} (Phase: ${state.phase})\nDocs consulted: ${state.docs}\n\nAgent must:\n1. Read task.json → find last 3 completed tasks\n2. Read their notes in docs/ (task-{ID}-{subject}.md)\n3. TaskList → see pending tasks\n4. TaskUpdate(in_progress) → before starting\n5. Apply SOLID (files < ${maxLines} lines)\n6. Write notes to docs/task-{ID}-{subject}.md\n7. TaskUpdate(completed) → triggers auto-commit`;
1331
+ function buildApexTaskContext(state, maxLines, id = "claude-code") {
1332
+ const seg = harnessHomeSegment(id);
1333
+ const isCodex = id === "codex";
1334
+ const step3 = isCodex ? "update_plan → review the current plan" : "TaskList → see pending tasks";
1335
+ const step4 = isCodex ? "update_plan → mark the active step in_progress before starting" : "TaskUpdate(in_progress) → before starting";
1336
+ const step7 = isCodex ? "update_plan → mark the step completed when done" : "TaskUpdate(completed) → triggers auto-commit";
1337
+ return `⚠️ APEX MODE - Read ${seg}/apex/AGENTS.md for rules\n\nCurrent: Task #${state.id} - ${state.subject} (Phase: ${state.phase})\nDocs consulted: ${state.docs}\n\nAgent must:\n1. Read task.json → find last 3 completed tasks\n2. Read their notes in docs/ (task-{ID}-{subject}.md)\n3. ${step3}\n4. ${step4}\n5. Apply SOLID (files < ${maxLines} lines)\n6. Write notes to docs/task-{ID}-{subject}.md\n7. ${step7}`;
1289
1338
  }
1290
1339
  /**
1291
- * Build the PreToolUse Task injection, gated on the existence of the project's
1292
- * `.claude/apex/` directory. Returns `null` when APEX is not active (no dir).
1340
+ * Build the PreToolUse Task injection, gated on the existence of the
1341
+ * project's target apex dir (`.claude/apex/`, `.codex/apex/`, ...). Returns
1342
+ * `null` when APEX is not active (no dir).
1293
1343
  * @param projectRoot - `CLAUDE_PROJECT_DIR` or cwd.
1344
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1294
1345
  * @returns The injection text, or `null` to emit nothing.
1295
1346
  */
1296
- function buildApexTaskInjection(projectRoot) {
1297
- const apexDir = join(projectRoot, ".claude", "apex");
1347
+ function buildApexTaskInjection(projectRoot, id = "claude-code") {
1348
+ const apexDir = join(projectRoot, harnessHomeSegment(id), "apex");
1298
1349
  if (!existsSync(apexDir)) return null;
1299
- return buildApexTaskContext(loadApexTaskState(join(apexDir, "task.json")), resolveMaxLines());
1350
+ return buildApexTaskContext(loadApexTaskState(join(apexDir, "task.json")), resolveMaxLines(), id);
1300
1351
  }
1301
1352
  //#endregion
1302
1353
  //#region src/policy/cartographer/indicators.ts
@@ -1540,4 +1591,4 @@ function missingSeoElements(html) {
1540
1591
  return missing;
1541
1592
  }
1542
1593
  //#endregion
1543
- export { detectCreationIntent as A, SKILL_TRIGGERS as C, MAX_EXA_RESULTS as D, isExcludedSwiftPath as E, docConsultedGate as F, evaluateApex as I, freshnessGate as L, POST_AUTH_GATES as M, PRE_AUTH_GATES as N, MAX_TOKENS as O, brainstormGate as P, solidReadGate as R, usesTailwindUtilities as S, isExcludedJsPath as T, scanPlugin as _, firstHeading as a, detectRequiredSkills as b, EXCLUDE_DIRS as c, buildApexTaskInjection as d, loadApexTaskState as f, detectClaudeMdProjectType as g, buildClaudeMdContext as h, firstComment as i, APEX_GATES as j, capVerbosity as k, PROJECT_INDICATORS as l, buildApexInstruction as m, missingSeoElements as n, parseEnrichment as o, DEV_VERBS as p, descFromText as r, parseEntry as s, isHtmlLike as t, buildApexTaskContext as u, parseBodyDesc as v, frameworkSolidGate as w, skillTriggerGate as x, parseField as y };
1594
+ export { capVerbosity as A, usesTailwindUtilities as C, isExcludedSwiftPath as D, isExcludedJsPath as E, brainstormGate as F, docConsultedGate as I, evaluateApex as L, APEX_GATES as M, POST_AUTH_GATES as N, MAX_EXA_RESULTS as O, PRE_AUTH_GATES as P, freshnessGate as R, skillTriggerGate as S, frameworkSolidGate as T, harnessHomeSegment as _, firstHeading as a, parseField as b, EXCLUDE_DIRS as c, buildApexTaskInjection as d, loadApexTaskState as f, detectClaudeMdProjectType as g, buildClaudeMdContext as h, firstComment as i, detectCreationIntent as j, MAX_TOKENS as k, PROJECT_INDICATORS as l, buildApexInstruction as m, missingSeoElements as n, parseEnrichment as o, DEV_VERBS as p, descFromText as r, parseEntry as s, isHtmlLike as t, buildApexTaskContext as u, scanPlugin as v, SKILL_TRIGGERS as w, detectRequiredSkills as x, parseBodyDesc as y, solidReadGate as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
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",