@fusengine/harness 0.1.92 → 0.1.94

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 (68) 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/hermes/index.mjs +1 -1
  5. package/dist/adapters/kimi/index.mjs +1 -1
  6. package/dist/apex-target-Xc2M32Pl.mjs +48 -0
  7. package/dist/{claude-Ckv2_TgP.mjs → claude-D62hkUfS.mjs} +2 -84
  8. package/dist/cli/bin.mjs +6 -4
  9. package/dist/cli/index.d.mts +69 -1
  10. package/dist/cli/index.mjs +2 -2
  11. package/dist/config/index.mjs +2 -1
  12. package/dist/{dotenv-BLBkBTww.mjs → dotenv-C1LkcfW-.mjs} +1 -26
  13. package/dist/{handle-BF1dZFjY.mjs → handle-CDgBbPRz.mjs} +4648 -3699
  14. package/dist/{hermes-B9-p_3IF.mjs → hermes-ByopGx6C.mjs} +1 -1
  15. package/dist/index.d.mts +1 -1
  16. package/dist/index.mjs +3 -2
  17. package/dist/{kimi-G2wcSh5-.mjs → kimi-C-Oia9q-.mjs} +1 -1
  18. package/dist/policy/index.mjs +1 -1
  19. package/dist/prd-CixxWJIR.mjs +466 -0
  20. package/dist/prd-compact-BE45t8UR.mjs +499 -0
  21. package/dist/runtime/index.d.mts +3 -1
  22. package/dist/runtime/index.mjs +1 -1
  23. package/dist/runtime-io-DuumUeE6.mjs +84 -0
  24. package/dist/{session-state-D5gLr66m.d.mts → session-state-COg7Ej_2.d.mts} +32 -1
  25. package/dist/{skill-path-DVML3zfp.mjs → skill-path-Cz8WFaGu.mjs} +1 -1
  26. package/dist/{store-5-ZPKb0u.mjs → store-BVY6gIYM.mjs} +75 -3
  27. package/dist/tracking/index.d.mts +2 -2
  28. package/dist/tracking/index.mjs +2 -2
  29. package/dist/ttl-Dgwg_QAv.mjs +26 -0
  30. package/dist/{validate-KjZ1X9tH.mjs → validate-Dcjl0LUS.mjs} +4 -49
  31. package/package.json +1 -1
  32. package/src/cli/bin.ts +3 -0
  33. package/src/cli/index.ts +1 -0
  34. package/src/cli/prd/compact.ts +53 -0
  35. package/src/cli/prd/format.ts +23 -0
  36. package/src/cli/prd/index.ts +23 -0
  37. package/src/cli/prd/resolve.ts +88 -0
  38. package/src/cli/prd/shared.ts +80 -0
  39. package/src/cli/prd/status.ts +87 -0
  40. package/src/cli/prd/validate.ts +90 -0
  41. package/src/policy/prd/index.ts +36 -0
  42. package/src/policy/prd/interfaces/types.ts +103 -0
  43. package/src/policy/prd/prd-compact.ts +28 -0
  44. package/src/policy/prd/prd-context.ts +131 -0
  45. package/src/policy/prd/prd-crosscheck.ts +76 -0
  46. package/src/policy/prd/prd-enabled.ts +39 -0
  47. package/src/policy/prd/prd-io.ts +89 -0
  48. package/src/policy/prd/prd-ownership.ts +99 -0
  49. package/src/policy/prd/prd-paths.ts +88 -0
  50. package/src/policy/prd/prd-schema.ts +151 -0
  51. package/src/runtime/handle-post.ts +2 -0
  52. package/src/runtime/handle-pre.ts +11 -0
  53. package/src/runtime/lifecycle/dispatch.ts +18 -5
  54. package/src/runtime/normalize.ts +4 -0
  55. package/src/runtime/prd/index.ts +8 -0
  56. package/src/runtime/prd/prd-bash-targets.ts +199 -0
  57. package/src/runtime/prd/prd-candidate-files.ts +23 -0
  58. package/src/runtime/prd/prd-canon.ts +50 -0
  59. package/src/runtime/prd/prd-identity.ts +32 -0
  60. package/src/runtime/prd/prd-post-check.ts +73 -0
  61. package/src/runtime/prd/prd-pre-gate.ts +172 -0
  62. package/src/runtime/prd/prd-stop-gate.ts +167 -0
  63. package/src/runtime/prd/prd-subagent-context.ts +69 -0
  64. package/src/runtime/prd/prd-subagent-stop.ts +152 -0
  65. package/src/tracking/session-state.ts +46 -0
  66. package/src/tracking/track-diff.ts +3 -0
  67. package/src/tracking/track-journal.ts +10 -1
  68. 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,
