@fusengine/harness 0.1.77 → 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.
package/dist/cli/bin.mjs CHANGED
@@ -4,7 +4,7 @@ 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-BDlk2utY.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
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";
@@ -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";
@@ -591,10 +591,11 @@ 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
601
  return attachSystemMessage(contextResponse("UserPromptSubmit", ctx), "CLAUDE.md injected");
@@ -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
@@ -1442,14 +1444,17 @@ function stamp$1(now) {
1442
1444
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1443
1445
  }
1444
1446
  /**
1445
- * Handle PreCompact: back up `.claude/apex/task.json` to `backups/`, keep only
1446
- * 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`.
1447
1450
  * @param cwd - Project root.
1448
1451
  * @param now - Clock (defaults to `Date.now()`).
1452
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
1449
1453
  * @returns The native hook stdout (possibly empty when no task.json).
1450
1454
  */
1451
- function saveApexState(cwd, now = Date.now()) {
1452
- 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");
1453
1458
  const stateFile = join(apexDir, "task.json");
1454
1459
  if (!existsSync(stateFile)) return "";
1455
1460
  const backupDir = join(apexDir, "backups");
@@ -1459,7 +1464,7 @@ function saveApexState(cwd, now = Date.now()) {
1459
1464
  for (const old of backups.slice(5)) try {
1460
1465
  rmSync(join(backupDir, old), { force: true });
1461
1466
  } catch {}
1462
- 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/` });
1463
1468
  }
1464
1469
  //#endregion
1465
1470
  //#region src/runtime/lifecycle/session-end.ts
@@ -4074,18 +4079,25 @@ function cartographerContext() {
4074
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`;
4075
4080
  }
4076
4081
  /**
4077
- * Build the APEX sub-agent injection for SubagentStart, or "" when the project
4078
- * 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.
4079
4083
  * @param cwd - Fallback project root when `CLAUDE_PROJECT_DIR` is unset.
4080
4084
  * @param home - Home dir (unused placeholder; kept for symmetry/testing).
4085
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
4081
4086
  * @returns The native hook stdout (possibly empty).
4082
4087
  */
4083
- async function injectApexSubagentContext(cwd, home = homedir()) {
4084
- 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");
4085
4092
  if (!existsSync(apexDir)) return "";
4086
4093
  const agentsPath = join(apexDir, "AGENTS.md");
4087
4094
  const agents = existsSync(agentsPath) ? readText(agentsPath).slice(0, 4e3) : "";
4088
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";
4089
4101
  return contextResponse("SubagentStart", capFragment("apex-subagent", `## APEX Sub-Agent Instructions
4090
4102
 
4091
4103
  You are a sub-agent in APEX workflow. Follow these rules:
@@ -4094,24 +4106,24 @@ You are a sub-agent in APEX workflow. Follow these rules:
4094
4106
  ${agents}
4095
4107
 
4096
4108
  ### 2. Task Context
4097
- - Last completed: ${taskData ? completedTasks(taskData.tasks) : "none"}
4098
- - Pending: ${taskData ? pendingTasks(taskData.tasks) : "none"}
4109
+ - Last completed: ${completed}
4110
+ - Pending: ${pending}
4099
4111
 
4100
4112
  ### 3. Before Starting Work
4101
- - Use TaskUpdate(taskId, status: in_progress) before starting
4113
+ - ${beforeStart}
4102
4114
 
4103
4115
  ### 4. SOLID Rules
4104
4116
  - Files < ${resolveMaxLines()} lines | Interfaces in src/interfaces/ | JSDoc/PHPDoc required
4105
4117
 
4106
4118
  ### 5. Research Before Code
4107
- - Use Context7/Exa for docs | Write notes to .claude/apex/docs/
4119
+ - Use Context7/Exa for docs | Write notes to ${seg}/apex/docs/
4108
4120
 
4109
4121
  ### 6. Before Done (NEVER skip)
4110
4122
  - eLicit: self-review with a NAMED elicitation technique; fix findings first
4111
4123
  - Verify: run/functional-check your changes (references⇔declarations)
4112
4124
 
4113
4125
  ### 7. When Done
4114
- - TaskUpdate(taskId, status: completed) triggers auto-commit${cartographerContext()}`));
4126
+ - ${whenDone}${cartographerContext()}`));
4115
4127
  }
4116
4128
  /** 16-char hex SHA-256 of `text` (project hash / doc topic key). */
4117
4129
  function hashText16(text) {
@@ -5047,18 +5059,22 @@ async function onComplete(taskFile, taskId, projectRoot) {
5047
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.`);
5048
5060
  }
