@fusengine/harness 0.1.77 → 0.1.79

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,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.79",
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",