@@ -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,466 @@
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 { A as prdRouterPath, C as isCompacted, E as withSubTaskValidated, T as withRouterStatus, b as writeRouter, c as isPrdFlagSet, f as incompleteSubTasks, g as readRouter, h as readAllTaskFiles, k as prdDir, n as compactTaskFile, p as readAgentReport, r as acquireLock, u as crossCheckTask, v as readTaskFile, w as subTasksOf, x as writeTaskFile } from "./prd-compact-BE45t8UR.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/cli/prd/compact.ts
411
+ /**
412
+ * `harness prd compact <task> [--id] [--root]` — collapses every fully
413
+ * validated agent entry of a task-PRD to its compacted shape. Requires
414
+ * `FUSE_PRD=1`; refuses (exit 1) when any sub-task is not yet `validated`.
415
+ */
416
+ /**
417
+ * Run `harness prd compact`. Exit 0 on success (including a no-op compact),
418
+ * exit 1 when `FUSE_PRD` is unset, a sub-task is unvalidated, or the lock is
419
+ * held, exit 2 on usage/lookup errors.
420
+ */
421
+ async function runPrdCompact(argv, cwd, env = process.env) {
422
+ const resolved = requireFusePrd(argv, cwd, env, "compact");
423
+ if (!resolved.ok) {
424
+ process.stderr.write(resolved.message + "\n");
425
+ return resolved.code;
426
+ }
427
+ const { root, homeSeg } = resolved;
428
+ const tf = await resolveTaskFile(argv, root, homeSeg, "usage: harness prd compact <task>");
429
+ if (!tf.ok) {
430
+ process.stderr.write(tf.message + "\n");
431
+ return tf.code;
432
+ }
433
+ const { routerEntry, taskFile } = tf;
434
+ for (const [agent, entry] of Object.entries(taskFile)) {
435
+ if (isCompacted(entry)) continue;
436
+ for (const [sub, status] of Object.entries(subTasksOf(entry))) if (status.status !== "validated") {
437
+ process.stderr.write(`prd compact: sub-task "${sub}" of agent "${agent}" is not validated\n`);
438
+ return 1;
439
+ }
440
+ }
441
+ const result = await withPrdLock(root, homeSeg, async () => {
442
+ const now = (/* @__PURE__ */ new Date()).toISOString();
443
+ const { file, compacted } = compactTaskFile(taskFile, now);
444
+ await writeTaskFile(root, homeSeg, routerEntry.prd, file);
445
+ return compacted;
446
+ });
447
+ if (!result.ok) {
448
+ process.stderr.write(`prd compact: ${result.message}\n`);
449
+ return 1;
450
+ }
451
+ process.stdout.write(result.value.length > 0 ? `compacted: ${result.value.join(", ")}\n` : "nothing to compact\n");
452
+ return 0;
453
+ }
454
+ //#endregion
455
+ //#region src/cli/prd/index.ts
456
+ /** Dispatch `harness prd <sub> ...rest` to the matching `run*` function. */
457
+ async function runPrd(argv, cwd, env = process.env) {
458
+ const [sub, ...rest] = argv;
459
+ if (sub === "status") return runPrdStatus(rest, cwd, env);
460
+ if (sub === "validate") return runPrdValidate(rest, cwd, env);
461
+ if (sub === "compact") return runPrdCompact(rest, cwd, env);
462
+ process.stderr.write(`harness prd: unknown sub-command "${sub ?? ""}" (expected status|validate|compact)\n`);
463
+ return 2;
464
+ }
465
+ //#endregion
466
+ 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 };