@fusengine/harness 0.1.91 → 0.1.93

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.
Files changed (83) hide show
  1. package/README.md +17 -0
  2. package/dist/adapters/claude/index.mjs +1 -1
  3. package/dist/adapters/codex/index.mjs +1 -1
  4. package/dist/adapters/cursor/index.mjs +1 -1
  5. package/dist/adapters/hermes/index.mjs +1 -1
  6. package/dist/adapters/kimi/index.mjs +1 -1
  7. package/dist/apex-target-Xc2M32Pl.mjs +48 -0
  8. package/dist/apex-task-store-Cb65rE5i.mjs +467 -0
  9. package/dist/{claude-Ckv2_TgP.mjs → claude-D62hkUfS.mjs} +2 -84
  10. package/dist/cli/bin.mjs +7 -5
  11. package/dist/cli/index.d.mts +69 -1
  12. package/dist/cli/index.mjs +2 -2
  13. package/dist/config/index.mjs +2 -1
  14. package/dist/{dotenv-BLBkBTww.mjs → dotenv-C1LkcfW-.mjs} +1 -26
  15. package/dist/{handle-C43gA-Pr.mjs → handle-B-g9KL_9.mjs} +4837 -3466
  16. package/dist/{hermes-B9-p_3IF.mjs → hermes-ByopGx6C.mjs} +1 -1
  17. package/dist/index.d.mts +1 -1
  18. package/dist/index.mjs +3 -2
  19. package/dist/{kimi-G2wcSh5-.mjs → kimi-C-Oia9q-.mjs} +1 -1
  20. package/dist/{normalize-BjG6unTj.mjs → normalize-Dy8g9Ybl.mjs} +83 -3
  21. package/dist/policy/index.mjs +1 -1
  22. package/dist/prd-PvK7PKWS.mjs +494 -0
  23. package/dist/runtime/index.d.mts +13 -1
  24. package/dist/runtime/index.mjs +1 -1
  25. package/dist/runtime-io-DuumUeE6.mjs +84 -0
  26. package/dist/{session-state-D5gLr66m.d.mts → session-state-COg7Ej_2.d.mts} +32 -1
  27. package/dist/{skill-path-DVML3zfp.mjs → skill-path-Cz8WFaGu.mjs} +1 -1
  28. package/dist/{store-5-ZPKb0u.mjs → store-BVY6gIYM.mjs} +75 -3
  29. package/dist/tracking/index.d.mts +2 -2
  30. package/dist/tracking/index.mjs +2 -2
  31. package/dist/ttl-Dgwg_QAv.mjs +26 -0
  32. package/dist/{validate-KjZ1X9tH.mjs → validate-Dcjl0LUS.mjs} +4 -49
  33. package/package.json +1 -1
  34. package/src/adapters/cursor/context-budget.ts +144 -0
  35. package/src/adapters/cursor/context-limit.ts +115 -0
  36. package/src/adapters/cursor/context.ts +21 -2
  37. package/src/adapters/cursor/interfaces/context-budget.ts +25 -0
  38. package/src/adapters/cursor/native-response.ts +10 -129
  39. package/src/adapters/cursor/native-schemas.ts +161 -0
  40. package/src/adapters/cursor/normalize.ts +65 -0
  41. package/src/adapters/cursor/plugin-root.ts +103 -0
  42. package/src/adapters/cursor/respond.ts +94 -47
  43. package/src/cli/bin.ts +3 -0
  44. package/src/cli/index.ts +1 -0
  45. package/src/cli/prd/compact.ts +53 -0
  46. package/src/cli/prd/format.ts +23 -0
  47. package/src/cli/prd/index.ts +23 -0
  48. package/src/cli/prd/resolve.ts +88 -0
  49. package/src/cli/prd/shared.ts +80 -0
  50. package/src/cli/prd/status.ts +87 -0
  51. package/src/cli/prd/validate.ts +90 -0
  52. package/src/policy/prd/index.ts +36 -0
  53. package/src/policy/prd/interfaces/types.ts +103 -0
  54. package/src/policy/prd/prd-compact.ts +28 -0
  55. package/src/policy/prd/prd-context.ts +131 -0
  56. package/src/policy/prd/prd-crosscheck.ts +76 -0
  57. package/src/policy/prd/prd-enabled.ts +39 -0
  58. package/src/policy/prd/prd-io.ts +89 -0
  59. package/src/policy/prd/prd-ownership.ts +99 -0
  60. package/src/policy/prd/prd-paths.ts +88 -0
  61. package/src/policy/prd/prd-schema.ts +151 -0
  62. package/src/runtime/handle-post.ts +2 -0
  63. package/src/runtime/handle-pre.ts +11 -0
  64. package/src/runtime/handle.ts +87 -6
  65. package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +7 -1
  66. package/src/runtime/lifecycle/dispatch.ts +18 -5
  67. package/src/runtime/lifecycle/failure-lesson.ts +6 -2
  68. package/src/runtime/lifecycle/rules-root.ts +18 -2
  69. package/src/runtime/normalize.ts +4 -0
  70. package/src/runtime/prd/index.ts +8 -0
  71. package/src/runtime/prd/prd-bash-targets.ts +199 -0
  72. package/src/runtime/prd/prd-candidate-files.ts +23 -0
  73. package/src/runtime/prd/prd-canon.ts +50 -0
  74. package/src/runtime/prd/prd-identity.ts +32 -0
  75. package/src/runtime/prd/prd-post-check.ts +73 -0
  76. package/src/runtime/prd/prd-pre-gate.ts +172 -0
  77. package/src/runtime/prd/prd-stop-gate.ts +101 -0
  78. package/src/runtime/prd/prd-subagent-context.ts +69 -0
  79. package/src/runtime/prd/prd-subagent-stop.ts +152 -0
  80. package/src/tracking/session-state.ts +46 -0
  81. package/src/tracking/track-diff.ts +3 -0
  82. package/src/tracking/track-journal.ts +10 -1
  83. package/dist/run-DkrzC0gb.mjs +0 -42