5049
5061
  /**
5050
- * 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.
5051
5065
  * @param payload - The raw hook payload (`tool_name`, `tool_input`, `tool_response`).
5052
5066
  * @param cwd - Fallback project root (uses `CLAUDE_PROJECT_DIR` first).
5067
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5053
5068
  * @returns The native hook stdout (possibly empty).
5054
5069
  */
5055
- async function syncTaskTracking(payload, cwd) {
5070
+ async function syncTaskTracking(payload, cwd, id = "claude-code") {
5056
5071
  const toolName = String(payload.tool_name ?? "");
5057
5072
  if (toolName !== "TaskCreate" && toolName !== "TaskUpdate") return "";
5058
5073
  const projectRoot = process.env.CLAUDE_PROJECT_DIR ?? cwd;
5059
- const taskFile = join(projectRoot, ".claude", "apex", "task.json");
5074
+ const seg = harnessHomeSegment(id);
5075
+ const taskFile = join(projectRoot, seg, "apex", "task.json");
5060
5076
  if (!existsSync(taskFile)) return "";
5061
- const unlock = await acquireLock(join(projectRoot, ".claude", "apex", ".task.lock"), 1e4);
5077
+ const unlock = await acquireLock(join(projectRoot, seg, "apex", ".task.lock"), 1e4);
5062
5078
  if (!unlock) return "";
5063
5079
  try {
5064
5080
  const ti = payload.tool_input ?? {};
@@ -5267,9 +5283,9 @@ async function typeSpecificCache(agent, cwd, now, home) {
5267
5283
  * entries (APEX context + lessons) fire for EVERY sub-agent, then the type-specific
5268
5284
  * cache is concatenated on top — sniper too, hence no early return.
5269
5285
  */
5270
- async function onSubagentStart(payload, cwd, now, home) {
5286
+ async function onSubagentStart(payload, cwd, now, home, id) {
5271
5287
  const agent = agentTypeOf(payload);
5272
- 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));
5273
5289
  }
5274
5290
  /** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. */
5275
5291
  async function onSubagentStop(payload, cwd, home) {
@@ -5285,10 +5301,10 @@ async function onSubagentStop(payload, cwd, home) {
5285
5301
  /**
5286
5302
  * Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
5287
5303
  * `null` when unhandled (caller falls through to the default pipeline).
5288
- * @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").
5289
5305
  */
5290
- async function dispatchAipilot(event, payload, cwd, now, home = homedir()) {
5291
- 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);
5292
5308
  if (event === "SubagentStop") return onSubagentStop(payload, cwd, home);
5293
5309
  if (event === "SessionEnd" || event === "Stop") {
5294
5310
  await cacheAnalyticsSave(home, now);
@@ -5297,9 +5313,9 @@ async function dispatchAipilot(event, payload, cwd, now, home = homedir()) {
5297
5313
  if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home);
5298
5314
  return null;
5299
5315
  }
5300
- /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
5301
- async function aipilotPostToolUse(payload, cwd) {
5302
- 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);
5303
5319
  }
5304
5320
  //#endregion
5305
5321
  //#region src/runtime/lifecycle/dispatch.ts
@@ -5341,7 +5357,7 @@ function dispatchLifecycle(input) {
5341
5357
  case "TeammateIdle": return teammateIdleContext(input.payload, input.cwd, void 0, input.now);
5342
5358
  case "PostToolUseFailure": return failureLessonContext(input.payload, input.cwd, void 0, input.now);
5343
5359
  case "PostCompact": return input.scope === "core" ? postCompactContext(input.payload, input.cwd, import.meta.url, input.now) : "";
5344
- case "PreCompact": return saveApexState(input.cwd, input.now);
5360
+ case "PreCompact": return saveApexState(input.cwd, input.now, input.id ?? "claude-code");
5345
5361
  case "SessionEnd":
5346
5362
  if (input.scope !== "aipilot") cleanupSession(void 0, input.now);
5347
5363
  return "";
@@ -5883,19 +5899,22 @@ function docTypeOf(filePath) {
5883
5899
  }
5884
5900
  /**
5885
5901
  * Auto-document a Read of a SKILL.md/README/CLAUDE.md/docs/references file
5886
- * into `.claude/apex/docs/task-<current>-<framework>.md` (skipped when the
5887
- * 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`).
5888
5905
  * @param filePath - The path passed to the Read tool.
5889
5906
  * @param now - Clock (defaults to `Date.now()`).
5907
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5890
5908
  * @returns The native `systemMessage` stdout, or "" when nothing was logged.
5891
5909
  */
5892
- async function autoDocumentRead(filePath, now = Date.now()) {
5910
+ async function autoDocumentRead(filePath, now = Date.now(), id = "claude-code") {
5893
5911
  if (!filePath || !DOC_PATTERNS.some((p) => p.test(filePath))) return "";
5894
5912
  const root = projectRootOrNull(dirname(filePath));
5895
5913
  if (!root) return "";
5914
+ const seg = harnessHomeSegment(id);
5896
5915
  const framework = detectProjectType$1(root);
5897
- const current = (await readJsonFile(join(root, ".claude", "apex", "task.json")))?.current_task ?? "1";
5898
- 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");
5899
5918
  const docFile = join(docDir, `task-${current}-${framework}.md`);
5900
5919
  const ts = new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z");
5901
5920
  const fname = basename(filePath);
@@ -5934,15 +5953,17 @@ function additionalContextOf(stdout) {
5934
5953
  * @param cwd - Project root.
5935
5954
  * @param scope - The invoking plugin scope (defaults to `core`).
5936
5955
  * @param now - Clock.
5956
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5937
5957
  * @returns The native stdout, or `null` when unhandled.
5938
5958
  */
5939
- function lifecycleStdout(payload, cwd, scope, now) {
5959
+ function lifecycleStdout(payload, cwd, scope, now, id = "claude-code") {
5940
5960
  return dispatchLifecycle({
5941
5961
  event: rawEvent(payload),
5942
5962
  payload,
5943
5963
  cwd,
5944
5964
  scope,
5945
- now
5965
+ now,
5966
+ id
5946
5967
  });
5947
5968
  }
5948
5969
  /**
@@ -5955,11 +5976,12 @@ function lifecycleStdout(payload, cwd, scope, now) {
5955
5976
  * @param scope - The invoking plugin scope.
5956
5977
  * @param event - The normalized event.
5957
5978
  * @param now - Clock.
5979
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
5958
5980
  * @returns The extra stdout (possibly empty).
5959
5981
  */
5960
- async function postEditContext(scope, event, now) {
5982
+ async function postEditContext(scope, event, now, id = "claude-code") {
5961
5983
  if (scope !== "core" || !event.filePath) return "";
5962
- if (event.tool === "Read") return autoDocumentRead(event.filePath, now);
5984
+ if (event.tool === "Read") return autoDocumentRead(event.filePath, now, id);
5963
5985
  if (event.tool !== "Write" && event.tool !== "Edit") return "";
5964
5986
  const sniper = trackSessionChanges(event.sessionId, event.filePath, void 0, now);
5965
5987
  const lint = postEditTypescript(event.filePath);
@@ -6019,7 +6041,7 @@ const EXEMPT_PATTERNS = [
6019
6041
  /\.claude-plugin\//,
6020
6042
  /CHANGELOG\.md$/,
6021
6043
  /marketplace\.json$/,
6022
- /\/\.claude\/(apex|memory|logs|fusengine-cache)\//,
6044
+ /\/\.(claude|codex)\/(apex|memory|logs|fusengine-cache)\//,
6023
6045
  /\/\.fuse-harness\//
6024
6046
  ];
6025
6047
  /**
@@ -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({
@@ -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
  }
@@ -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
  /**
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 };
@@ -287,18 +287,20 @@ declare function claudeMdKey(prompt: string, ctx: string): string;
287
287
  * is emitted on EVERY message" is thus preserved.
288
288
  * @param prompt - The raw user prompt.
289
289
  * @param cwd - Project root (for project-type detection).
290
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
290
291
  * @returns The native hook stdout (possibly empty).
291
292
  */
292
- declare function promptSubmitContext(prompt: string, cwd: string): string;
293
+ declare function promptSubmitContext(prompt: string, cwd: string, id?: string): string;
293
294
  /**
294
295
  * PreToolUse Task context injection: render the APEX sub-agent context as a
295
296
  * Claude `additionalContext` response when `.claude/apex/` exists, else "".
296
297
  * Harness-produced (not owner CLAUDE.md content), so it is subject to the
297
298
  * per-fragment {@link capFragment} budget — unlike {@link promptSubmitContext}.
298
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).
299
301
  * @returns The native hook stdout (possibly empty).
300
302
  */
301
- declare function taskContext(cwd: string): string;
303
+ declare function taskContext(cwd: string, id?: string): string;
302
304
  //#endregion
303
305
  //#region src/runtime/home-state.d.ts
304
306
  /** Home `~/.claude` dir — per-harness config (CLAUDE.md, logs, plugins). */
@@ -472,13 +474,15 @@ declare function logToolFailure(data: Record<string, unknown>, home?: string, no
472
474
  //#endregion
473
475
  //#region src/runtime/lifecycle/pre-compact.d.ts
474
476
  /**
475
- * Handle PreCompact: back up `.claude/apex/task.json` to `backups/`, keep only
476
- * 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`.
477
480
  * @param cwd - Project root.
478
481
  * @param now - Clock (defaults to `Date.now()`).
482
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
479
483
  * @returns The native hook stdout (possibly empty when no task.json).
480
484
  */
481
- declare function saveApexState(cwd: string, now?: number): string;
485
+ declare function saveApexState(cwd: string, now?: number, id?: string): string;
482
486
  //#endregion
483
487
  //#region src/runtime/lifecycle/session-end.d.ts
484
488
  /**
@@ -526,11 +530,11 @@ declare function postEditTypescript(filePath: string): string;
526
530
  /**
527
531
  * Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
528
532
  * `null` when unhandled (caller falls through to the default pipeline).
529
- * @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").
530
534
  */
531
- declare function dispatchAipilot(event: string, payload: Record<string, unknown>, cwd: string, now: number, home?: string): Promise<string | null>;
532
- /** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
533
- 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>;
534
538
  //#endregion
535
539
  //#region src/runtime/lifecycle/dispatch.d.ts
536
540
  /** Which plugin's hooks.json invoked the harness (selects SessionStart behavior). */
@@ -542,6 +546,8 @@ interface LifecycleInput {
542
546
  cwd: string;
543
547
  scope: PluginScope;
544
548
  now: number;
549
+ /** Harness target id (defaults to "claude-code" — zero-regression default). */
550
+ id?: string;
545
551
  }
546
552
  /**
547
553
  * Route a lifecycle/session/context hook event to its ported handler. Returns
@@ -815,9 +821,10 @@ declare function writePluginMap(outputDir: string, pluginName: string, version:
815
821
  * @param cwd - Project root.
816
822
  * @param scope - The invoking plugin scope (defaults to `core`).
817
823
  * @param now - Clock.
824
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
818
825
  * @returns The native stdout, or `null` when unhandled.
819
826
  */
820
- 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;
821
828
  /**
822
829
  * Post-edit additions for core-scope PostToolUse: auto-document a Read of a
823
830
  * SKILL.md/README/docs file; else (Write/Edit) track cumulative session
@@ -828,9 +835,10 @@ declare function lifecycleStdout(payload: Record<string, unknown>, cwd: string,
828
835
  * @param scope - The invoking plugin scope.
829
836
  * @param event - The normalized event.
830
837
  * @param now - Clock.
838
+ * @param id - Harness target id (defaults to "claude-code" — zero-regression default).
831
839
  * @returns The extra stdout (possibly empty).
832
840
  */
833
- 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>;
834
842
  //#endregion
835
843
  //#region src/runtime/handle-types.d.ts
836
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-BDlk2utY.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.77",
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",