@@ -1,7 +1,7 @@
1
1
  import { t as evaluate } from "./evaluate-CMiIqHeH.mjs";
2
2
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
3
3
  import { t as commandToString } from "./command-string-CALMTnwN.mjs";
4
- import { c as readClaudeInput } from "./claude-Ckv2_TgP.mjs";
4
+ import { c as readClaudeInput } from "./claude-D62hkUfS.mjs";
5
5
  //#region src/adapters/hermes/index.ts
6
6
  /**
7
7
  * Hermes Agent adapter (hook-mode). Nous Research's hermes-agent (2026) pipes a
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as PromptKind, r as formatPrompt, t as Prompt } from "./types-Bh0jEF3_.mjs";
2
2
  import { _ as queryHash, a as cacheLookupSubstringMeta, c as mcpCacheKey, d as extractText, f as IndexSummary, g as jaccardSimilar, h as compactMarkdown, i as cacheLookupSubstring, l as mcpCacheWrite, m as summarizeIndex, n as cacheLookup, o as cachePath, p as loadIndex, r as cacheLookupMeta, s as cacheStore, t as CacheHit, u as webfetchCacheWrite } from "./index-DhmV2MPh.mjs";
3
- import { _ as DEFAULT_TTL_SEC, a as ProjectLayout, b as ttlLabel, c as projectLayout, d as MAX_LINES_ENV_KEY, f as hookBudget, g as storeBudget, h as splitTarget, i as parseEnvFile, l as DEFAULT_MAX_LINES, m as resolveStdinMaxBytes, n as envCandidates, o as STATE_GITIGNORE, p as resolveMaxLines, r as loadDotenv, s as STATE_ROOT, t as HOME_DIR, u as DEFAULT_STDIN_MAX_BYTES, v as TTL_ENV_KEY, x as parseEnvInt, y as resolveTtlSec } from "./index-oXXPs2xl.mjs";
4
3
  import { i as HarnessVia, n as HarnessInfo, r as HarnessMode, t as HarnessId } from "./types-DvXTI71X.mjs";
4
+ import { _ as DEFAULT_TTL_SEC, a as ProjectLayout, b as ttlLabel, c as projectLayout, d as MAX_LINES_ENV_KEY, f as hookBudget, g as storeBudget, h as splitTarget, i as parseEnvFile, l as DEFAULT_MAX_LINES, m as resolveStdinMaxBytes, n as envCandidates, o as STATE_GITIGNORE, p as resolveMaxLines, r as loadDotenv, s as STATE_ROOT, t as HOME_DIR, u as DEFAULT_STDIN_MAX_BYTES, v as TTL_ENV_KEY, x as parseEnvInt, y as resolveTtlSec } from "./index-oXXPs2xl.mjs";
5
5
  import { n as detectMode, r as modeFor, t as detectHarness } from "./index-CigBu4K9.mjs";
6
6
  import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "./doc-helpers-BGCzP9VF.mjs";
7
7
  import { t as incrementTrivialEditCounter } from "./index-84heAAm_.mjs";
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { a as resolveMaxLines, c as storeBudget, i as hookBudget, l as parseEnvInt, n as DEFAULT_STDIN_MAX_BYTES, o as resolveStdinMaxBytes, r as MAX_LINES_ENV_KEY, s as splitTarget, t as DEFAULT_MAX_LINES } from "./limits-DG5pl-5s.mjs";
2
- import { c as resolveTtlSec, i as parseEnvFile, l as ttlLabel, n as envCandidates, o as DEFAULT_TTL_SEC, r as loadDotenv, s as TTL_ENV_KEY, t as HOME_DIR } from "./dotenv-BLBkBTww.mjs";
2
+ import { i as ttlLabel, n as TTL_ENV_KEY, r as resolveTtlSec, t as DEFAULT_TTL_SEC } from "./ttl-Dgwg_QAv.mjs";
3
3
  import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "./layout-KWoE_Mqn.mjs";
4
+ import { i as parseEnvFile, n as envCandidates, r as loadDotenv, t as HOME_DIR } from "./dotenv-C1LkcfW-.mjs";
4
5
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
5
6
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
6
7
  import { n as detectMode, r as modeFor, t as detectHarness } from "./harness-BMuLJ9lm.mjs";
@@ -8,7 +9,7 @@ import { $ as isRalphMode, B as protectedPathGuard, C as isInterfacesPath, D as
8
9
  import { d as countFrameworkCodeLines, f as countLines, l as PLUGINS_DIR, p as evaluateFileSize, u as SOLID_REF } from "./home-state-oUGFB4ds.mjs";
9
10
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
10
11
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-PKVNBHge.mjs";
11
- import { A as importsTanstackQuery, B as APEX_GATES, C as detectRequiredSkills, D as frameworkSolidGate, E as SKILL_TRIGGERS, G as evaluateApex, H as PRE_AUTH_GATES, I as MAX_EXA_RESULTS, K as freshnessGate, L as MAX_TOKENS, M as countStores, N as declaresStore, O as declaresCustomHook, R as capVerbosity, S as parseField, T as usesTailwindUtilities, U as brainstormGate, V as POST_AUTH_GATES, W as docConsultedGate, _ as detectClaudeMdProjectType, a as firstHeading, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as buildClaudeMdContext, h as buildApexInstruction, i as firstComment, j as queryCapActive, k as declaresQueryHook, l as PROJECT_INDICATORS, m as DEV_VERBS, n as missingSeoElements, o as parseEnrichment, q as solidReadGate, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as skillTriggerGate, x as parseBodyDesc, z as detectCreationIntent } from "./validate-KjZ1X9tH.mjs";
12
+ import { A as countStores, B as PRE_AUTH_GATES, C as usesTailwindUtilities, D as declaresQueryHook, E as declaresCustomHook, F as MAX_TOKENS, G as solidReadGate, H as docConsultedGate, I as capVerbosity, L as detectCreationIntent, O as importsTanstackQuery, P as MAX_EXA_RESULTS, R as APEX_GATES, S as skillTriggerGate, T as frameworkSolidGate, U as evaluateApex, V as brainstormGate, W as freshnessGate, _ as detectClaudeMdProjectType, a as firstHeading, b as parseField, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as buildClaudeMdContext, h as buildApexInstruction, i as firstComment, j as declaresStore, k as queryCapActive, l as PROJECT_INDICATORS, m as DEV_VERBS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as SKILL_TRIGGERS, x as detectRequiredSkills, y as parseBodyDesc, z as POST_AUTH_GATES } from "./validate-Dcjl0LUS.mjs";
12
13
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
13
14
  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-IhHk2KlT.mjs";
14
15
  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,7 +1,7 @@
1
1
  import { t as evaluate } from "./evaluate-CMiIqHeH.mjs";
2
2
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
3
3
  import { t as commandToString } from "./command-string-CALMTnwN.mjs";
4
- import { c as readClaudeInput } from "./claude-Ckv2_TgP.mjs";
4
+ import { c as readClaudeInput } from "./claude-D62hkUfS.mjs";
5
5
  //#region src/adapters/kimi/index.ts
6
6
  /**
7
7
  * Kimi Code CLI adapter (hook-mode). Moonshot AI's Kimi Code CLI (v0.27.0,
@@ -189,14 +189,32 @@ function contains(root, filePath) {
189
189
  const rel = relative(root, filePath);
190
190
  return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
191
191
  }
192
- /** Select Cursor's project scope without replacing a valid payload cwd. */
193
- function cursorProjectCwd(cwd, workspaceRoots, filePath, fallback) {
192
+ /**
193
+ * Select Cursor's project scope without replacing a valid payload cwd. Order:
194
+ * payload `cwd` -> longest workspace root containing `filePath` ->
195
+ * `workspaceRoots[0]` -> `CURSOR_PROJECT_DIR` env -> `CLAUDE_PROJECT_DIR` env
196
+ * -> `fallback`. Both env vars are validated the same way as any other Cursor
197
+ * path (`cursorAbsolutePath`: absolute, NUL-free, realpath-resolved), so an
198
+ * unset or malformed value is silently skipped rather than trusted.
199
+ * @param cwd - Cursor payload `cwd`, when present.
200
+ * @param workspaceRoots - Validated, deduped Cursor `workspace_roots`.
201
+ * @param filePath - The file the current event targets, when present.
202
+ * @param fallback - Caller-supplied last resort (never `process.cwd()`).
203
+ * @param env - Environment (defaults to `process.env`).
204
+ * @returns The resolved project root.
205
+ */
206
+ function cursorProjectCwd(cwd, workspaceRoots, filePath, fallback, env = process.env) {
194
207
  if (cwd) return cwd;
195
208
  if (filePath) {
196
209
  const matches = workspaceRoots.filter((root) => contains(root, filePath));
197
210
  if (matches.length > 0) return matches.sort((a, b) => b.length - a.length)[0];
198
211
  }
199
- return workspaceRoots[0] ?? fallback;
212
+ if (workspaceRoots[0]) return workspaceRoots[0];
213
+ const fromCursorEnv = cursorAbsolutePath(env.CURSOR_PROJECT_DIR);
214
+ if (fromCursorEnv) return fromCursorEnv;
215
+ const fromClaudeEnv = cursorAbsolutePath(env.CLAUDE_PROJECT_DIR);
216
+ if (fromClaudeEnv) return fromClaudeEnv;
217
+ return fallback;
200
218
  }
201
219
  //#endregion
202
220
  //#region src/adapters/cursor/normalize.ts
@@ -240,10 +258,72 @@ function sanitizedCursorInput(input) {
240
258
  else delete safe.workspace_roots;
241
259
  return safe;
242
260
  }
261
+ /**
262
+ * Closed table: Cursor's `MCP:<tool>` tool_name form on preToolUse/
263
+ * postToolUse/postToolUseFailure LOSES the MCP server name (ground truth:
264
+ * Cursor CLI 3.18.25 + official docs — only beforeMCPExecution/
265
+ * afterMCPExecution carry `mcp_server_name`). This reconstructs the real
266
+ * server for the closed set of tool names this repo's gates actually depend
267
+ * on (GATED_TOOLS in doc-cache-gate.ts, CONTEXT7_SOURCE, RESEARCH_TOOLS,
268
+ * SHOT_TOOLS, gemini-mcp-gate, shadcn-skill-gate) — same closed-table
269
+ * philosophy as `mcp-tool-name.ts`'s Codex aliasing, never a blanket
270
+ * reversal. Coordinator decision: a tool name OUTSIDE this table (server
271
+ * genuinely unrecoverable, and no safe placeholder) is left as Cursor's raw
272
+ * `MCP:<tool>` string — `test/cursor-followup-normalize.test.ts` pins this
273
+ * as the committed contract ("commandless MCP tools keep their name"), so a
274
+ * fabricated `mcp__cursor__<tool>` placeholder is never introduced for the
275
+ * unknown case.
276
+ */
277
+ const CURSOR_MCP_TOOL_SERVERS = Object.assign(Object.create(null), {
278
+ "query-docs": "context7",
279
+ "resolve-library-id": "context7",
280
+ web_search_exa: "exa",
281
+ get_code_context_exa: "exa",
282
+ deep_researcher_start: "exa",
283
+ deep_researcher_check: "exa",
284
+ create_frontend: "gemini-design",
285
+ modify_frontend: "gemini-design",
286
+ snippet_frontend: "gemini-design",
287
+ search_items_in_registries: "shadcn",
288
+ view_items_in_registries: "shadcn",
289
+ get_item_examples_from_registries: "shadcn",
290
+ get_add_command_for_items: "shadcn",
291
+ get_audit_checklist: "shadcn"
292
+ });
293
+ /**
294
+ * The real MCP server for a bare Cursor tool name (the part after `MCP:`),
295
+ * or `undefined` when it isn't in the closed table. fuse-browser is inferred
296
+ * from the `browser_*` prefix — every fuse-browser tool is named that way
297
+ * and no other server in this ecosystem uses it — the remaining,
298
+ * non-distinctive tool names go through {@link CURSOR_MCP_TOOL_SERVERS}.
299
+ * NO placeholder fallback (coordinator decision, see {@link CURSOR_MCP_TOOL_SERVERS}):
300
+ * an unknown tool name means the server is genuinely unrecoverable, so the
301
+ * caller leaves the raw `MCP:<tool>` string untouched instead of fabricating one.
302
+ */
303
+ function cursorMcpServer(bareTool) {
304
+ if (bareTool.startsWith("browser_")) return "fuse-browser";
305
+ return CURSOR_MCP_TOOL_SERVERS[bareTool];
306
+ }
307
+ /**
308
+ * Canonicalize Cursor's `MCP:<tool>` tool_name (preToolUse/postToolUse/
309
+ * postToolUseFailure) into the shared `mcp__<server>__<tool>` shape every
310
+ * other harness/gate expects. Returns `undefined` — meaning "leave the raw
311
+ * `MCP:<tool>` string as-is" — both when `tool` isn't the `MCP:` form and
312
+ * when the bare tool name is outside the closed {@link CURSOR_MCP_TOOL_SERVERS}
313
+ * table (server unrecoverable, no placeholder fabricated).
314
+ */
315
+ function cursorBareMcpToolName(tool) {
316
+ if (!tool || !tool.startsWith("MCP:")) return void 0;
317
+ const bare = tool.slice(4);
318
+ const server = cursorMcpServer(bare);
319
+ return server ? `mcp__${server}__${bare}` : void 0;
320
+ }
243
321
  function cursorToolName(raw, event, tool, hasCommand) {
244
322
  if (hasCommand) return "Bash";
245
323
  const server = str(raw.mcp_server_name)?.trim().replace(/[^A-Za-z0-9_-]+/g, "_");
246
324
  if (/^(before|after)MCPExecution$/i.test(event) && server && tool && !tool.startsWith("mcp__")) return `mcp__${server}__${tool}`;
325
+ const bareMcp = cursorBareMcpToolName(tool);
326
+ if (bareMcp) return bareMcp;
247
327
  if (tool === "Write") return "Edit";
248
328
  return tool ?? "";
249
329
  }
@@ -1,5 +1,5 @@
1
1
  import { $ as isRalphMode, B as protectedPathGuard, C as isInterfacesPath, D as isVendorPath, E as isTypesPath, F as FILE_REDIRECT, H as ASK_PATTERNS, I as SAFE_PREFIXES, J as GIT_ASK, L as SESSION_STATE_FRAGMENT, M as ASK_WRITERS, N as CODE_MUTATORS, O as langOfPath, P as CODE_REDIRECT, Q as SYSTEM_INSTALL, R as PROTECTED_FRAGMENTS, S as isHooksPath, T as isStoresPath, U as CRITICAL_PATTERNS, W as securityGuard, X as PROJECT_INSTALL, Y as GIT_BLOCKED, Z as RALPH_SAFE, a as registerGuard, at as maskCommentsOnly, b as interfaceDeclLevel, c as GO_DECL_RE, ct as detectProjectType, d as PY_MODEL_RE, et as matchPatterns, f as RUST_DECL_RE, h as interfaceSeparationGuard, i as clearUserGuards, it as maskCommentsAndStrings, j as bashWriteGuard, k as lexProfileOf, l as JAVA_DECL_RE, lt as isApexCommand, m as TS_DECL_RE, n as FAIL_CLOSED, o as runGuards, ot as DEV_KEYWORDS, p as SWIFT_PROTO_RE, r as GUARDS, s as installGuard, st as detectModularArchitecture, t as evaluate, tt as detectFramework, u as PHP_DECL_RE, ut as requiredArchSkill, v as declaresExportedTypeAlias, w as isQueryPath, x as isComponentsPath, y as declaresInterface, z as PROTECTED_GIT_RE } from "../evaluate-CMiIqHeH.mjs";
2
2
  import { d as countFrameworkCodeLines, f as countLines, l as PLUGINS_DIR, p as evaluateFileSize, u as SOLID_REF } from "../home-state-oUGFB4ds.mjs";
3
- import { A as importsTanstackQuery, B as APEX_GATES, C as detectRequiredSkills, D as frameworkSolidGate, E as SKILL_TRIGGERS, G as evaluateApex, H as PRE_AUTH_GATES, I as MAX_EXA_RESULTS, K as freshnessGate, L as MAX_TOKENS, M as countStores, N as declaresStore, O as declaresCustomHook, R as capVerbosity, S as parseField, T as usesTailwindUtilities, U as brainstormGate, V as POST_AUTH_GATES, W as docConsultedGate, _ as detectClaudeMdProjectType, a as firstHeading, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as buildClaudeMdContext, h as buildApexInstruction, i as firstComment, j as queryCapActive, k as declaresQueryHook, l as PROJECT_INDICATORS, m as DEV_VERBS, n as missingSeoElements, o as parseEnrichment, q as solidReadGate, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as skillTriggerGate, x as parseBodyDesc, z as detectCreationIntent } from "../validate-KjZ1X9tH.mjs";
3
+ import { A as countStores, B as PRE_AUTH_GATES, C as usesTailwindUtilities, D as declaresQueryHook, E as declaresCustomHook, F as MAX_TOKENS, G as solidReadGate, H as docConsultedGate, I as capVerbosity, L as detectCreationIntent, O as importsTanstackQuery, P as MAX_EXA_RESULTS, R as APEX_GATES, S as skillTriggerGate, T as frameworkSolidGate, U as evaluateApex, V as brainstormGate, W as freshnessGate, _ as detectClaudeMdProjectType, a as firstHeading, b as parseField, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as buildClaudeMdContext, h as buildApexInstruction, i as firstComment, j as declaresStore, k as queryCapActive, l as PROJECT_INDICATORS, m as DEV_VERBS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, w as SKILL_TRIGGERS, x as detectRequiredSkills, y as parseBodyDesc, z as POST_AUTH_GATES } from "../validate-Dcjl0LUS.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, countStores, declaresCustomHook, declaresExportedTypeAlias, declaresInterface, declaresQueryHook, declaresStore, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, importsTanstackQuery, installGuard, interfaceDeclLevel, interfaceSeparationGuard, isApexCommand, isComponentsPath, isHooksPath, isHtmlLike, isInterfacesPath, isQueryPath, isRalphMode, isStoresPath, isTypesPath, isVendorPath, langOfPath, lexProfileOf, loadApexTaskState, maskCommentsAndStrings, maskCommentsOnly, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, queryCapActive, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate, usesTailwindUtilities };
@@ -0,0 +1,494 @@
1
+ import { r as loadDotenv, t as HOME_DIR } from "./dotenv-C1LkcfW-.mjs";
2
+ import { t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
3
+ import { t as evaluate } from "./evaluate-CMiIqHeH.mjs";
4
+ import { r as harnessHomeSegment } from "./apex-target-Xc2M32Pl.mjs";
5
+ import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
6
+ import { C as withRouterStatus, D as prdDir, O as prdRouterPath, S as subTasksOf, c as crossCheckTask, d as readAgentReport, g as readTaskFile, m as readRouter, o as isPrdFlagSet, p as readAllTaskFiles, t as acquireLock, u as incompleteSubTasks, v as writeRouter, w as withSubTaskValidated, x as isCompacted, y as writeTaskFile } from "./apex-task-store-Cb65rE5i.mjs";
7
+ import { join } from "node:path";
8
+ import { existsSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { execFileSync } from "node:child_process";
11
+ //#region src/cli/run.ts
12
+ /** Staged files (Added/Copied/Modified/Renamed). Uses `node:child_process` (Bun shell can hang on `git show`). */
13
+ function stagedFiles() {
14
+ return execFileSync("git", [
15
+ "diff",
16
+ "--cached",
17
+ "--name-only",
18
+ "--diff-filter=ACMR"
19
+ ], { encoding: "utf8" }).trim().split("\n").filter(Boolean);
20
+ }
21
+ /**
22
+ * Read a file's staged (index) content — not the working-tree version.
23
+ * Uses `execFileSync` with an argv array (never a shell), so a staged
24
+ * filename containing shell metacharacters (backticks, `$(...)`, quotes)
25
+ * reaches `git show` as a single literal argument — see issue #87.
26
+ */
27
+ function stagedContent(path) {
28
+ return execFileSync("git", ["show", `:${path}`], { encoding: "utf8" });
29
+ }
30
+ /**
31
+ * Evaluate staged code files against the policy core; return violation blocks.
32
+ * `read` is injected (the real impl is {@link stagedContent}) so this is pure + testable.
33
+ */
34
+ function checkStaged(files, read) {
35
+ const violations = [];
36
+ for (const file of files) {
37
+ if (!isCodeFile(file)) continue;
38
+ const r = evaluate({
39
+ tool: "Write",
40
+ filePath: file,
41
+ content: read(file)
42
+ });
43
+ if (r.decision === "deny" && r.prompt) violations.push(`${file}\n${formatPrompt(r.prompt)}`);
44
+ }
45
+ return violations;
46
+ }
47
+ //#endregion
48
+ //#region src/cli/prd/resolve.ts
49
+ /**
50
+ * Argument parsing + harness resolution for `harness prd <cmd>`. Resolves
51
+ * `--root` (default `cwd`) and `--id`/`homeSeg` (default: the sole
52
+ * `HOME_DIR` (`config/dotenv.ts`) segment under which
53
+ * `<root>/<seg>/apex/prd.json` exists — ambiguous or absent is a caller
54
+ * error, never guessed).
55
+ */
56
+ const VALUE_FLAGS = /* @__PURE__ */ new Set(["--id", "--root"]);
57
+ const BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["--json"]);
58
+ /** Read `--<name> <value>` from argv, or `undefined` when absent. */
59
+ function readFlag(argv, name) {
60
+ const idx = argv.indexOf(name);
61
+ return idx !== -1 ? argv[idx + 1] : void 0;
62
+ }
63
+ /** True when `--json` is present in argv. */
64
+ function hasJsonFlag(argv) {
65
+ return argv.includes("--json");
66
+ }
67
+ /** Non-flag tokens in argv, skipping known flags and their values. */
68
+ function positionalArgs(argv) {
69
+ const out = [];
70
+ for (let i = 0; i < argv.length; i++) {
71
+ const tok = argv[i];
72
+ if (tok === void 0) continue;
73
+ if (VALUE_FLAGS.has(tok)) {
74
+ i++;
75
+ continue;
76
+ }
77
+ if (BOOLEAN_FLAGS.has(tok)) continue;
78
+ out.push(tok);
79
+ }
80
+ return out;
81
+ }
82
+ /**
83
+ * Resolve `{root, homeSeg, id}` for a `prd` sub-command. `--id` wins
84
+ * outright; otherwise auto-detects the sole `HOME_DIR` segment under which
85
+ * `<root>/<seg>/apex/prd.json` exists.
86
+ */
87
+ function resolvePrdArgs(argv, cwd) {
88
+ const root = readFlag(argv, "--root") ?? cwd;
89
+ const idFlag = readFlag(argv, "--id");
90
+ if (idFlag) return {
91
+ ok: true,
92
+ value: {
93
+ root,
94
+ homeSeg: harnessHomeSegment(idFlag),
95
+ id: idFlag
96
+ }
97
+ };
98
+ const matches = [];
99
+ for (const [id, seg] of Object.entries(HOME_DIR)) if (existsSync(join(root, seg, "apex", "prd.json"))) matches.push({
100
+ id,
101
+ seg
102
+ });
103
+ if (matches.length === 0) return {
104
+ ok: false,
105
+ message: `no PRD router found under ${root}/<home>/apex/prd.json — pass --id <harness>`
106
+ };
107
+ if (matches.length > 1) return {
108
+ ok: false,
109
+ message: `ambiguous harness: PRD routers found for ${matches.map((m) => m.id).join(", ")} — pass --id`
110
+ };
111
+ const only = matches[0];
112
+ return {
113
+ ok: true,
114
+ value: {
115
+ root,
116
+ homeSeg: only.seg,
117
+ id: only.id
118
+ }
119
+ };
120
+ }
121
+ /**
122
+ * Shared first step of every `prd` sub-command: {@link resolvePrdArgs}, then
123
+ * (on success) `loadDotenv` the resolved harness's home `.env` + `<root>/.env`
124
+ * as a side effect on `env` — same env-loading contract as the `hook` branch
125
+ * (`bin.ts`'s `loadDotenv(id)` call).
126
+ */
127
+ function resolveAndLoadEnv(argv, cwd, env = process.env) {
128
+ const resolved = resolvePrdArgs(argv, cwd);
129
+ if (resolved.ok) loadDotenv(resolved.value.id, env, homedir(), resolved.value.root);
130
+ return resolved;
131
+ }
132
+ //#endregion
133
+ //#region src/cli/prd/format.ts
134
+ /**
135
+ * Rendering helpers for `harness prd` output: an aligned ASCII table for the
136
+ * human-readable path, and pretty JSON for `--json`.
137
+ */
138
+ /** Pad `s` with trailing spaces to `width`. */
139
+ function pad(s, width) {
140
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
141
+ }
142
+ /** Render an aligned ASCII table from a header row + data rows. */
143
+ function renderTable(header, rows) {
144
+ const all = [header, ...rows];
145
+ const widths = header.map((_, col) => Math.max(...all.map((r) => (r[col] ?? "").length)));
146
+ return all.map((r) => r.map((cell, col) => pad(cell ?? "", widths[col] ?? 0)).join(" ").trimEnd()).join("\n");
147
+ }
148
+ /** Render `data` as pretty-printed JSON with a trailing newline. */
149
+ function renderJson(data) {
150
+ return JSON.stringify(data, null, 2) + "\n";
151
+ }
152
+ //#endregion
153
+ //#region src/cli/prd/shared.ts
154
+ /**
155
+ * Shared plumbing for `status`/`validate`/`compact`: root+env resolution,
156
+ * the `FUSE_PRD` write gate, task-PRD lookup, and the `.lock`-guarded write.
157
+ * All return a result instead of calling `process.exit` themselves —
158
+ * callers own the exit code (extracted to kill a jscpd-measured clone
159
+ * across the three `run*` functions).
160
+ */
161
+ /** {@link resolveAndLoadEnv}, narrowed to `{root, homeSeg}` — the common first step of every sub-command. */
162
+ function resolveRoot(argv, cwd, env) {
163
+ const resolved = resolveAndLoadEnv(argv, cwd, env);
164
+ if (!resolved.ok) return {
165
+ ok: false,
166
+ code: 2,
167
+ message: resolved.message
168
+ };
169
+ return {
170
+ ok: true,
171
+ root: resolved.value.root,
172
+ homeSeg: resolved.value.homeSeg
173
+ };
174
+ }
175
+ /** {@link resolveRoot} + the `FUSE_PRD=1` write gate shared by `validate`/`compact`. */
176
+ function requireFusePrd(argv, cwd, env, sub) {
177
+ const resolved = resolveRoot(argv, cwd, env);
178
+ if (!resolved.ok) return resolved;
179
+ if (!isPrdFlagSet(env)) return {
180
+ ok: false,
181
+ code: 1,
182
+ message: `prd ${sub} requires FUSE_PRD=1 in ${resolved.homeSeg}/.env or the project .env`
183
+ };
184
+ return resolved;
185
+ }
186
+ /** Reads the router + the named task's task-PRD file. Never calls `process.exit`. */
187
+ async function loadTaskFileFor(root, homeSeg, task) {
188
+ const router = await readRouter(root, homeSeg);
189
+ const routerEntry = router?.[task];
190
+ if (!router || !routerEntry) return {
191
+ ok: false,
192
+ code: 2,
193
+ message: `no such task "${task}" in PRD router`
194
+ };
195
+ const taskFile = await readTaskFile(root, homeSeg, routerEntry.prd);
196
+ if (!taskFile) return {
197
+ ok: false,
198
+ code: 2,
199
+ message: `task PRD not found or malformed: ${routerEntry.prd}`
200
+ };
201
+ return {
202
+ ok: true,
203
+ router,
204
+ routerEntry,
205
+ taskFile
206
+ };
207
+ }
208
+ /** Extracts `<task> [agent]` from argv, then {@link loadTaskFileFor} — the shared `validate`/`compact` entry lookup. */
209
+ async function resolveTaskFile(argv, root, homeSeg, usage) {
210
+ const [task, agentArg] = positionalArgs(argv);
211
+ if (!task) return {
212
+ ok: false,
213
+ code: 2,
214
+ message: usage
215
+ };
216
+ const lookup = await loadTaskFileFor(root, homeSeg, task);
217
+ if (!lookup.ok) return lookup;
218
+ return {
219
+ task,
220
+ agentArg,
221
+ ...lookup
222
+ };
223
+ }
224
+ /** Runs `fn` under the PRD `.lock` directory-lock. Never calls `process.exit`. */
225
+ async function withPrdLock(root, homeSeg, fn) {
226
+ const lockDir = join(prdDir(root, homeSeg), ".lock");
227
+ const release = await acquireLock(lockDir);
228
+ if (!release) return {
229
+ ok: false,
230
+ message: `lock held at ${lockDir}`
231
+ };
232
+ try {
233
+ return {
234
+ ok: true,
235
+ value: await fn()
236
+ };
237
+ } finally {
238
+ await release();
239
+ }
240
+ }
241
+ //#endregion
242
+ //#region src/cli/prd/status.ts
243
+ /**
244
+ * `harness prd status [--json] [--id <harness>] [--root <dir>]` — read-only
245
+ * PRD snapshot. Never requires `FUSE_PRD`. Exit 0 on success, exit 1 when no
246
+ * router is found, exit 2 on an unresolved/ambiguous `--id`.
247
+ */
248
+ const HEADER = [
249
+ "Task",
250
+ "Router status",
251
+ "Agents",
252
+ "Sub-tasks done/total",
253
+ "Violations"
254
+ ];
255
+ /**
256
+ * Sub-task readiness counts for the "done/total" column: a sub-task counts as
257
+ * done once it is `validated` OR its owning agent's own report already shows
258
+ * `"done"` for it (readiness view — a report can land well before the
259
+ * coordinator runs `prd validate`). A compacted (fully-validated) entry
260
+ * counts as one done/one total unit — its per-sub-task detail is gone.
261
+ */
262
+ function subTaskCounts(taskFile, reports, task) {
263
+ let done = 0;
264
+ let total = 0;
265
+ for (const [agent, entry] of Object.entries(taskFile)) {
266
+ if (isCompacted(entry)) {
267
+ done++;
268
+ total++;
269
+ continue;
270
+ }
271
+ const report = reports[agent];
272
+ for (const [sub, subTask] of Object.entries(subTasksOf(entry))) {
273
+ total++;
274
+ if (subTask.status === "validated" || report?.[task]?.[sub]?.status === "done") done++;
275
+ }
276
+ }
277
+ return {
278
+ done,
279
+ total
280
+ };
281
+ }
282
+ /**
283
+ * Run `harness prd status`. Prints an aligned table (or `--json` dump of
284
+ * `{router, taskFiles, reports}`) and returns the process exit code.
285
+ */
286
+ async function runPrdStatus(argv, cwd, env = process.env) {
287
+ const resolved = resolveRoot(argv, cwd, env);
288
+ if (!resolved.ok) {
289
+ process.stderr.write(resolved.message + "\n");
290
+ return resolved.code;
291
+ }
292
+ const { root, homeSeg } = resolved;
293
+ const router = await readRouter(root, homeSeg);
294
+ if (!router) {
295
+ process.stderr.write(`no PRD router at ${prdRouterPath(root, homeSeg)}\n`);
296
+ return 1;
297
+ }
298
+ const taskFiles = await readAllTaskFiles(root, homeSeg, router);
299
+ const agentNames = /* @__PURE__ */ new Set();
300
+ for (const tf of Object.values(taskFiles)) if (tf) for (const agent of Object.keys(tf)) agentNames.add(agent);
301
+ const reports = {};
302
+ for (const agent of agentNames) reports[agent] = await readAgentReport(root, homeSeg, agent);
303
+ if (hasJsonFlag(argv)) {
304
+ process.stdout.write(renderJson({
305
+ router,
306
+ taskFiles,
307
+ reports
308
+ }));
309
+ return 0;
310
+ }
311
+ const rows = [];
312
+ for (const [task, entry] of Object.entries(router)) {
313
+ const tf = taskFiles[task];
314
+ const agentCount = tf ? Object.keys(tf).length : 0;
315
+ const { done, total } = tf ? subTaskCounts(tf, reports, task) : {
316
+ done: 0,
317
+ total: 0
318
+ };
319
+ const violations = tf ? crossCheckTask(tf, reports, task) : [];
320
+ rows.push([
321
+ task,
322
+ entry.status,
323
+ String(agentCount),
324
+ `${done}/${total}`,
325
+ String(violations.length)
326
+ ]);
327
+ }
328
+ process.stdout.write(renderTable(HEADER, rows) + "\n");
329
+ return 0;
330
+ }
331
+ //#endregion
332
+ //#region src/cli/prd/validate.ts
333
+ /**
334
+ * `harness prd validate <task> [agent] [--id] [--root]` — cross-checks a
335
+ * task-PRD against the named agent(s)' own report(s); on success flips the
336
+ * matching sub-tasks to `validated` and promotes the router entry when every
337
+ * agent is fully validated. Requires `FUSE_PRD=1`.
338
+ */
339
+ function allValidated(taskFile) {
340
+ return Object.values(taskFile).every((entry) => isCompacted(entry) || Object.values(subTasksOf(entry)).every((s) => s.status === "validated"));
341
+ }
342
+ /**
343
+ * Sub-tasks of `agents` with no matching `done` entry in their owning
344
+ * agent's own report — the readiness gate `validate` enforces before
345
+ * flipping anything. Reuses `incompleteSubTasks` (distinct from
346
+ * `crossCheckTask`, which only flags an ALREADY-`validated` sub-task lacking
347
+ * a report; here nothing has been validated yet).
348
+ */
349
+ function findUnreadySubTasks(taskFile, reports, task, agents) {
350
+ const out = [];
351
+ for (const agent of agents) {
352
+ const missing = incompleteSubTasks(taskFile, agent, task, reports[agent] ?? null);
353
+ for (const sub of missing) out.push({
354
+ task,
355
+ agent,
356
+ sub,
357
+ reason: `no "done" report from "${agent}" for sub-task "${sub}"`
358
+ });
359
+ }
360
+ return out;
361
+ }
362
+ /**
363
+ * Run `harness prd validate`. Exit 0 on success, exit 1 on cross-check
364
+ * violations or a missing `FUSE_PRD=1`/held lock, exit 2 on usage/lookup errors.
365
+ */
366
+ async function runPrdValidate(argv, cwd, env = process.env) {
367
+ const resolved = requireFusePrd(argv, cwd, env, "validate");
368
+ if (!resolved.ok) {
369
+ process.stderr.write(resolved.message + "\n");
370
+ return resolved.code;
371
+ }
372
+ const { root, homeSeg } = resolved;
373
+ const tf = await resolveTaskFile(argv, root, homeSeg, "usage: harness prd validate <task> [agent]");
374
+ if (!tf.ok) {
375
+ process.stderr.write(tf.message + "\n");
376
+ return tf.code;
377
+ }
378
+ const { task, agentArg, router, routerEntry, taskFile } = tf;
379
+ if (agentArg && !(agentArg in taskFile)) {
380
+ process.stderr.write(`no such agent "${agentArg}" in task PRD for "${task}"\n`);
381
+ return 2;
382
+ }
383
+ const agents = agentArg ? [agentArg] : Object.keys(taskFile);
384
+ const reports = {};
385
+ for (const agent of Object.keys(taskFile)) reports[agent] = await readAgentReport(root, homeSeg, agent);
386
+ const violations = findUnreadySubTasks(taskFile, reports, task, agents);
387
+ if (violations.length > 0) {
388
+ process.stderr.write(`prd validate: ${violations.length} violation(s)\n`);
389
+ for (const v of violations) process.stderr.write(` - ${v.task}/${v.agent}/${v.sub}: ${v.reason}\n`);
390
+ return 1;
391
+ }
392
+ const result = await withPrdLock(root, homeSeg, async () => {
393
+ const now = (/* @__PURE__ */ new Date()).toISOString();
394
+ let updated = taskFile;
395
+ for (const agent of agents) {
396
+ const entry = updated[agent];
397
+ if (!entry || isCompacted(entry)) continue;
398
+ for (const sub of Object.keys(subTasksOf(entry))) updated = withSubTaskValidated(updated, agent, sub, now);
399
+ }
400
+ await writeTaskFile(root, homeSeg, routerEntry.prd, updated);
401
+ if (allValidated(updated)) await writeRouter(root, homeSeg, withRouterStatus(router, task, "validated", now));
402
+ });
403
+ if (!result.ok) {
404
+ process.stderr.write(`prd validate: ${result.message}\n`);
405
+ return 1;
406
+ }
407
+ return 0;
408
+ }
409
+ //#endregion
410
+ //#region src/policy/prd/prd-compact.ts
411
+ /** Collapses fully-validated task-PRD agent entries to their compact shape. Pure — no fs. */
412
+ /** Collapses one agent entry to the compacted shape ONLY when every sub-task is `validated`; else returns it unchanged. */
413
+ function compactAgentEntry(e, at) {
414
+ if (isCompacted(e)) return e;
415
+ const subTasks = Object.values(subTasksOf(e));
416
+ if (subTasks.length === 0 || !subTasks.every((s) => s.status === "validated")) return e;
417
+ return {
418
+ status: "validated",
419
+ files: e.files,
420
+ "validated-at": at
421
+ };
422
+ }
423
+ /** Compacts every eligible agent entry in a task-PRD; reports which agent names got collapsed. */
424
+ function compactTaskFile(taskFile, at) {
425
+ const compacted = [];
426
+ const file = {};
427
+ for (const [agent, entry] of Object.entries(taskFile)) {
428
+ const next = compactAgentEntry(entry, at);
429
+ if (next !== entry) compacted.push(agent);
430
+ file[agent] = next;
431
+ }
432
+ return {
433
+ file,
434
+ compacted
435
+ };
436
+ }
437
+ //#endregion
438
+ //#region src/cli/prd/compact.ts
439
+ /**
440
+ * `harness prd compact <task> [--id] [--root]` — collapses every fully
441
+ * validated agent entry of a task-PRD to its compacted shape. Requires
442
+ * `FUSE_PRD=1`; refuses (exit 1) when any sub-task is not yet `validated`.
443
+ */
444
+ /**
445
+ * Run `harness prd compact`. Exit 0 on success (including a no-op compact),
446
+ * exit 1 when `FUSE_PRD` is unset, a sub-task is unvalidated, or the lock is
447
+ * held, exit 2 on usage/lookup errors.
448
+ */
449
+ async function runPrdCompact(argv, cwd, env = process.env) {
450
+ const resolved = requireFusePrd(argv, cwd, env, "compact");
451
+ if (!resolved.ok) {
452
+ process.stderr.write(resolved.message + "\n");
453
+ return resolved.code;
454
+ }
455
+ const { root, homeSeg } = resolved;
456
+ const tf = await resolveTaskFile(argv, root, homeSeg, "usage: harness prd compact <task>");
457
+ if (!tf.ok) {
458
+ process.stderr.write(tf.message + "\n");
459
+ return tf.code;
460
+ }
461
+ const { routerEntry, taskFile } = tf;
462
+ for (const [agent, entry] of Object.entries(taskFile)) {
463
+ if (isCompacted(entry)) continue;
464
+ for (const [sub, status] of Object.entries(subTasksOf(entry))) if (status.status !== "validated") {
465
+ process.stderr.write(`prd compact: sub-task "${sub}" of agent "${agent}" is not validated\n`);
466
+ return 1;
467
+ }
468
+ }
469
+ const result = await withPrdLock(root, homeSeg, async () => {
470
+ const now = (/* @__PURE__ */ new Date()).toISOString();
471
+ const { file, compacted } = compactTaskFile(taskFile, now);
472
+ await writeTaskFile(root, homeSeg, routerEntry.prd, file);
473
+ return compacted;
474
+ });
475
+ if (!result.ok) {
476
+ process.stderr.write(`prd compact: ${result.message}\n`);
477
+ return 1;
478
+ }
479
+ process.stdout.write(result.value.length > 0 ? `compacted: ${result.value.join(", ")}\n` : "nothing to compact\n");
480
+ return 0;
481
+ }
482
+ //#endregion
483
+ //#region src/cli/prd/index.ts
484
+ /** Dispatch `harness prd <sub> ...rest` to the matching `run*` function. */
485
+ async function runPrd(argv, cwd, env = process.env) {
486
+ const [sub, ...rest] = argv;
487
+ if (sub === "status") return runPrdStatus(rest, cwd, env);
488
+ if (sub === "validate") return runPrdValidate(rest, cwd, env);
489
+ if (sub === "compact") return runPrdCompact(rest, cwd, env);
490
+ process.stderr.write(`harness prd: unknown sub-command "${sub ?? ""}" (expected status|validate|compact)\n`);
491
+ return 2;
492
+ }
493
+ //#endregion
494
+ export { renderJson as a, positionalArgs as c, resolvePrdArgs as d, checkStaged as f, runPrdStatus as i, readFlag as l, stagedFiles as m, runPrdCompact as n, renderTable as o, stagedContent as p, runPrdValidate as r, hasJsonFlag as s, runPrd as t, resolveAndLoadEnv as u };