@infinitedusky/indusk-mcp 1.57.0 → 1.57.2

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 (29) hide show
  1. package/admin/.next/BUILD_ID +1 -1
  2. package/admin/.next/build-manifest.json +3 -3
  3. package/admin/.next/fallback-build-manifest.json +3 -3
  4. package/admin/.next/server/app/_global-error.html +1 -1
  5. package/admin/.next/server/app/_global-error.rsc +1 -1
  6. package/admin/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/admin/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/admin/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/admin/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/admin/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/admin/.next/server/chunks/ssr/[root-of-the-server]__0h32kmi._.js.map +1 -1
  12. package/admin/.next/server/chunks/ssr/[root-of-the-server]__0wm9s16._.js.map +1 -1
  13. package/admin/.next/server/middleware-build-manifest.js +3 -3
  14. package/admin/.next/server/pages/500.html +1 -1
  15. package/admin/.next/trace +1 -1
  16. package/admin/.next/trace-build +1 -1
  17. package/dist/bin/commands/promises.js +8 -20
  18. package/dist/lib/eval/scorecard-extractor.d.ts +4 -3
  19. package/dist/lib/eval/scorecard-extractor.js +19 -16
  20. package/dist/lib/promises/incidents.d.ts +3 -1
  21. package/dist/lib/promises/incidents.js +15 -5
  22. package/dist/lib/promises/reopen.d.ts +21 -1
  23. package/dist/lib/promises/reopen.js +22 -3
  24. package/dist/lib/promises/watch.d.ts +30 -5
  25. package/dist/lib/promises/watch.js +95 -18
  26. package/package.json +3 -2
  27. /package/admin/.next/static/{cfBb4Ykd8a8KEE1Zj6ihm → J9OMtrT45NqCFCk5PfvFj}/_buildManifest.js +0 -0
  28. /package/admin/.next/static/{cfBb4Ykd8a8KEE1Zj6ihm → J9OMtrT45NqCFCk5PfvFj}/_clientMiddlewareManifest.js +0 -0
  29. /package/admin/.next/static/{cfBb4Ykd8a8KEE1Zj6ihm → J9OMtrT45NqCFCk5PfvFj}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../apps/indusk-mcp/dist/lib/worktree/repos.js","../../../../../../apps/indusk-admin/src/components/ui/Badge.tsx","../../../../../../apps/indusk-mcp/dist/lib/falsification/log.js","../../../../../../apps/indusk-mcp/dist/lib/falsification/skip.js","../../../../../../apps/indusk-mcp/dist/lib/impl-headings.js","../../../../../../apps/indusk-mcp/dist/lib/workflow-types.js","../../../../../../apps/indusk-mcp/dist/lib/lifecycle.js","../../../../../../apps/indusk-mcp/dist/lib/cleanup/gate.js","../../../../../../apps/indusk-mcp/dist/lib/trajectory/audit.js","../../../../../../apps/indusk-mcp/dist/lib/trajectory/parser.js","../../../../../../apps/indusk-mcp/dist/lib/impl-parser-core.js","../../../../../../apps/indusk-mcp/dist/lib/plan-parser.js","../../../../../../apps/indusk-mcp/dist/lib/papers/summary.js","../../../../../../apps/indusk-mcp/dist/lib/papers/provenance.js","../../../../../../apps/indusk-mcp/dist/lib/promises/after-close.js","../../../../../../apps/indusk-mcp/dist/lib/promises/incidents.js","../../../../../../apps/indusk-mcp/dist/lib/promises/reopen.js","../../../../../../apps/indusk-mcp/dist/lib/git.js","../../../../../../apps/indusk-mcp/dist/lib/agents/lock.js","../../../../../../apps/indusk-mcp/dist/lib/worktree/plan-worktree-record.js","../../../../../../apps/indusk-admin/src/lib/planning-reader.ts","../../../../../../apps/indusk-mcp/dist/lib/worktree/plan-worktrees.js"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { isCleanSegment, isUsableRelPath } from \"../path-segment.js\";\nfunction readRawConfig(root) {\n const configPath = join(root, \".indusk\", \"config.json\");\n if (!existsSync(configPath))\n return null;\n try {\n return JSON.parse(readFileSync(configPath, \"utf-8\"));\n }\n catch {\n return null;\n }\n}\n/**\n * Read the declared repo set, newest shape first, singular reduced into it.\n *\n * **The reduction IS the backward-compatibility guarantee**, not a claim about\n * one: `wrapped_repo: \"numero\"` reads as `[{ name: \"numero\" }]`, so every\n * workbench written before this plan keeps working with no config edit and no\n * migration step. The same shape as `phaseOrdinal` reducing to the phase\n * number when a document has no test phase.\n *\n * Names are boundary values — sanitized before any caller can join them into a\n * path, and first-occurrence-deduped so declared order survives. A malformed\n * declaration degrades to fewer repos, never to a traversal.\n *\n * Returns `[]` for a project that declares nothing, which callers read as\n * \"not a workbench\" exactly as they read a missing `wrapped_repo` today.\n */\nexport function readWorkbenchRepos(root) {\n const worktree = readRawConfig(root)?.worktree;\n if (!worktree)\n return [];\n const declared = Array.isArray(worktree.repos)\n ? worktree.repos\n : typeof worktree.wrapped_repo === \"string\"\n ? [{ name: worktree.wrapped_repo }]\n : [];\n const seen = new Set();\n const repos = [];\n for (const entry of declared) {\n if (typeof entry !== \"object\" || entry === null)\n continue;\n const { name, remote } = entry;\n if (typeof name !== \"string\" || !isCleanSegment(name))\n continue;\n if (seen.has(name))\n continue;\n seen.add(name);\n // `path` and `worktrees` are joined into filesystem paths, so they are\n // boundary values exactly as `name` is. A declared value that is not a\n // clean segment is DROPPED, degrading to the default rather than\n // resolving to a traversal.\n const { path, worktrees } = entry;\n const repo = { name };\n if (typeof remote === \"string\" && remote.trim() !== \"\")\n repo.remote = remote;\n if (typeof path === \"string\" && isUsableRelPath(path))\n repo.path = path;\n if (typeof worktrees === \"string\" && isUsableRelPath(worktrees))\n repo.worktrees = worktrees;\n repos.push(repo);\n }\n return repos;\n}\n/**\n * Whether this project is workbench-shaped at all.\n *\n * `shape: \"workbench\"` says so explicitly; declaring repos says so just as\n * loudly. A config with `repos[]` and no `shape` flag used to read as a flat\n * project here while every other reader (`readWorkbenchRepos`, the bash lane,\n * the eval hook) treated it as a workbench — so `verify` would have judged the\n * wrapper repo's plan documents as code (workbench-trust-fixes, F6 / A14). One\n * rule, and every refusal inherits it through this function.\n */\nexport function isWorkbench(root) {\n if (readRawConfig(root)?.worktree?.shape === \"workbench\")\n return true;\n return readWorkbenchRepos(root).length > 0;\n}\n/** The parent directory the sibling clones live in, or null when undeclared. */\nexport function readSiblingParent(root) {\n const value = readRawConfig(root)?.worktree?.sibling_parent;\n return typeof value === \"string\" && value.trim() !== \"\" ? value : null;\n}\n/**\n * Where this workbench's repos live.\n *\n * `repos_root` supersedes `sibling_parent`, which named a *relationship*\n * (\"the parent of the siblings\") rather than the value. The relationship stops\n * being true the moment the repos live inside the workbench, and a field that\n * describes a layout it no longer governs is worse than a bland one.\n *\n * The old key is still read, so no existing workbench needs an edit — the same\n * reduction `wrapped_repo` gets. Returns the declared string verbatim;\n * resolving it (relative to the workbench, or absolute) belongs to the caller\n * that knows where the workbench is.\n */\nexport function readReposRoot(root) {\n const w = readRawConfig(root)?.worktree;\n for (const value of [w?.repos_root, w?.sibling_parent]) {\n if (typeof value === \"string\" && value.trim() !== \"\")\n return value;\n }\n return undefined;\n}\n/**\n * Where this workbench's repos live, as an absolute path.\n *\n * THE one definition. It had four — `workbench restore`, the health-check\n * runner, doppler's env-pull, and `indusk update`'s materialization nudge — and\n * they disagreed. The nudge read only the legacy `sibling_parent`, never\n * resolved a relative value, and looked repos up by `name` rather than declared\n * `path`, so on a nested workbench it reported every repo missing and told the\n * operator to run `workbench restore` — which would have cloned a second copy\n * beside the workbench and relinked the trunks at it.\n *\n * The first defect in this family to recommend a destructive action, and the\n * reason a copy of this rule is unacceptable rather than merely untidy.\n *\n * The rule: relative resolves against the workbench (which is what makes a\n * nested layout reproduce on a clone), absolute is used as given, absent means\n * the workbench's parent.\n */\nexport function resolveReposRoot(projectRoot) {\n const declared = readReposRoot(projectRoot);\n if (!declared)\n return resolve(projectRoot, \"..\");\n if (declared.startsWith(\"/\") || declared.startsWith(\"~\"))\n return declared;\n return resolve(projectRoot, declared);\n}\n/**\n * The refusal a command owes a project that is not a workbench.\n *\n * One string, because the *rule* it belongs to lives in bash. `worktree\n * create`/`refresh`/`preflight` all resolve which repo the user meant inside\n * `_resolve_workbench_repo`, and `worktreeCreate` deliberately passes `--repo`\n * straight through rather than deciding first — so there is exactly one\n * implementation of pick-one and it is the shell one.\n *\n * A TypeScript `resolveRepo` stood here with no caller, its docblock claiming\n * to be \"the rule the CLI surface needs in one place\" while the CLI surface\n * read the shell. Dead code that describes itself as authoritative is worse\n * than dead code; what the TS lane actually shares with bash is this message.\n */\nexport const NOT_A_WORKBENCH = 'this project is not a workbench (set worktree.shape=\"workbench\" and worktree.repos[] in .indusk/config.json, or run `indusk init --workbench`).';\n/**\n * Where this repo's checkout lives, relative to the workbench root.\n *\n * The declared `path`, or the name when nothing is declared. One function so\n * no caller re-derives it — a second copy would be the place the two answers\n * quietly diverge.\n */\nexport function repoDir(repo) {\n return repo.path ?? repo.name;\n}\n/**\n * Every declared repo with the directory its checkout lives in — the join of\n * `resolveReposRoot` (where repos live) and `repoDir` (each one's directory\n * name). Empty for a flat project.\n *\n * The one place that join is spelled (context-tiers cleanup, A24). It was\n * written at four sites — the health roots, the execution roots, the papers\n * destination and `check-pointers` — and each is the same fact, the kind\n * whose copies the layout lessons say drift silently. The hook lane's port is\n * `declaredRepoDirsAt` in `hooks/_hook-paths.js`.\n */\nexport function declaredRepoDirs(projectRoot) {\n const repos = readWorkbenchRepos(projectRoot);\n if (repos.length === 0)\n return [];\n const reposRoot = resolveReposRoot(projectRoot);\n return repos.map((r) => ({ name: r.name, dir: join(reposRoot, repoDir(r)) }));\n}\n","import type { HTMLAttributes } from \"react\";\n\n/**\n * Trajectory states from `apps/indusk-mcp/src/lib/trajectory/parser.ts`.\n * The Badge variant set mirrors them so trajectory rows map 1:1.\n */\nexport type BadgeVariant =\n | \"passing\"\n | \"blocked\"\n | \"skipped\"\n | \"planned\"\n | \"writable\"\n | \"written\"\n | \"unknown\"\n | \"neutral\";\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n variant?: BadgeVariant;\n}\n\n/**\n * Color palette per A9 visibility requirement: pass/fail status\n * legible at a glance without reading the State text.\n *\n * - passing: green (success)\n * - blocked: red (attention required)\n * - skipped: muted/yellow (intentional non-test)\n * - planned/writable/written: gray-toned (in flight)\n * - unknown: gray (parser fallback)\n * - neutral: utility variant for non-trajectory uses (status labels, etc.)\n */\nconst variantClasses: Record<BadgeVariant, string> = {\n passing: \"bg-green-100 text-green-800 ring-green-600/20\",\n blocked: \"bg-red-100 text-red-800 ring-red-600/20\",\n skipped: \"bg-yellow-100 text-yellow-800 ring-yellow-600/20\",\n planned: \"bg-gray-100 text-gray-700 ring-gray-500/20\",\n writable: \"bg-gray-200 text-gray-800 ring-gray-500/20\",\n written: \"bg-blue-50 text-blue-700 ring-blue-600/20\",\n unknown: \"bg-gray-50 text-gray-500 ring-gray-400/20\",\n neutral: \"bg-gray-100 text-gray-700 ring-gray-500/20\",\n};\n\nexport function Badge({\n variant = \"neutral\",\n className = \"\",\n children,\n ...rest\n}: BadgeProps) {\n const classes = [\n \"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ring-1 ring-inset\",\n variantClasses[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span className={classes} {...rest}>\n {children}\n </span>\n );\n}\n","import { appendFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nconst VALID_OUTCOMES = new Set([\n \"fix-in-scope\",\n \"spawn-plan\",\n \"accept-finding\",\n]);\n/**\n * Reject multiline content at the library boundary. The log's on-disk\n * format is markdown sections with bold-labeled single-line fields; any\n * line-separator character in hypothesis / note / reason silently\n * truncates during parse (the regex uses /m mode, where $ matches before\n * LF, CR, LS, and PS). Throwing here forces callers to sanitize — either\n * collapse to a single line or split across multiple entries.\n *\n * Line separators rejected: LF (\\n), CR (\\r), LS (U+2028), PS (U+2029).\n */\nconst LINE_SEPARATOR_RE = /[\\n\\r\\u2028\\u2029]/;\nfunction assertSingleLine(field, value) {\n if (LINE_SEPARATOR_RE.test(value)) {\n throw new Error(`Falsification log ${field} must be single-line (got a value containing a line separator). Either collapse to one line (replace '\\\\n' with '; '), or split the content across multiple entries. The log's parser is line-oriented; any line-separator (LF, CR, LS, PS) would silently truncate.`);\n }\n}\nfunction logPath(planRoot) {\n return join(planRoot, \"falsification.md\");\n}\nfunction headerFor(planRoot) {\n return `# Falsification Log — ${basename(planRoot)}\\n\\nAppend-only record of the /falsify bounty hunt for this plan. Never edit in place; entries are appended via \\`appendHypothesis\\` and \\`markTerminated\\` from \\`apps/indusk-mcp/src/lib/falsification/log.ts\\`.\\n\\n`;\n}\n/**\n * Append a confirmed-hypothesis entry to the plan's falsification log.\n * Creates the log file with a header if it doesn't yet exist. Throws if\n * the log is already terminated (a new hypothesis after a terminator is a\n * sign the ritual was restarted incorrectly — see `isFalsificationComplete`\n * and start a new plan or explicitly un-terminate first).\n */\nexport function appendHypothesis(planRoot, entry) {\n assertSingleLine(\"hypothesis\", entry.hypothesis);\n if (entry.note !== undefined)\n assertSingleLine(\"note\", entry.note);\n const path = logPath(planRoot);\n const existing = existsSync(path) ? readFalsificationLog(planRoot) : [];\n if (existing.length > 0 && existing[existing.length - 1].kind === \"terminator\") {\n throw new Error(`Falsification log at ${path} is already terminated. Start a new plan or remove the terminator before appending.`);\n }\n if (!existsSync(path)) {\n writeFileSync(path, headerFor(planRoot), \"utf-8\");\n }\n const stored = {\n kind: \"hypothesis\",\n hypothesis: entry.hypothesis,\n testPath: entry.testPath,\n outcome: entry.outcome,\n note: entry.note,\n timestamp: new Date().toISOString(),\n };\n appendFileSync(path, renderHypothesis(stored), \"utf-8\");\n return stored;\n}\n/**\n * Append a terminator entry marking the falsification ritual complete for\n * this plan. No further hypotheses can be appended after this. The reason\n * is the user-confirmed rationale for termination (e.g., \"investigated\n * concurrency, race conditions, partial-write paths, and type-narrowing\n * gaps; no in-scope failure remained\").\n */\nexport function markTerminated(planRoot, reason) {\n if (!reason.trim()) {\n throw new Error(\"markTerminated requires a non-empty reason.\");\n }\n assertSingleLine(\"reason\", reason);\n const path = logPath(planRoot);\n const existing = existsSync(path) ? readFalsificationLog(planRoot) : [];\n if (existing.length > 0 && existing[existing.length - 1].kind === \"terminator\") {\n throw new Error(`Falsification log at ${path} is already terminated.`);\n }\n if (!existsSync(path)) {\n writeFileSync(path, headerFor(planRoot), \"utf-8\");\n }\n const stored = {\n kind: \"terminator\",\n reason: reason.trim(),\n timestamp: new Date().toISOString(),\n };\n appendFileSync(path, renderTerminator(stored), \"utf-8\");\n return stored;\n}\n/**\n * Read the falsification log for a plan. Returns an empty array if the log\n * file does not exist. Malformed entries are skipped (not thrown) and\n * surfaced via the optional `onMalformed` callback, matching the semantic\n * graph event log's resilience pattern.\n */\nexport function readFalsificationLog(planRoot, opts) {\n const path = logPath(planRoot);\n if (!existsSync(path))\n return [];\n const content = readFileSync(path, \"utf-8\");\n const entries = [];\n const sectionRegex = /^##\\s+(Hypothesis|Terminated)\\s+(.+?)\\s*$/gm;\n const matches = [...content.matchAll(sectionRegex)];\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n const [, kind, timestamp] = match;\n const start = (match.index ?? 0) + match[0].length;\n const end = i + 1 < matches.length ? (matches[i + 1].index ?? content.length) : content.length;\n const body = content.slice(start, end).trim();\n if (kind === \"Hypothesis\") {\n const entry = parseHypothesisBody(body, timestamp);\n if (\"lineNumber\" in entry) {\n opts?.onMalformed?.(entry);\n }\n else {\n entries.push(entry);\n }\n }\n else if (kind === \"Terminated\") {\n const entry = parseTerminatorBody(body, timestamp);\n if (\"lineNumber\" in entry) {\n opts?.onMalformed?.(entry);\n }\n else {\n entries.push(entry);\n }\n }\n }\n return entries;\n}\n/**\n * True iff the plan's falsification log exists AND its last entry is a\n * terminator. False for a missing log, a log with only hypotheses (ritual\n * started but not terminated), or an empty log file.\n */\nexport function isFalsificationComplete(planRoot) {\n const entries = readFalsificationLog(planRoot);\n if (entries.length === 0)\n return false;\n return entries[entries.length - 1].kind === \"terminator\";\n}\n// ---------------------------------------------------------------\n// Rendering (writing entries to markdown)\n// ---------------------------------------------------------------\nfunction renderHypothesis(entry) {\n const lines = [\n `## Hypothesis ${entry.timestamp}`,\n \"\",\n `**Hypothesis:** ${entry.hypothesis}`,\n `**Test:** ${entry.testPath ?? \"(not written)\"}`,\n `**Outcome:** ${entry.outcome}`,\n ];\n if (entry.note) {\n lines.push(`**Note:** ${entry.note}`);\n }\n lines.push(\"\", \"\");\n return lines.join(\"\\n\");\n}\nfunction renderTerminator(entry) {\n return [`## Terminated ${entry.timestamp}`, \"\", `**Reason:** ${entry.reason}`, \"\", \"\"].join(\"\\n\");\n}\n// ---------------------------------------------------------------\n// Parsing (reading entries from markdown)\n// ---------------------------------------------------------------\nfunction parseHypothesisBody(body, timestamp) {\n const hypothesisMatch = body.match(/^\\*\\*Hypothesis:\\*\\*\\s+(.+)$/m);\n const testMatch = body.match(/^\\*\\*Test:\\*\\*\\s+(.+)$/m);\n const outcomeMatch = body.match(/^\\*\\*Outcome:\\*\\*\\s+([a-z-]+)$/m);\n const noteMatch = body.match(/^\\*\\*Note:\\*\\*\\s+(.+)$/m);\n if (!hypothesisMatch || !outcomeMatch) {\n return {\n lineNumber: 0,\n content: body,\n reason: \"Hypothesis entry missing required fields (hypothesis or outcome)\",\n };\n }\n const outcome = outcomeMatch[1];\n if (!VALID_OUTCOMES.has(outcome)) {\n return {\n lineNumber: 0,\n content: body,\n reason: `Invalid outcome \"${outcome}\"; must be one of ${[...VALID_OUTCOMES].join(\", \")}`,\n };\n }\n const testRaw = testMatch?.[1]?.trim() ?? \"(not written)\";\n return {\n kind: \"hypothesis\",\n hypothesis: hypothesisMatch[1].trim(),\n testPath: testRaw === \"(not written)\" ? null : testRaw,\n outcome,\n note: noteMatch?.[1]?.trim(),\n timestamp,\n };\n}\nfunction parseTerminatorBody(body, timestamp) {\n const reasonMatch = body.match(/^\\*\\*Reason:\\*\\*\\s+(.+)$/m);\n if (!reasonMatch) {\n return {\n lineNumber: 0,\n content: body,\n reason: \"Terminator entry missing required Reason field\",\n };\n }\n return {\n kind: \"terminator\",\n reason: reasonMatch[1].trim(),\n timestamp,\n };\n}\n","import matter from \"gray-matter\";\n/**\n * Parse an impl.md body (the full file content including frontmatter) and\n * return whether the author has explicitly opted out of the falsification\n * ritual. Opt-out requires both fields in frontmatter:\n *\n * falsification: skipped\n * falsification_reason: \"a non-empty reason\"\n *\n * Returns `{ skipped: true, reason }` only if both fields are present and\n * the reason is non-empty after trimming. Any other state — missing\n * `falsification`, falsification set to anything other than `skipped`,\n * missing or empty reason — returns `{ skipped: false, reason: null }`.\n *\n * The two-field shape matches the planner skill's precedent (gate_policy\n * as a single enum value) while keeping the reason unambiguous against\n * YAML parsers. Colons inside quoted YAML strings are fragile across\n * parsers; two fields avoid that class of bug entirely.\n */\nexport function isFalsificationSkipped(implContent) {\n try {\n const { data } = matter(implContent);\n const flag = data.falsification;\n const reasonRaw = data.falsification_reason;\n if (flag !== \"skipped\")\n return { skipped: false, reason: null };\n if (typeof reasonRaw !== \"string\")\n return { skipped: false, reason: null };\n const reason = reasonRaw.trim();\n if (!reason)\n return { skipped: false, reason: null };\n return { skipped: true, reason };\n }\n catch {\n return { skipped: false, reason: null };\n }\n}\n","/**\n * The one definition of every heading an impl document uses.\n *\n * Before this module the phase-heading pattern existed in six places across\n * five files, with no test holding them together. That was survivable while\n * there was one kind of phase. It stops being survivable the moment there are\n * two: a second kind added to five copies is a plan that fails four times\n * before it works, and each failure is silent — an unrecognised heading does\n * not error, it simply isn't a phase, and every rule keyed on phases quietly\n * has nothing to say.\n *\n * `impl-headings.test.ts` asserts by counting: exactly one definition under\n * `src/`. It is the same structural shape as `shared-resolution.test.ts`, and\n * for the same reason — no behavioural test can catch a divergence that has\n * not happened yet.\n *\n * The JS hooks cannot import a `.ts` module and mirror these constants inline\n * with a deliberate-port comment, the way the trajectory hooks already do.\n * Change this file and every port together.\n */\n/**\n * The heading text every build-phase pattern is built from — declared once so\n * the \"any phase\" and \"this phase\" forms cannot drift apart.\n */\nconst BUILD_PHASE = String.raw `###\\s+(?:Build\\s+)?Phase`;\n/**\n * A build phase: `### Phase 1: Name` or `### Build Phase 1: Name`.\n *\n * The `Build ` group is optional, which is the whole backward-compatibility\n * story — every impl written before test phases existed keeps parsing, so no\n * archived plan needs editing and no reader needs to learn that two spellings\n * mean the same thing before they can read old work.\n *\n * Capture 1 is the number, capture 2 the name.\n */\nexport const PHASE_HEADING = new RegExp(`^${BUILD_PHASE}\\\\s+(\\\\d+)[:\\\\s]+(.*)`);\n/**\n * The same heading, pinned to one specific phase number — what a caller needs\n * when it is walking to *this* phase's block rather than recognising any phase.\n * `\\b` keeps Phase 1 from matching Phase 10.\n *\n * Composed from `BUILD_PHASE` rather than restating it. That matters: this\n * pattern previously lived as its own template string in `shape/impl-blocks.ts`,\n * where the escaped-in-a-template form (`\\\\s`) slipped past the\n * single-definition guard entirely. It matched `### Phase N` only, so once\n * `/planner` began emitting `### Build Phase N`, Shape found no phase at all\n * and appended its findings nowhere — silently.\n */\nexport function buildPhaseHeadingFor(phase) {\n const ref = toPhaseRef(phase);\n if (ref.kind === \"test\")\n return new RegExp(`^###\\\\s+Test\\\\s+Phase\\\\s+${ref.number}\\\\b`);\n return new RegExp(`^${BUILD_PHASE}\\\\s+${ref.number}\\\\b`);\n}\n/** `#### Phase N <Gate>` for one specific phase, in that phase's spelling. */\nexport function gateHeadingFor(phase, gate) {\n const ref = toPhaseRef(phase);\n if (ref.kind === \"test\") {\n return new RegExp(`^####\\\\s+Test\\\\s+Phase\\\\s+${ref.number}\\\\s+${gate}\\\\b`);\n }\n return new RegExp(`^####\\\\s+(?:Build\\\\s+)?Phase\\\\s+${ref.number}\\\\s+${gate}\\\\b`);\n}\nexport function toPhaseRef(address) {\n return typeof address === \"number\" ? { kind: \"build\", number: address } : address;\n}\n/** `Test Phase 1` / `Build Phase 2` — for messages and appended notes. */\nexport function phaseLabel(address) {\n const ref = toPhaseRef(address);\n return `${ref.kind === \"test\" ? \"Test\" : \"Build\"} Phase ${ref.number}`;\n}\n/**\n * A test phase: `### Test Phase 1: Name`.\n *\n * Deliberately a separate pattern rather than another optional group in\n * `PHASE_HEADING`. The two kinds must never be confusable by a regex that\n * \"helpfully\" matches both — a test phase counted as a build phase would\n * satisfy the very rules it exists to make checkable.\n *\n * Capture 1 is the number, capture 2 the name.\n */\nexport const TEST_PHASE_HEADING = /^###\\s+Test\\s+Phase\\s+(\\d+)[:\\s]+(.*)/;\n/**\n * The number-only form, for callers that need to recognise a phase heading\n * without caring which kind it is — anything scanning for \"where does this\n * phase end\" wants both, because a test phase ends a build phase just as\n * surely as another build phase does.\n */\nexport const ANY_PHASE_HEADING = /^###\\s+(?:Test\\s+|Build\\s+)?Phase\\s+(\\d+)\\b/;\n/**\n * A gate heading: `#### Phase 1 Verification`, `#### Build Phase 1 Context`,\n * `#### Test Phase 1 Verification`.\n *\n * **Capture 1 is always the phase number.** `kind` may be a single kind or an\n * alternation like `(Verification|Context|Document)`, in which case its own\n * group lands at capture 2 — group numbering is worth stating because it is\n * the one thing a caller can get silently wrong here, and a gate type read\n * from the wrong index is an undefined lookup rather than a crash.\n */\nexport function gateHeading(kind) {\n return new RegExp(`^####\\\\s+(?:Test\\\\s+|Build\\\\s+)?Phase\\\\s+(\\\\d+)\\\\s+${kind}\\\\b`);\n}\n/** `#### Phase N Forward Intelligence`, in any phase-kind spelling. */\nexport const FORWARD_INTELLIGENCE_HEADING = /^####\\s+(?:Test\\s+|Build\\s+)?Phase\\s+\\d+\\s+Forward Intelligence\\b/;\n/**\n * Parse a line as a phase heading, or `null` if it is not one.\n *\n * Test phases are tried first: `PHASE_HEADING` does not match `### Test Phase\n * 1` today, but the ordering makes that a property of this function rather\n * than a property of a regex someone might later loosen.\n */\nexport function parsePhaseHeading(line) {\n const test = TEST_PHASE_HEADING.exec(line);\n if (test) {\n return { kind: \"test\", number: Number.parseInt(test[1], 10), name: test[2].trim() };\n }\n const build = PHASE_HEADING.exec(line);\n if (build) {\n return { kind: \"build\", number: Number.parseInt(build[1], 10), name: build[2].trim() };\n }\n return null;\n}\n/**\n * A trajectory cell naming a phase: `Phase 3`, `Build Phase 3`, `Test Phase 1`.\n *\n * Anchored and whole-cell, like the pattern it replaces — a cell with prose\n * around a phase name is not a reference, it is a malformed cell, and the\n * validator has a specific error for that.\n */\nexport const PHASE_REFERENCE = /^\\s*(Test\\s+|Build\\s+)?Phase\\s+(\\d+)\\s*$/i;\n/** Parse a `Writable at` / `Passes at` cell. `null` if it is not a reference. */\nexport function parsePhaseRef(cell) {\n const match = PHASE_REFERENCE.exec(cell);\n if (!match)\n return null;\n const kind = match[1]?.trim().toLowerCase() === \"test\" ? \"test\" : \"build\";\n return { kind, number: Number.parseInt(match[2], 10) };\n}\n/** A fence marker: three or more backticks or tildes, optionally indented. */\nconst FENCE = /^\\s*(`{3,}|~{3,})(.*)$/;\nfunction parseFence(line) {\n const m = FENCE.exec(line);\n if (!m)\n return null;\n return { char: m[1][0], length: m[1].length, info: m[2].trim() };\n}\n/**\n * The index of the line closing the fence opened at `start`, or `-1`.\n *\n * CommonMark's rule, and it is the rule that matters here: a fence closes only\n * on a marker of the **same character** and **at least the same length**, with\n * nothing after it. That is what lets a carried test body contain a fence of\n * its own — you nest by lengthening the marker — and it is what stops a `~~~`\n * inside a ```` ``` ```` block from ending the block early.\n */\nfunction findFenceClose(lines, start, open) {\n for (let i = start + 1; i < lines.length; i++) {\n const candidate = parseFence(lines[i]);\n if (candidate &&\n candidate.char === open.char &&\n candidate.length >= open.length &&\n candidate.info === \"\") {\n return i;\n }\n }\n return -1;\n}\n/**\n * Mark every line that sits inside a fenced code block.\n *\n * Structure-scanning must skip these. A deferral entry in Test Phase 1 may\n * carry the deferred test's body, and that body will contain lines that look\n * exactly like checklist items and gate headings — which is the point of\n * carrying it. A parser that reads them as structure turns a piece of evidence\n * into a phantom phase.\n *\n * **An unterminated fence masks nothing.** Failing open is deliberate: the\n * alternative is that one missing backtick deletes every phase below it from\n * every parser at once, and nothing reports a problem — the zero-phase guard\n * cannot save it, because the phases *above* the fence still parse. A leaked\n * code body produces loud, confusing errors; a swallowed document produces\n * silence, and silence is the failure mode this whole plan exists to remove.\n * The validator separately refuses an impl with an unterminated fence, so the\n * confusing errors are not what the author actually sees.\n */\nexport function fencedLineMask(lines) {\n const mask = new Array(lines.length).fill(false);\n for (let i = 0; i < lines.length; i++) {\n const open = parseFence(lines[i]);\n if (!open)\n continue;\n const close = findFenceClose(lines, i, open);\n if (close === -1)\n continue; // unterminated — not a fence at all\n for (let j = i; j <= close; j++)\n mask[j] = true;\n i = close;\n }\n return mask;\n}\n/**\n * The 1-based line number of an unterminated fence, or `null` if every fence\n * in `body` is closed. Reported rather than tolerated — see `fencedLineMask`.\n */\nexport function unterminatedFenceLine(body) {\n const lines = body.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const open = parseFence(lines[i]);\n if (!open)\n continue;\n const close = findFenceClose(lines, i, open);\n if (close === -1)\n return i + 1;\n i = close;\n }\n return null;\n}\n/** The document's phases, in the order they appear. Fenced blocks ignored. */\nexport function phaseSequence(body) {\n const lines = body.split(\"\\n\");\n const fenced = fencedLineMask(lines);\n const out = [];\n for (let i = 0; i < lines.length; i++) {\n if (fenced[i])\n continue;\n const heading = parsePhaseHeading(lines[i]);\n if (heading)\n out.push({ kind: heading.kind, number: heading.number });\n }\n return out;\n}\n/**\n * Where `ref` sits on the document's single phase timeline.\n *\n * Two sequences that number independently cannot be ordered by number alone —\n * Test Phase 1 and Build Phase 1 are different phases wearing the same digit.\n * Document order is the only thing that orders them, because it is the order\n * the work actually happens in.\n *\n * **A document with no test phase reduces to the phase number**, exactly as\n * before. That is deliberate and load-bearing: every impl written before test\n * phases existed compares precisely as it always did, so backward\n * compatibility is a property of this function rather than a claim made about\n * it elsewhere. It also keeps `Phase 0` meaningful — it orders before `Phase 1`\n * by arithmetic, with no special case.\n *\n * A ref naming a phase the document does not contain is placed among phases of\n * its own kind by number, half a step past the last one smaller than it.\n * Trajectory rows legitimately name phases before those phases are written, and\n * a reference to the future should sort into the future rather than collapse to\n * zero.\n */\n/**\n * Whether `ref` names a phase this document actually contains.\n *\n * `Phase 0` is always present by definition: it means \"before any of this\n * plan's work\", which is a real moment in a plan's life and deliberately has no\n * heading. Every other reference has to point at something written down.\n *\n * The distinction matters for enforcement rather than for ordering. A row whose\n * authoring phase exists can be late; a row naming a phase nobody has written\n * yet cannot be — you cannot miss a deadline that has not been scheduled — and\n * treating the two alike turns a plan's forward reference into a false\n * accusation.\n */\nexport function phaseExists(ref, sequence) {\n if (ref.kind === \"build\" && ref.number === 0)\n return true;\n return sequence.some((p) => p.kind === ref.kind && p.number === ref.number);\n}\nexport function phaseOrdinal(ref, sequence) {\n if (!sequence.some((p) => p.kind === \"test\"))\n return ref.number;\n const exact = sequence.findIndex((p) => p.kind === ref.kind && p.number === ref.number);\n if (exact !== -1)\n return exact;\n let lastBefore = -1;\n for (let i = 0; i < sequence.length; i++) {\n const p = sequence[i];\n if (p.kind === ref.kind && p.number < ref.number)\n lastBefore = i;\n }\n return lastBefore + 0.5;\n}\n","/**\n * The one definition of a plan's type (admin-plan-type).\n *\n * A plan declares what kind it is with `workflow:` in its brief. The kind\n * decides which planning documents the plan needs, and so whether an absent\n * document is a gap or was never going to exist.\n *\n * Before this module those facts were stated twice — the planner skill's\n * Workflow Types table and the four files under `templates/workflows/` — and\n * the two disagreed: the table said a bugfix has a test plan, the bugfix\n * template listed only a brief and an impl, and a bugfix written from the\n * template closed without one. The admin needs the same facts to explain a\n * type, which would have been a third statement. This is where they live;\n * `workflow-types-parity.test.ts` pins the skill and the templates to it.\n *\n * No filesystem and no Node built-ins: the admin imports this from a browser\n * component.\n */\nexport const WORKFLOW_TYPES = [\"feature\", \"bugfix\", \"refactor\", \"spike\"];\n/**\n * The retrospective is required by every kind that ships an impl. The planner\n * skill's table lists it only for `feature` because that table describes what\n * `/planner` creates; the retrospective is `/retrospective`'s, and a bugfix or\n * a refactor is closed by one like any other plan.\n */\nexport const WORKFLOW_DEFINITIONS = {\n feature: {\n purpose: \"A new capability or a change to how the system works — the full lifecycle.\",\n requires: [\"research\", \"brief\", \"test-plan\", \"adr\", \"impl\", \"retrospective\"],\n skips: [],\n why: \"Nothing is skipped: the problem has to be explored, the direction proposed, the behaviour pinned down, and the decision recorded before code is written.\",\n },\n bugfix: {\n purpose: \"Something is broken and the fix is known.\",\n requires: [\"brief\", \"test-plan\", \"impl\", \"retrospective\"],\n skips: [\"research\", \"adr\"],\n why: \"The problem is already understood, so there is nothing to research and no architecture to decide. The test plan stays: its first assertion is the failing test that proves the bug.\",\n },\n refactor: {\n purpose: \"Existing code is restructured without changing what it does.\",\n requires: [\"brief\", \"test-plan\", \"impl\", \"retrospective\"],\n skips: [\"research\", \"adr\"],\n why: \"Behaviour does not change, so there is no problem space to research and no decision to record. The test plan stays — it is what proves the behaviour held — and the impl carries a boundary map.\",\n },\n spike: {\n purpose: \"Pure exploration, with no commitment to build anything.\",\n requires: [\"research\"],\n skips: [\"brief\", \"test-plan\", \"adr\", \"impl\", \"retrospective\"],\n why: \"A spike ends in findings. If it leads to building something, that is a new plan with its own brief.\",\n },\n};\n/** What each document is called when a person reads it. */\nexport const DOCUMENT_LABELS = {\n research: \"research\",\n brief: \"brief\",\n \"test-plan\": \"test plan\",\n adr: \"ADR\",\n impl: \"impl\",\n retrospective: \"retrospective\",\n};\n/**\n * Read a `workflow:` frontmatter value. Three outcomes, kept apart on purpose:\n * a known type; a word that is not a type (`declared` set, `type` null — shown\n * as written, never guessed at); and nothing declared.\n *\n * A non-string is still a declaration — a list, a mapping, a number — and\n * treating it as \"not declared\" would hide the mistake. `written` is the text\n * on the document's `workflow:` line, when the caller has the document: it is\n * what such a declaration is shown as.\n */\nexport function readWorkflow(value, written = null) {\n if (value === undefined || value === null)\n return { type: null, declared: null };\n // Only a plain string can be a type. A list, a mapping, a number or a\n // boolean is a declaration that is not one: `String()` of a one-element\n // list is its element's text, which read `[bugfix]` as a bugfix. It is\n // shown as it was written on the line; a value written on the lines below\n // (a block list) has no text there, and is shown as YAML read it.\n if (typeof value !== \"string\") {\n return { type: null, declared: written ?? JSON.stringify(value) };\n }\n const declared = value.trim();\n if (declared === \"\")\n return { type: null, declared: null };\n const type = WORKFLOW_TYPES.includes(declared)\n ? declared\n : null;\n return { type, declared };\n}\n/**\n * One sentence for an absent document the bar cannot leave to colour alone.\n * `missing`: the type requires it and the plan moved past it. `unknown`: the\n * absence cannot be judged — because no type is declared, or because the word\n * that was declared is not one. Those are different facts, and the sentence\n * says which: a plan that declared `hotfix` did not declare nothing.\n */\nexport function absentDocumentNote(document, state, type, \n/** The word that was declared, when it is not one of the types. */\ndeclared = null) {\n const label = DOCUMENT_LABELS[document];\n if (state === \"missing\" && type !== null) {\n return `The ${label} is missing — a ${type} requires it.`;\n }\n if (declared !== null) {\n return `The ${label} is absent, and this plan declares \"${declared}\", which is not a recognised type, so whether that matters cannot be judged.`;\n }\n return `The ${label} is absent, and this plan declares no type, so whether that matters cannot be judged.`;\n}\n","import { DOCUMENT_LABELS, WORKFLOW_DEFINITIONS } from \"./workflow-types.js\";\n/**\n * Every position, in order. `monitor` follows `archived` (day-monitor, ADR\n * D8): a closed plan holding a behaviour promise waits there until its\n * promises have been quiet for the window.\n */\nexport const PLAN_POSITIONS = [\n \"research\",\n \"brief\",\n \"test-plan\",\n \"adr\",\n \"impl-approved\",\n \"executing\",\n \"falsify\",\n \"cleanup\",\n \"retrospective\",\n \"archived\",\n \"monitor\",\n];\n/**\n * The positions that are a document on disk, in lifecycle order — the plan\n * parser's stage order. `test-plan` joins it here: every new plan has one,\n * and the old private order walked past it as if it were not a stage.\n */\nexport const DOCUMENT_POSITIONS = [\n \"research\",\n \"brief\",\n \"test-plan\",\n \"adr\",\n \"impl\",\n \"retrospective\",\n];\n/**\n * Whether a document's status says it is finished. Research documents say\n * `complete`, impls and retrospectives `completed`, the rest `accepted`. One\n * definition, because the plan bar and the plan list's next step both ask\n * it, and a copy that knew one word fewer told a finished spike to review\n * its research (admin-plan-type, A23).\n */\nexport function isFinishedDocumentStatus(status) {\n return status === \"accepted\" || status === \"complete\" || status === \"completed\";\n}\n/**\n * The positions that exist only because a plan has an impl: it is executed,\n * then falsified, then cleaned up. A type with no impl — a spike — never\n * reaches them, so they read skipped for it rather than pending forever\n * (admin-plan-type, A21).\n */\nexport const IMPL_DEPENDENT_POSITIONS = [\n \"executing\",\n \"falsify\",\n \"cleanup\",\n];\n/**\n * The close-out rituals, in order, as the word a ritual phase's title must\n * START with (`### Phase N: Falsification — …`). The retrospective readiness\n * gate matches these; the skill prose used to be the only place the order was\n * written down.\n */\nexport const RITUAL_ORDER = [\"falsification\", \"cleanup\"];\nexport const PHASE_ACTIVITIES = [\n \"authoring\",\n \"implementing\",\n \"instrumenting\",\n \"verifying\",\n \"capturing-context\",\n \"documenting\",\n \"closed\",\n \"falsifying\",\n \"cleaning-up\",\n];\n/**\n * The gate stages a phase passes through after its implementation items, in\n * the order `/work` completes them. OTel is a stage wherever the project emits\n * it; a project whose `otel.role` is `library` simply never has the block.\n */\nexport const GATE_STAGES = [\"Verification\", \"OTel\", \"Context\", \"Document\"];\n// ---------------------------------------------------------------------------\n// Segments\n// ---------------------------------------------------------------------------\n/**\n * Every bar segment is one of these (Sandy, 2026-09-16). A bar that is only\n * filled-or-empty cannot show the middle, and the middle is the whole point\n * of watching. Skipped is drawn, never omitted, so every plan's bar has the\n * same shape.\n *\n * `missing` and `unknown` are an absent document's other two readings\n * (admin-plan-type): `skipped` says the plan's type does not require it,\n * `missing` that the type requires it and the plan moved past it, `unknown`\n * that no type is declared so the absence cannot be judged. A runtime list, so\n * the admin's render-parity pin can walk it like the positions and activities.\n */\nexport const SEGMENT_STATES = [\n \"done\",\n \"active\",\n \"pending\",\n \"skipped\",\n \"missing\",\n \"unknown\",\n];\n/** \"3 of 7 days quiet\", \"window restarted 2026-09-18 — 0 of 7 days quiet\". */\nexport function monitorAwaiting(m) {\n const quiet = `${Math.floor(m.elapsedDays)} of ${m.windowDays} days quiet`;\n return m.restartedAt ? `window restarted ${m.restartedAt.slice(0, 10)} — ${quiet}` : quiet;\n}\nconst DOC_POSITION_TO_PLAN = {\n research: \"research\",\n brief: \"brief\",\n \"test-plan\": \"test-plan\",\n adr: \"adr\",\n impl: \"impl-approved\",\n retrospective: \"retrospective\",\n};\n/** The plan-level position a plan summary + impl state resolves to, and why. */\nfunction resolvePosition(input) {\n const { summary, impl, readiness, archived, afterClose } = input;\n if (archived) {\n if (afterClose && afterClose.reopened.length > 0) {\n return { position: \"executing\", awaiting: `executing ${afterClose.reopened[0]}` };\n }\n if (afterClose?.monitor) {\n return { position: \"monitor\", awaiting: monitorAwaiting(afterClose.monitor) };\n }\n return { position: \"archived\", awaiting: null };\n }\n const stage = summary.stage;\n const status = summary.stageStatus;\n if (stage === \"retrospective\") {\n return { position: \"retrospective\", awaiting: \"retrospective written, awaiting archive\" };\n }\n if (stage === \"impl\") {\n if (status === \"completed\") {\n // A29: the bar may never say \"cleaned\" over a fact it does not hold.\n // No readiness means the impl could not be read — say so; a\n // non-terminal row means the gate would refuse — name it.\n if (readiness === null) {\n return {\n position: \"falsify\",\n awaiting: \"impl complete — readiness unknown (impl unreadable)\",\n };\n }\n const missing = readiness.missing;\n if (missing.includes(\"falsification\")) {\n return { position: \"falsify\", awaiting: \"impl complete, awaiting /falsify\" };\n }\n if (missing.includes(\"cleanup\")) {\n return { position: \"cleanup\", awaiting: \"falsified, awaiting /cleanup\" };\n }\n if (missing.includes(\"rows\")) {\n const rows = readiness.nonTerminalRows.join(\", \");\n return {\n position: \"retrospective\",\n awaiting: `rows not terminal — retrospective blocked${rows ? ` (${rows})` : \"\"}`,\n };\n }\n return { position: \"retrospective\", awaiting: \"cleaned, awaiting /retrospective\" };\n }\n if (status === \"in-progress\") {\n // A33: the active segment always carries the message. Normally the\n // active phase speaks; when nothing is open there is no active\n // phase, so the position itself says what it is waiting for.\n const nothingOpen = impl !== null &&\n impl.phases.length > 0 &&\n impl.phases.every((p) => p.gates.every((g) => g.items.every((i) => i.checked)));\n return {\n position: \"executing\",\n awaiting: nothingOpen ? \"every item checked — impl status is still in-progress\" : null,\n };\n }\n if (status === \"approved\")\n return { position: \"impl-approved\", awaiting: \"impl approved, awaiting /work\" };\n return { position: \"impl-approved\", awaiting: `impl ${status}, awaiting approval` };\n }\n if (stage === \"research\" || stage === \"brief\" || stage === \"test-plan\" || stage === \"adr\") {\n const noun = DOCUMENT_LABELS[stage];\n if (isFinishedDocumentStatus(status)) {\n // A22: the active label never claims a fact the reader does not\n // hold. A spike's finished research awaits nothing.\n const type = summary.workflow ?? null;\n if (type !== null && nextRequiredDocument(stage, type) === undefined) {\n return { position: stage, awaiting: `${noun} finished — a ${type} ends here` };\n }\n return { position: stage, awaiting: `${noun} accepted, awaiting the next document` };\n }\n if (status === \"proposed\" || status === \"draft\" || status === \"in-progress\") {\n return { position: stage, awaiting: `${noun} ${status}, awaiting acceptance` };\n }\n return { position: stage, awaiting: `${noun} ${status}` };\n }\n // `paper`, `unknown`, `malformed`: no lifecycle position has been reached.\n return { position: \"research\", awaiting: `${stage} — no lifecycle document yet` };\n}\n/**\n * The plan bar. Positions before the current one are done, the current one is\n * active, later ones pending. Archived has no active segment: nothing is\n * happening. `monitor` is pending unless a closed plan is inside its quiet\n * window, when it is active and `archived` behind it is done.\n *\n * A document position whose file is absent is judged by the plan's declared\n * type, never by the absence alone (admin-plan-type). This function used to\n * call every absent earlier document `skipped`, so a bugfix that never needed\n * research and a bugfix that closed without its test plan drew the same\n * segment — a judgment asserted that nothing had made.\n */\nexport function derivePlanPosition(input) {\n const { position, awaiting } = resolvePosition(input);\n const currentIndex = PLAN_POSITIONS.indexOf(position);\n const docs = new Set(input.summary.documents);\n const type = input.summary.workflow ?? null;\n const required = type === null ? null : new Set(WORKFLOW_DEFINITIONS[type].requires);\n const segments = {};\n for (const [index, candidate] of PLAN_POSITIONS.entries()) {\n if (index === currentIndex) {\n segments[candidate] = position === \"archived\" ? \"done\" : \"active\";\n continue;\n }\n segments[candidate] = restingState(candidate, index < currentIndex, docs, required);\n }\n const monitor = position === \"monitor\" ? input.afterClose?.monitor : null;\n return {\n position,\n segments,\n awaiting: position === \"archived\" ? null : awaiting,\n ...(monitor ? { monitor } : {}),\n };\n}\n/**\n * The state of a position the plan is not at — one it has passed, or one\n * ahead of it. The type speaks first: a position that exists only because of\n * an impl is skipped for a type that has none. Then the document, if the\n * position is one. Then where the plan stands.\n */\nfunction restingState(position, passed, docs, required) {\n if (required !== null && !required.has(\"impl\") && IMPL_DEPENDENT_POSITIONS.includes(position)) {\n return \"skipped\";\n }\n const doc = documentFor(position);\n if (doc !== null && !docs.has(`${doc}.md`))\n return absentDocumentState(doc, required, passed);\n return passed ? \"done\" : \"pending\";\n}\n/**\n * The next document a plan's declared type requires after `stage`, or\n * undefined when there is none. With no declared type every document is next\n * in turn, as before the type existed.\n *\n * One definition, read by the active label, the plan list's next step and\n * `advance_plan`. Each once answered \"what comes next\" by lifecycle order\n * alone, so a bugfix with an accepted test plan was told to create the ADR\n * its type skips (admin-plan-type, A23 and A25).\n */\nexport function nextRequiredDocument(stage, type) {\n const index = DOCUMENT_POSITIONS.indexOf(stage);\n return DOCUMENT_POSITIONS.slice(index + 1).find((doc) => type === null || WORKFLOW_DEFINITIONS[type].requires.includes(doc));\n}\n/**\n * What an absent document reads as. `required` is the declared type's document\n * list, or null when the plan declares no type.\n *\n * - Not required by the type: `skipped`, whether the plan has passed it or not\n * — the bar says up front what this kind of plan will never have.\n * - Required and passed: `missing`.\n * - Required and not reached: `pending`, like any step ahead.\n * - No type, and passed: `unknown` — where this used to say skipped.\n * - No type, and not reached: `pending`. Nothing is in question yet.\n */\nfunction absentDocumentState(doc, required, passed) {\n if (required === null)\n return passed ? \"unknown\" : \"pending\";\n if (!required.has(doc))\n return \"skipped\";\n return passed ? \"missing\" : \"pending\";\n}\n/** The document a position stands for, or null for a position that is not a document (`executing`, `archived`, …). */\nexport function documentFor(position) {\n for (const doc of DOCUMENT_POSITIONS) {\n if (DOC_POSITION_TO_PLAN[doc] === position)\n return doc;\n }\n return null;\n}\n// ---------------------------------------------------------------------------\n// Deriving a phase's activity\n// ---------------------------------------------------------------------------\n/** `(none needed — asked: \"…\" — user: \"…\")` — the conversation proof a skipped gate carries. */\nconst OPT_OUT = /\\(\\s*(?:none needed|not applicable)\\b[^)]*asked:\\s*\"[^\"]+\"[^)]*user:\\s*\"[^\"]+\"/i;\nconst STAGE_VERB = {\n implementation: \"implementing\",\n Verification: \"verifying\",\n OTel: \"instrumenting\",\n Context: \"capturing-context\",\n Document: \"documenting\",\n};\nconst GATE_TYPE_TO_STAGE = {\n implementation: \"implementation\",\n verification: \"Verification\",\n otel: \"OTel\",\n context: \"Context\",\n document: \"Document\",\n};\n/**\n * The phase bar: the implementation items, then each gate the phase carries,\n * in `GATE_STAGES` order. A stage is done when every item is checked,\n * opted-out when its only checked item is a conversation-proof skip, pending\n * otherwise. The first stage that is not done is active and names the verb —\n * `authoring` for a test phase's items, `falsifying` / `cleaning-up` for a\n * ritual phase's, `implementing` for everything else. All done ⇒ `closed`.\n */\nexport function derivePhaseActivity(phase, boundary) {\n const byStage = new Map();\n for (const gate of phase.gates) {\n const stage = GATE_TYPE_TO_STAGE[gate.type];\n if (!stage)\n continue;\n byStage.set(stage, [...(byStage.get(stage) ?? []), ...gate.items]);\n }\n const order = [\"implementation\", ...GATE_STAGES];\n const stages = [];\n let activeFound = false;\n let activity = \"closed\";\n for (const kind of order) {\n const items = byStage.get(kind);\n // A32: a stage with nothing in it is not a stage — the parser always\n // emits an implementation gate, and an empty one read \"implementing\n // 0 of 0\" forever while the active-phase rule said nothing was open.\n if (!items || items.length === 0)\n continue;\n const checked = items.filter((i) => i.checked).length;\n const total = items.length;\n const optOut = items.find((i) => i.checked && OPT_OUT.test(i.text));\n let state;\n if (total > 0 && checked === total) {\n state = optOut ? \"opted-out\" : \"done\";\n }\n else if (!activeFound) {\n state = \"active\";\n activeFound = true;\n activity = verbFor(phase, kind);\n }\n else {\n state = \"pending\";\n }\n stages.push({\n kind,\n state,\n checked,\n total,\n ...(optOut ? { proof: optOut.text } : {}),\n });\n }\n return { activity, stages, openedAt: boundary?.timestamp ?? null };\n}\nfunction verbFor(phase, stage) {\n if (stage !== \"implementation\")\n return STAGE_VERB[stage];\n if (phase.kind === \"test\")\n return \"authoring\";\n const title = phase.name.toLowerCase();\n if (title.startsWith(RITUAL_ORDER[0]))\n return \"falsifying\";\n if (title.startsWith(RITUAL_ORDER[1]))\n return \"cleaning-up\";\n return \"implementing\";\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { isFalsificationComplete } from \"../falsification/log.js\";\nimport { isFalsificationSkipped } from \"../falsification/skip.js\";\nimport { parsePhaseHeading } from \"../impl-headings.js\";\nimport { RITUAL_ORDER } from \"../lifecycle.js\";\nimport { findNonTerminalRows } from \"../trajectory/audit.js\";\nimport { parseTrajectory } from \"../trajectory/parser.js\";\n/**\n * Parse an impl.md's frontmatter and return whether the author has explicitly\n * opted out of the cleanup ritual. Opt-out requires both fields:\n *\n * cleanup: skipped\n * cleanup_reason: \"a non-empty reason\"\n *\n * Near-clone of `isFalsificationSkipped` — the two-field shape avoids the\n * quoted-YAML-colon fragility that a single `skip-reason:` field would carry.\n */\nexport function isCleanupSkipped(implContent) {\n try {\n const { data } = matter(implContent);\n if (data.cleanup !== \"skipped\")\n return { skipped: false, reason: null };\n const reasonRaw = data.cleanup_reason;\n if (typeof reasonRaw !== \"string\")\n return { skipped: false, reason: null };\n const reason = reasonRaw.trim();\n if (!reason)\n return { skipped: false, reason: null };\n return { skipped: true, reason };\n }\n catch {\n return { skipped: false, reason: null };\n }\n}\n/**\n * True iff `implContent` has a `### Phase N: <RitualWord>…` phase whose every\n * checklist item is checked. The title must **start** with the ritual word\n * (after `Phase N:`) — matching a substring would misdetect a topic-named phase\n * like \"The /cleanup skill\" as the ritual phase (found by the cleanup-ritual\n * falsification, H1). Returns false when no such phase exists or any item under\n * it is unchecked. Gate sub-headers (`#### Phase N Verification`, etc.) do NOT\n * reset the phase — their items belong to it.\n */\nfunction isRitualPhaseTerminal(implContent, ritualWord) {\n const startsWith = new RegExp(`^${ritualWord}\\\\b`, \"i\");\n const lines = implContent.split(\"\\n\");\n let inPhase = false;\n let found = false;\n let itemCount = 0;\n for (const line of lines) {\n const phaseMatch = parsePhaseHeading(line);\n if (phaseMatch) {\n if (startsWith.test(phaseMatch.name)) {\n inPhase = true;\n found = true;\n }\n else {\n inPhase = false;\n }\n continue;\n }\n if (!inPhase)\n continue;\n // Leading whitespace allowed — nested sub-items count (round-2 F3; the\n // old column-0 anchor made an indented unchecked item invisible).\n if (/^\\s*-\\s+\\[[ xX]\\]/.test(line)) {\n itemCount++;\n if (/^\\s*-\\s+\\[ \\]/.test(line)) {\n return false; // an unchecked item inside the ritual phase\n }\n }\n }\n // An empty ritual phase is NOT terminal — a bare heading with zero items\n // must not vacuously satisfy the retrospective gate (round-2 F2).\n return found && itemCount > 0;\n}\n/** True iff a terminal `### Phase N: Cleanup …` phase exists in the impl body. */\nexport function isCleanupPhaseTerminal(implContent) {\n return isRitualPhaseTerminal(implContent, RITUAL_ORDER[1]);\n}\n/** True iff a terminal `### Phase N: Falsification …` phase exists — the default\n * phase-authoring falsify flow leaves one (no legacy log, not skipped). */\nexport function isFalsificationPhaseTerminal(implContent) {\n return isRitualPhaseTerminal(implContent, RITUAL_ORDER[0]);\n}\n/**\n * True iff the plan's impl.md has a terminal Cleanup phase. Reads\n * `<planRoot>/impl.md`; returns false when the file is absent.\n */\nexport function isCleanupComplete(planRoot) {\n const implPath = join(planRoot, \"impl.md\");\n if (!existsSync(implPath))\n return false;\n return isCleanupPhaseTerminal(readFileSync(implPath, \"utf-8\"));\n}\n/**\n * The composed retrospective Step 0 readiness check: a plan is ready to close\n * only when BOTH rituals are satisfied — each either complete or explicitly\n * skipped. This is the single source of truth the retrospective skill's Step 0\n * gate references for the cleanup requirement.\n *\n * Falsification is satisfied by a completed log OR the skip frontmatter; the\n * retrospective skill independently also honors the \"all falsification phases\n * terminal\" path from the phase-authoring falsify flow.\n */\nexport function checkRetrospectiveReadiness(planRoot, implContent) {\n const falsificationOk = isFalsificationComplete(planRoot) ||\n isFalsificationSkipped(implContent).skipped ||\n isFalsificationPhaseTerminal(implContent);\n const cleanupOk = isCleanupComplete(planRoot) || isCleanupSkipped(implContent).skipped;\n // The check the skill's text always promised and the code never performed:\n // a row left `written` after its phase closed is invisible to the two\n // ritual walks above, which see checkboxes and not rows.\n const body = matter(implContent).content;\n const nonTerminalRows = findNonTerminalRows(parseTrajectory(body), body).map((f) => f.row.id);\n const rowsOk = nonTerminalRows.length === 0;\n const missing = [];\n if (!falsificationOk)\n missing.push(\"falsification\");\n if (!cleanupOk)\n missing.push(\"cleanup\");\n if (!rowsOk)\n missing.push(\"rows\");\n return {\n falsificationOk,\n cleanupOk,\n rowsOk,\n nonTerminalRows,\n passes: missing.length === 0,\n missing,\n };\n}\n","import { phaseExists, phaseSequence } from \"../impl-headings.js\";\nimport { parseTrajectory } from \"./parser.js\";\nconst TELEMETRY_KEYWORDS = [\"alert\", \"metric\", \"otel\", \"dash0\", \"grafana\", \"threshold\"];\nconst REVIEW_KEYWORDS = [\n \"weekly\",\n \"monthly\",\n \"quarterly\",\n \"daily\",\n \"review\",\n \"spot-check\",\n \"audit\",\n];\nconst PLAN_REF = /\\b[a-z][a-z0-9-]*-[a-z][a-z0-9-]*\\b/g; // kebab-case identifiers that look like plan slugs\nconst CANARY_KEYWORDS = [\"staging\", \"canary\", \"smoke\", \"preflight\", \"pre-release\"];\nconst FEEDBACK_KEYWORDS = [\"feedback\", \"ticket\", \"support\", \"channel\", \"signal\"];\nfunction classifyMitigation(row) {\n const text = row.mitigation.toLowerCase();\n const hints = [];\n const hasAny = (words) => words.some((w) => text.includes(w));\n let kind = \"unclassified\";\n if (hasAny(TELEMETRY_KEYWORDS))\n kind = \"telemetry-alert\";\n else if (hasAny(CANARY_KEYWORDS))\n kind = \"canary-or-staging\";\n else if (hasAny(FEEDBACK_KEYWORDS))\n kind = \"feedback-signal\";\n else if (hasAny(REVIEW_KEYWORDS))\n kind = \"scheduled-review\";\n const planRefs = [...row.mitigation.matchAll(PLAN_REF)].map((m) => m[0]);\n if (planRefs.length > 0) {\n hints.push(...planRefs);\n if (kind === \"unclassified\")\n kind = \"downstream-plan\";\n }\n let warning = null;\n if (kind === \"unclassified\" || row.mitigation.trim().length < 20) {\n warning = `Mitigation for \"${row.name}\" is vague — expected a specific alert, review cadence, plan reference, or procedure. Got: \"${row.mitigation}\"`;\n }\n return { row, kind, hints, warning };\n}\n/**\n * Retrospective audit: classify every Deferred Verification row, flag any\n * whose mitigation text is too vague or unclassifiable. Returns findings\n * suitable for inclusion in a retrospective's \"What We Learned\" section\n * or a Graphiti audit episode.\n */\nexport function auditDeferredMitigations(trajectory) {\n return trajectory.deferred.map(classifyMitigation);\n}\n/**\n * Retrospective audit: surface trajectory rows that ended the plan in\n * `blocked` state. Blocked means \"was writable/written but regressed or\n * changed\" — if the plan closes with blocked rows, they should be\n * explicitly resolved (fix, move passesAt, or move to Deferred Verification).\n */\nexport function findBlockedRows(trajectory) {\n return trajectory.rows\n .filter((row) => row.state === \"blocked\")\n .map((row) => ({\n row,\n message: `Row ${row.id} ended plan in 'blocked' state — needs resolution (fix, reschedule, or move to Deferred Verification)`,\n }));\n}\n/**\n * Derive a concrete test file glob + runnable command from a trajectory\n * row's asserts text. Heuristic: extract identifiers (camelCase, kebab-case,\n * backtick-quoted code) and use the longest as a filename hint. Returns a\n * fallback `pnpm test` with a `-t` name filter if no filename hint found.\n *\n * The verify skill uses this to resolve phase-Verification items that say\n * \"T3 passes (...)\" into a real invocation. Best-effort — human can override.\n */\nexport function resolveTestIdCommand(trajectory, id) {\n const row = trajectory.rows.find((r) => r.id === id);\n if (!row)\n return null;\n const backtickMatches = [...row.asserts.matchAll(/`([^`]+)`/g)].map((m) => m[1]);\n const identifiers = [...row.asserts.matchAll(/\\b[a-zA-Z][a-zA-Z0-9_]{3,}\\b/g)].map((m) => m[0]);\n const keyword = backtickMatches.filter((s) => /[a-zA-Z]/.test(s)).sort((a, b) => b.length - a.length)[0] ??\n identifiers.sort((a, b) => b.length - a.length)[0] ??\n null;\n const fileGlob = keyword\n ? `**/*${keyword.toLowerCase().replace(/[^a-z0-9]/g, \"\")}*.test.ts`\n : null;\n const suggestedCommand = keyword\n ? `pnpm test -t \"${keyword}\"`\n : `pnpm test -t \"${row.asserts.slice(0, 40)}\"`;\n return { id, asserts: row.asserts, fileGlob, suggestedCommand };\n}\n/**\n * The states a row may end a plan in. `check-gates` uses the same three for\n * Gate B; `TERMINAL_STATES` in the parser is a different set (it includes\n * `written` — \"authored\", which is terminal for the test-first duty and not\n * for a close).\n */\nconst CLOSE_TERMINAL_STATES = new Set([\"passing\", \"skipped\", \"blocked\"]);\n/**\n * Close-out audit: rows that are not terminal although the phase they pass at\n * exists in the document.\n *\n * workbench-trust-fixes closed with two rows still `written` after eight phase\n * closes; `checkRetrospectiveReadiness` walked the ritual phases' checkboxes\n * and `auditPlanAtClose` reported `blocked` rows only, so a row left `written`\n * was invisible to both — while the retrospective skill promised \"every row\n * terminal\". A row whose `Passes at` names a phase the document does not have\n * is a forward reference and is not counted, the same rule `check-gates`\n * applies (carried into dawn-workbench-execution from hook-cwd-independence).\n */\nexport function findNonTerminalRows(trajectory, body) {\n const sequence = phaseSequence(body);\n return trajectory.rows\n .filter((row) => phaseExists({ kind: row.passesAtKind, number: row.passesAt }, sequence) &&\n !CLOSE_TERMINAL_STATES.has(row.state))\n .map((row) => ({\n row,\n message: `Row ${row.id} is still '${row.state}' although ${row.passesAtKind === \"test\" ? \"Test \" : \"\"}Phase ${row.passesAt} exists — a plan does not close with a row that never reached passing, skipped or blocked`,\n }));\n}\n/** Convenience — parse + audit + resolve in one call for the retrospective skill. */\nexport function auditPlanAtClose(body) {\n const trajectory = parseTrajectory(body);\n return {\n deferred: auditDeferredMitigations(trajectory),\n blocked: findBlockedRows(trajectory),\n nonTerminal: findNonTerminalRows(trajectory, body),\n };\n}\n","import { parsePhaseRef } from \"../impl-headings.js\";\nconst TRAJECTORY_HEADING = /^##\\s+Test Trajectory\\b/;\nconst DEFERRED_HEADING = /^###\\s+Deferred Verification\\b/;\nconst NEXT_SECTION_HEADING = /^#{1,3}\\s+/;\nconst VALID_STATES = new Set([\n \"planned\",\n \"writable\",\n \"written\",\n \"passing\",\n \"blocked\",\n \"skipped\",\n \"unknown\",\n]);\n/**\n * States in which a row's authoring obligation is discharged.\n *\n * **One definition on purpose.** The phase-close probe (`run/probe.ts`) and\n * `atdawn verify` (`verify/detect.ts`) both ask \"has this row been dealt with?\",\n * and if they answered differently a phase would close in one lane and not the\n * other. A second copy would not be a duplicated line — it would be a silent\n * divergence between two enforcement lanes, so the rule lives here.\n *\n * `hooks/check-gates.js` encodes the same set inline; that JS port is a\n * deliberate mirror under the existing hook-port convention (change the TS and\n * every JS port together), not an accidental third copy.\n */\nexport const TERMINAL_STATES = new Set([\n \"written\",\n \"passing\",\n \"skipped\",\n \"blocked\",\n]);\nconst VALID_KINDS = new Set([\n \"example\",\n \"property\",\n \"contract\",\n \"approval\",\n \"formal\",\n]);\nconst VALID_SCOPES = new Set([\"unit\", \"integration\", \"e2e\"]);\n/**\n * Parse a markdown table row into cells. Strips the leading/trailing pipe and\n * splits on unescaped pipes. Does NOT handle escaped pipes inside cell content\n * — the trajectory asserts column does not contain pipes in practice.\n */\nfunction parseTableRow(line) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"|\") || !trimmed.endsWith(\"|\"))\n return [];\n return trimmed\n .slice(1, -1)\n .split(\"|\")\n .map((cell) => cell.trim());\n}\n/**\n * Normalize a header cell to a canonical key. Case-insensitive, trims\n * whitespace, maps variant spellings to canonical names.\n */\nfunction normalizeHeader(header) {\n const normalized = header.toLowerCase().replace(/\\s+/g, \" \").trim();\n const aliases = {\n id: \"id\",\n asserts: \"asserts\",\n \"writable at\": \"writableAt\",\n \"passes at\": \"passesAt\",\n state: \"state\",\n kind: \"kind\",\n scope: \"scope\",\n };\n return aliases[normalized] ?? normalized;\n}\n/**\n * Parse a \"Phase N\" / \"Build Phase N\" / \"Test Phase N\" cell into a number.\n * Returns NaN if the cell is not a valid phase reference — the validator\n * catches NaN and emits a specific error.\n */\nfunction parsePhaseReference(cell) {\n return parsePhaseRef(cell)?.number ?? Number.NaN;\n}\n/** Which sequence a phase cell counts in. Build when unspecified. */\nfunction parsePhaseReferenceKind(cell) {\n return parsePhaseRef(cell)?.kind ?? \"build\";\n}\nfunction parseState(cell) {\n const normalized = cell.toLowerCase().trim();\n if (VALID_STATES.has(normalized)) {\n return normalized;\n }\n return \"unknown\";\n}\nfunction parseOptionalKind(cell) {\n const normalized = cell.toLowerCase().trim();\n if (!normalized)\n return undefined;\n if (VALID_KINDS.has(normalized)) {\n return normalized;\n }\n return undefined;\n}\n/**\n * Parse the optional `Test` cell into file paths. Comma-separated, backticks\n * stripped (authors write them as code spans). Empty → undefined, which the\n * verifier reads as \"not checkable\", distinct from \"checked and passed\".\n */\nfunction parseOptionalTestRefs(cell) {\n const refs = cell\n .split(\",\")\n .map((ref) => ref\n .trim()\n .replace(/^`+|`+$/g, \"\")\n .trim())\n .filter((ref) => ref.length > 0);\n return refs.length > 0 ? refs : undefined;\n}\nfunction parseOptionalScope(cell) {\n const normalized = cell.toLowerCase().trim();\n if (!normalized)\n return undefined;\n if (VALID_SCOPES.has(normalized)) {\n return normalized;\n }\n return undefined;\n}\n/**\n * Extract the block of lines under `## Test Trajectory` up to the next\n * heading of equal or lesser depth. The trajectory block includes the\n * main table; the `### Deferred Verification` subsection is extracted\n * separately.\n */\nfunction extractTrajectoryBlock(lines) {\n let inTrajectory = false;\n let inDeferred = false;\n const tableLines = [];\n const deferredLines = [];\n for (const line of lines) {\n if (TRAJECTORY_HEADING.test(line)) {\n inTrajectory = true;\n inDeferred = false;\n continue;\n }\n if (!inTrajectory)\n continue;\n if (DEFERRED_HEADING.test(line)) {\n inDeferred = true;\n continue;\n }\n // A new top-level or second-level heading ends the trajectory block.\n // A third-level heading other than Deferred Verification also ends it.\n if (NEXT_SECTION_HEADING.test(line) && !DEFERRED_HEADING.test(line)) {\n const depth = line.match(/^(#{1,6})/)?.[1].length ?? 0;\n if (depth <= 2)\n break;\n // A ### heading that isn't Deferred Verification ends the trajectory\n if (depth === 3)\n break;\n }\n if (inDeferred) {\n deferredLines.push(line);\n }\n else {\n tableLines.push(line);\n }\n }\n return { tableLines, deferredLines };\n}\n/**\n * Parse a Deferred Verification block. Each deferred item is a top-level\n * bullet with three sub-bullets:\n *\n * - **{name}**\n * - reason: {text}\n * - would require: {text}\n * - mitigation: {text}\n */\nfunction parseDeferredBlock(lines) {\n const rows = [];\n let current = null;\n const flush = () => {\n if (current && current.name !== undefined) {\n rows.push({\n name: current.name,\n reason: current.reason ?? \"\",\n wouldRequire: current.wouldRequire ?? \"\",\n mitigation: current.mitigation ?? \"\",\n });\n }\n current = null;\n };\n for (const rawLine of lines) {\n const line = rawLine.replace(/\\s+$/, \"\");\n // Top-level bullet with bolded name: - **{name}**\n const nameMatch = line.match(/^-\\s+\\*\\*(.+?)\\*\\*\\s*(?:—\\s*(.*))?$/);\n if (nameMatch) {\n flush();\n current = { name: nameMatch[1].trim() };\n // Handle inline one-line form: - **Name** — reason: X — would require: Y — mitigation: Z\n const rest = nameMatch[2];\n if (rest) {\n const reasonMatch = rest.match(/reason:\\s*([^—]+?)(?:\\s*—|$)/i);\n const wrMatch = rest.match(/would require:\\s*([^—]+?)(?:\\s*—|$)/i);\n const mitMatch = rest.match(/mitigation:\\s*(.+)$/i);\n if (reasonMatch)\n current.reason = reasonMatch[1].trim();\n if (wrMatch)\n current.wouldRequire = wrMatch[1].trim();\n if (mitMatch)\n current.mitigation = mitMatch[1].trim();\n }\n continue;\n }\n if (!current)\n continue;\n // Sub-bullet: - reason: / - would require: / - mitigation:\n const subMatch = line.match(/^\\s+-\\s+(reason|would require|mitigation):\\s*(.*)$/i);\n if (subMatch) {\n const key = subMatch[1].toLowerCase();\n const value = subMatch[2].trim();\n if (key === \"reason\")\n current.reason = value;\n else if (key === \"would require\")\n current.wouldRequire = value;\n else if (key === \"mitigation\")\n current.mitigation = value;\n }\n }\n flush();\n return rows;\n}\n/**\n * Parse the trajectory table. Returns rows parsed from the GFM table under\n * `## Test Trajectory`. Unknown column headers are ignored. Rows with\n * missing required columns are skipped — the validator catches structural\n * problems and surfaces them.\n */\nfunction parseTrajectoryTable(lines) {\n const tableLines = lines.filter((line) => line.trim().startsWith(\"|\"));\n if (tableLines.length < 2)\n return [];\n const headerCells = parseTableRow(tableLines[0]);\n const separator = parseTableRow(tableLines[1]);\n // Separator row looks like |----|----|... — every cell is dashes/colons\n const isSeparator = separator.every((cell) => /^:?-+:?$/.test(cell));\n if (!isSeparator)\n return [];\n const columnKeys = headerCells.map(normalizeHeader);\n const rows = [];\n for (let i = 2; i < tableLines.length; i++) {\n const cells = parseTableRow(tableLines[i]);\n if (cells.length !== columnKeys.length)\n continue;\n const record = {};\n for (let j = 0; j < columnKeys.length; j++) {\n record[columnKeys[j]] = cells[j];\n }\n const id = record.id?.trim();\n const asserts = record.asserts?.trim();\n if (!id || !asserts)\n continue;\n rows.push({\n id,\n asserts,\n writableAt: parsePhaseReference(record.writableAt ?? \"\"),\n passesAt: parsePhaseReference(record.passesAt ?? \"\"),\n writableAtKind: parsePhaseReferenceKind(record.writableAt ?? \"\"),\n passesAtKind: parsePhaseReferenceKind(record.passesAt ?? \"\"),\n state: parseState(record.state ?? \"\"),\n kind: parseOptionalKind(record.kind ?? \"\"),\n scope: parseOptionalScope(record.scope ?? \"\"),\n test: parseOptionalTestRefs(record.test ?? \"\"),\n });\n }\n return rows;\n}\n/**\n * Parse a Test Trajectory from an impl.md body (the content after the\n * frontmatter). Returns an empty trajectory with `present: false` when the\n * `## Test Trajectory` section is absent. Never throws — errors are surfaced\n * by the validator, not the parser.\n */\nexport function parseTrajectory(body) {\n const lines = body.split(\"\\n\");\n const hasTrajectory = lines.some((line) => TRAJECTORY_HEADING.test(line));\n if (!hasTrajectory) {\n return { rows: [], deferred: [], present: false };\n }\n const { tableLines, deferredLines } = extractTrajectoryBlock(lines);\n const rows = parseTrajectoryTable(tableLines);\n const deferred = parseDeferredBlock(deferredLines);\n return { rows, deferred, present: true };\n}\n","/**\n * The impl.md parser — string in, phases out. No filesystem.\n *\n * Split from `impl-parser.ts` (admin-ui-phase-progress, Build Phase 3) so the\n * admin UI's browser components can import the one parser: the browser test\n * runtime externalizes `node:fs`, and a static `existsSync` import makes the\n * whole module unloadable there. `impl-parser.ts` re-exports everything here\n * and adds the two functions that touch disk (`resolveImplPath`, `parseImpl`);\n * the `@infinitedusky/indusk-mcp/impl-parser` subpath points at this file.\n */\nimport matter from \"gray-matter\";\nimport { FORWARD_INTELLIGENCE_HEADING, fencedLineMask, gateHeading, parsePhaseHeading, } from \"./impl-headings.js\";\n// One entry per `GateKind` in `impl-headings.ts`. OTel was missing here while\n// the hook-side port (`hooks/_impl-phases.js`) recognised it — the two walks\n// disagreed about a gate kind until admin-ui-phase-progress (A5).\nconst GATE_SUFFIXES = {\n Verification: \"verification\",\n OTel: \"otel\",\n Context: \"context\",\n Document: \"document\",\n};\nfunction parseChecklistItems(lines) {\n const items = [];\n for (const line of lines) {\n const match = line.match(/^-\\s+\\[([ x])\\]\\s+(.*)/);\n if (match) {\n items.push({\n checked: match[1] === \"x\",\n text: match[2].trim(),\n });\n }\n }\n return items;\n}\nexport function parseImplString(raw) {\n // gray-matter touches `Buffer` on every call, which the admin's browser\n // runtime does not have. The admin always hands this a frontmatter-free\n // body (its reader split the frontmatter server-side), so only a document\n // that actually opens with a fence goes through gray-matter — identical\n // output for every Node caller, and loadable in the browser.\n const { data, content } = raw.startsWith(\"---\")\n ? matter(raw)\n : { data: {}, content: raw };\n const title = data.title ?? \"\";\n const status = data.status ?? \"\";\n const lines = content.split(\"\\n\");\n const phases = [];\n let currentPhase = null;\n let currentGateType = \"implementation\";\n let currentGateLines = [];\n let inForwardIntelligence = false;\n let forwardIntelligenceLines = [];\n function flushGate() {\n if (!currentPhase)\n return;\n const items = parseChecklistItems(currentGateLines);\n if (items.length > 0) {\n currentPhase.gates.push({ type: currentGateType, items });\n }\n currentGateLines = [];\n }\n // Fenced blocks are content, not structure: a Test Phase 1 deferral may\n // carry the deferred test's body, and that body contains lines shaped\n // exactly like checklist items and gate headings.\n const fenced = fencedLineMask(lines);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const line = lines[lineIndex];\n if (fenced[lineIndex])\n continue;\n // Phase header: ### Phase N / ### Build Phase N / ### Test Phase N\n const phaseMatch = parsePhaseHeading(line);\n if (phaseMatch) {\n flushGate();\n if (currentPhase)\n phases.push(currentPhase);\n currentPhase = {\n number: phaseMatch.number,\n kind: phaseMatch.kind,\n ordinal: phases.length,\n name: phaseMatch.name,\n gates: [],\n blocker: null,\n forwardIntelligence: null,\n };\n currentGateType = \"implementation\";\n currentGateLines = [];\n continue;\n }\n // Forward Intelligence header: #### Phase N Forward Intelligence\n const fiMatch = line.match(FORWARD_INTELLIGENCE_HEADING);\n if (fiMatch) {\n flushGate();\n inForwardIntelligence = true;\n forwardIntelligenceLines = [];\n continue;\n }\n // Gate header: #### Phase N Verification|OTel|Context|Document\n const gateMatch = line.match(gateHeading(\"(Verification|OTel|Context|Document)\"));\n if (gateMatch) {\n if (inForwardIntelligence && currentPhase) {\n currentPhase.forwardIntelligence = forwardIntelligenceLines.join(\"\\n\").trim() || null;\n inForwardIntelligence = false;\n }\n flushGate();\n // [1] is the phase number, [2] the gate kind — see gateHeading().\n currentGateType = GATE_SUFFIXES[gateMatch[2]];\n continue;\n }\n // Blocker line: blocker: description\n if (currentPhase) {\n const blockerMatch = line.match(/^blocker:\\s+(.*)/);\n if (blockerMatch) {\n currentPhase.blocker = blockerMatch[1].trim();\n continue;\n }\n }\n if (inForwardIntelligence) {\n forwardIntelligenceLines.push(line);\n }\n else {\n currentGateLines.push(line);\n }\n }\n // Flush last forward intelligence, gate, and phase\n if (inForwardIntelligence && currentPhase) {\n currentPhase.forwardIntelligence = forwardIntelligenceLines.join(\"\\n\").trim() || null;\n }\n flushGate();\n if (currentPhase)\n phases.push(currentPhase);\n return { title, status, phases };\n}\nexport function getPhaseCompletion(phase) {\n const uncheckedByGate = {\n implementation: [],\n verification: [],\n otel: [],\n context: [],\n document: [],\n };\n let totalItems = 0;\n let checkedItems = 0;\n for (const gate of phase.gates) {\n for (const item of gate.items) {\n totalItems++;\n if (item.checked) {\n checkedItems++;\n }\n else {\n uncheckedByGate[gate.type].push(item.text);\n }\n }\n }\n return {\n phase: phase.number,\n ref: { kind: phase.kind, number: phase.number },\n ordinal: phase.ordinal,\n name: phase.name,\n complete: checkedItems === totalItems,\n totalItems,\n checkedItems,\n uncheckedByGate,\n };\n}\nexport function getAllPhaseCompletions(parsed) {\n return parsed.phases.map(getPhaseCompletion);\n}\n/**\n * The phase a `{kind, number}` reference names, or undefined. Number alone is\n * ambiguous once an impl has two sequences (Test Phase 1 / Build Phase 1);\n * every reader that keys a phase should key it this way.\n */\nexport function findPhase(parsed, ref) {\n return parsed.phases.find((p) => p.kind === ref.kind && p.number === ref.number);\n}\n","import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPlanningDir } from \"./config.js\";\nimport { fencedLineMask } from \"./impl-headings.js\";\nimport { DOCUMENT_POSITIONS, isFinishedDocumentStatus, nextRequiredDocument } from \"./lifecycle.js\";\nimport { leastAdvancedPaperStatus, paperNextStep, readPaper, } from \"./papers/summary.js\";\nimport { isCleanSegment } from \"./path-segment.js\";\nimport { readWorkflow } from \"./workflow-types.js\";\n// Re-exported so the `planning/plan-parser` subpath and existing imports keep\n// working; the definitions live in papers/summary.ts.\nexport { PAPER_STATUSES, paperContentHash, paperIsStale, } from \"./papers/summary.js\";\nfunction parseFrontmatter(filePath) {\n if (!existsSync(filePath))\n return { frontmatter: null };\n const raw = readFileSync(filePath, \"utf-8\");\n try {\n const { data } = matter(raw);\n return {\n frontmatter: {\n title: data.title ?? \"\",\n date: data.date ?? \"\",\n status: data.status ?? \"\",\n },\n };\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] frontmatter parse error in ${filePath}: ${message}\\n`);\n return {\n frontmatter: null,\n parseError: { file: filePath, message },\n };\n }\n}\n/**\n * One rule for what a plan's stage is when papers are present: a lifecycle\n * document always wins, and only a folder with no lifecycle document at all\n * is a paper-stage plan. So no existing plan changes stage by gaining a\n * paper, and a folder of papers stops reading as unknown / \"Create a brief\".\n */\nfunction resolvePlanStage(walked, papers, type) {\n if (walked.stage === \"unknown\" && papers.length > 0) {\n return {\n stage: \"paper\",\n stageStatus: leastAdvancedPaperStatus(papers),\n nextStep: paperNextStep(papers),\n };\n }\n return {\n stage: walked.stage,\n stageStatus: walked.stageStatus,\n nextStep: determineNextStep(walked.stage, walked.stageStatus, type, walked.parseError),\n };\n}\nfunction parseDependsOn(filePath) {\n if (!existsSync(filePath))\n return [];\n const content = readFileSync(filePath, \"utf-8\");\n const depsMatch = content.match(/## Depends On\\s*\\n([\\s\\S]*?)(?=\\n## |\\n$|$)/);\n if (!depsMatch)\n return [];\n const deps = [];\n for (const line of depsMatch[1].split(\"\\n\")) {\n const match = line.match(/^-\\s+`?(?:\\.indusk\\/)?planning\\/([^/`]+)\\/?`?/);\n if (match) {\n deps.push(match[1]);\n }\n }\n return deps;\n}\nfunction determineStage(planDir, docs) {\n // Walk stages in reverse to find the most advanced document\n for (let i = DOCUMENT_POSITIONS.length - 1; i >= 0; i--) {\n const stage = DOCUMENT_POSITIONS[i];\n const file = `${stage}.md`;\n if (docs.includes(file)) {\n const result = parseFrontmatter(join(planDir, file));\n if (result.parseError) {\n return {\n stage: \"malformed\",\n stageStatus: \"parse-error\",\n parseError: result.parseError,\n };\n }\n return { stage, stageStatus: result.frontmatter?.status ?? \"unknown\" };\n }\n }\n return { stage: \"unknown\", stageStatus: \"unknown\" };\n}\nfunction determineNextStep(stage, stageStatus, type, parseError) {\n if (stage === \"malformed\" && parseError) {\n return `Fix YAML frontmatter at ${parseError.file}: ${parseError.message}`;\n }\n if (stage === \"unknown\")\n return \"Create a brief\";\n if (isFinishedDocumentStatus(stageStatus)) {\n const next = nextRequiredDocument(stage, type);\n if (next)\n return `Create ${next}`;\n return \"Done\";\n }\n if (stageStatus === \"in-progress\") {\n return `Continue ${stage}`;\n }\n return `Review ${stage} (status: ${stageStatus})`;\n}\n/**\n * The plan's `workflow:` declaration. It lives in the brief; a research-only\n * plan has no brief, so its research document is read instead. Unreadable\n * frontmatter reads as nothing declared — the parse error is reported by the\n * stage walk, and a second report here would name the same file twice.\n */\nfunction readDeclaredWorkflow(planDir, documents) {\n const source = documents.includes(\"brief.md\")\n ? \"brief.md\"\n : documents.includes(\"research.md\")\n ? \"research.md\"\n : null;\n if (source === null)\n return readWorkflow(undefined);\n try {\n const raw = readFileSync(join(planDir, source), \"utf-8\");\n return readWorkflow(matter(raw).data.workflow, rawFrontmatterValue(raw, \"workflow\"));\n }\n catch {\n return readWorkflow(undefined);\n }\n}\n/**\n * The text written after `key:` on its own line of a document's frontmatter,\n * or null when that line is empty or absent (a value written on the lines\n * below it, like a block list, has no text there). Line-anchored, so a longer\n * key that ends in this one is not it.\n */\nfunction rawFrontmatterValue(raw, key) {\n const block = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(raw)?.[1];\n if (block === undefined)\n return null;\n const text = new RegExp(`^${key}:[ \\\\t]*(.*)$`, \"m\").exec(block)?.[1]?.trim();\n return text ? text : null;\n}\nexport function parsePlan(planDir) {\n const name = planDir.split(\"/\").pop() ?? \"\";\n const entries = readdirSync(planDir).filter((f) => f.endsWith(\".md\"));\n const walked = determineStage(planDir, entries);\n const dependencies = parseDependsOn(join(planDir, \"brief.md\"));\n const papers = entries\n .map((file) => readPaper(planDir, file))\n .filter((p) => p !== null);\n const declared = readDeclaredWorkflow(planDir, entries);\n const { stage, stageStatus, nextStep } = resolvePlanStage(walked, papers, declared.type);\n return {\n name,\n stage,\n stageStatus,\n nextStep,\n dependencies,\n documents: entries,\n ...(declared.type !== null && { workflow: declared.type }),\n ...(declared.type === null &&\n declared.declared !== null && { workflowDeclared: declared.declared }),\n ...(papers.length > 0 && { papers }),\n ...(walked.parseError && { parseError: walked.parseError }),\n };\n}\n// A declaration name must be a single clean path segment — it gets joined into\n// filesystem paths (`join(planningDir, name, \"master.md\")`) and rendered\n// verbatim in the sidebar. The guard now lives in `lib/path-segment.ts`,\n// shared with workbench repo names, which are the same question.\n/**\n * Read a frontmatter key as a string array; anything else yields [].\n * Non-string entries, non-segment names, and duplicates are dropped —\n * duplicates collapse to first occurrence so declared order is preserved.\n */\nfunction stringArray(data, key) {\n const value = data[key];\n if (!Array.isArray(value))\n return [];\n const out = [];\n for (const entry of value) {\n if (typeof entry !== \"string\" || !isCleanSegment(entry))\n continue;\n if (out.includes(entry))\n continue;\n out.push(entry);\n }\n return out;\n}\n/**\n * Frontmatter of a `master.md`, or null when absent/unreadable/malformed.\n *\n * gray-matter throws on malformed YAML in plain Node but returns `data: {}`\n * inside vitest — so this treats both the throw and the empty-object case as\n * \"no declaration\", which is the same safe outcome either way.\n */\nfunction readMasterFrontmatter(masterPath) {\n if (!existsSync(masterPath))\n return null;\n try {\n return matter(readFileSync(masterPath, \"utf-8\")).data;\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] master frontmatter unreadable in ${masterPath}: ${message}\\n`);\n return null;\n }\n}\n/**\n * Read the plan hierarchy declarations from a planning directory.\n *\n * Never throws and never reports a plan: a missing file, absent key, or\n * malformed YAML each degrade to empty, which renders as today's flat list.\n * Losing structure is acceptable; losing a plan is not.\n */\nexport function readPlanDeclarations(planningDir) {\n const empty = { parents: [], roadmap: [], subplans: {} };\n if (!existsSync(planningDir))\n return empty;\n const rootPath = join(planningDir, \"master.md\");\n const rootData = readMasterFrontmatter(rootPath);\n const parents = rootData ? stringArray(rootData, \"parents\") : [];\n const roadmap = rootData ? stringArray(rootData, \"roadmap\") : [];\n const root = existsSync(rootPath)\n ? { name: \"master\", title: rootTitle(rootPath, rootData) }\n : undefined;\n // A folder's own master.md is what makes it a parent in practice, so read\n // every candidate — those named in `parents:` plus any plan carrying a\n // master.md — and let the presence of children decide. This keeps a stale\n // `parents:` entry from suppressing a real declaration, and vice versa.\n const candidates = new Set(parents);\n try {\n for (const entry of readdirSync(planningDir, { withFileTypes: true })) {\n if (entry.isDirectory() && existsSync(join(planningDir, entry.name, \"master.md\"))) {\n candidates.add(entry.name);\n }\n }\n }\n catch {\n // Unreadable planning dir — fall through with whatever `parents:` gave us.\n }\n const subplans = {};\n for (const parent of candidates) {\n const data = readMasterFrontmatter(join(planningDir, parent, \"master.md\"));\n if (!data)\n continue;\n subplans[parent] = stringArray(data, \"subplans\");\n }\n return { ...(root ? { root } : {}), parents, roadmap, subplans };\n}\nexport function parseAllPlans(projectRoot) {\n const planningDir = getPlanningDir(projectRoot);\n if (!existsSync(planningDir))\n return [];\n return readdirSync(planningDir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => {\n try {\n return parsePlan(join(planningDir, d.name));\n }\n catch (err) {\n // Defense-in-depth: parsePlan should never throw post-1.31.6 (the\n // frontmatter parser catches YAML errors and returns a malformed\n // PlanSummary). If something else fails (filesystem race,\n // unexpected I/O error), return a placeholder so one broken plan\n // doesn't take down the whole list.\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] error parsing ${d.name}: ${message}\\n`);\n return {\n name: d.name,\n stage: \"malformed\",\n stageStatus: \"parse-error\",\n nextStep: `Fix plan directory ${d.name}: ${message}`,\n dependencies: [],\n documents: [],\n parseError: { file: join(planningDir, d.name), message },\n };\n }\n })\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n/**\n * The root master's display title: frontmatter `title`, else the first `# `\n * heading of the document BODY, else \"master\".\n *\n * The body, not the raw file (A28): the real root master carries `# …` YAML\n * comment lines inside its frontmatter, and a raw-file match titled the whole\n * sidebar with the first of them. Fenced code is masked for the same reason —\n * a shell comment is not a heading.\n */\nfunction rootTitle(masterPath, data) {\n const declared = data?.title;\n if (typeof declared === \"string\" && declared.trim() !== \"\")\n return declared.trim();\n try {\n const raw = readFileSync(masterPath, \"utf-8\");\n const body = raw.startsWith(\"---\") ? matter(raw).content : raw;\n const lines = body.split(\"\\n\");\n const fenced = fencedLineMask(lines);\n for (const [i, line] of lines.entries()) {\n if (fenced[i])\n continue;\n const heading = line.match(/^#\\s+(.+)$/);\n if (heading)\n return heading[1].trim();\n }\n }\n catch {\n // unreadable — fall through to the folder-ish default\n }\n return \"master\";\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { readPublishedRecord } from \"./provenance.js\";\n/**\n * Papers as documents: the status vocabulary, the summary the parser reports,\n * the content hash a publish records, and staleness derived from it.\n *\n * Lifted out of `plan-parser.ts` (writing-skill cleanup) because the parser\n * is the lifecycle-document parser and this is a different document kind;\n * the parser re-exports the public names so the `planning/plan-parser`\n * subpath and every existing import keep working. This module must not\n * import the parser.\n */\n/**\n * Papers — prose documents that declare `kind: paper` in frontmatter and live\n * beside (or instead of) the lifecycle documents. Declared, never inferred\n * from filenames: a thesis, a shape document, and an essay can all be papers,\n * and nothing about a name says which. See `.indusk/planning/writing-skill/adr.md`.\n */\nexport const PAPER_STATUSES = [\"draft\", \"accepted\", \"published\"];\n/**\n * The content hash a publish records and staleness compares against.\n *\n * Hashes the document with `status` and the `published` block removed, so\n * writing either back after a publish does not change what it hashes; a\n * publish computes this on the pre-write content and the next read computes\n * it on the written file and gets the same answer.\n */\nexport function paperContentHash(raw) {\n const { data, content } = matter(raw);\n const rest = { ...data };\n delete rest.published;\n delete rest.status;\n return `sha256:${createHash(\"sha256\").update(matter.stringify(content, rest)).digest(\"hex\")}`;\n}\nfunction isPaperStatus(value) {\n return typeof value === \"string\" && PAPER_STATUSES.includes(value);\n}\n/**\n * One document read as a paper; null when it does not declare `kind: paper`.\n * A document whose frontmatter cannot be parsed cannot declare anything, so\n * it is not a paper here — the lifecycle walk reports it if it is a lifecycle\n * document, and the admin's raw view shows it either way.\n */\nexport function readPaper(planDir, file) {\n const raw = readFileSync(join(planDir, file), \"utf-8\");\n let data;\n try {\n data = matter(raw).data;\n }\n catch {\n return null;\n }\n if (data.kind !== \"paper\")\n return null;\n const status = isPaperStatus(data.status) ? data.status : \"malformed\";\n return {\n file,\n title: typeof data.title === \"string\" ? data.title : file,\n status,\n stale: paperIsStale(raw, data, status),\n };\n}\n/**\n * The staleness policy, in one place: a published paper is stale when its\n * current content hash differs from the one the publish recorded, or when\n * no hash was recorded at all (published by hand; cannot be confirmed\n * current, so it reads stale rather than reassuringly fresh). Anything not\n * yet published has nothing to be stale against. The publish step records\n * exactly what this compares, so the two must agree here, not in two places.\n */\nexport function paperIsStale(raw, data, status) {\n if (status !== \"published\")\n return false;\n const published = readPublishedRecord(data);\n const recorded = typeof published?.hash === \"string\" ? published.hash : null;\n return recorded === null || recorded !== paperContentHash(raw);\n}\nconst PAPER_STATUS_ORDER = {\n malformed: 0,\n draft: 1,\n accepted: 2,\n published: 3,\n};\n/** The least-advanced paper's status; `malformed` outranks everything so it surfaces. */\nexport function leastAdvancedPaperStatus(papers) {\n return papers.reduce((least, p) => (PAPER_STATUS_ORDER[p.status] < PAPER_STATUS_ORDER[least] ? p.status : least), \"published\");\n}\nexport function paperNextStep(papers) {\n const malformed = papers.find((p) => p.status === \"malformed\");\n if (malformed) {\n return `Fix paper status in ${malformed.file} (expected ${PAPER_STATUSES.join(\" | \")})`;\n }\n const draft = papers.find((p) => p.status === \"draft\");\n if (draft)\n return `Review paper: ${draft.file}`;\n const owed = papers.filter((p) => p.status === \"accepted\" || p.stale).length;\n if (owed > 0)\n return `Publish ${owed} paper(s)`;\n return \"Done\";\n}\n","/**\n * The provenance a publish writes back into the paper's frontmatter.\n *\n * A text edit, deliberately not a gray-matter round trip: the round trip\n * re-dumps every key, and js-yaml turns an unquoted `date: 2026-09-09` into\n * an ISO timestamp and strips quotes it considers unnecessary. Every publish\n * would then rewrite lines it has no business touching. This replaces only\n * the `status` line and the `published` block, and leaves every other byte\n * of the frontmatter where it was.\n */\n/** The `published` block of a parsed frontmatter, or undefined when absent or not an object. */\nexport function readPublishedRecord(data) {\n const block = data.published;\n return block !== null && typeof block === \"object\" ? block : undefined;\n}\nconst FENCE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n/;\nexport function withProvenance(raw, prov) {\n const match = FENCE.exec(raw);\n if (!match)\n throw new Error(\"paper has no frontmatter fence to write provenance into\");\n const kept = [];\n let inPublishedBlock = false;\n for (const line of match[1].split(/\\r?\\n/)) {\n if (/^status:/.test(line)) {\n inPublishedBlock = false;\n continue;\n }\n if (/^published:/.test(line)) {\n inPublishedBlock = true;\n continue;\n }\n if (inPublishedBlock && /^[ \\t]/.test(line))\n continue;\n inPublishedBlock = false;\n kept.push(line);\n }\n // Commits are quoted: an all-digit short sha would otherwise read as a number.\n kept.push(\"status: published\", \"published:\", ` destination: ${prov.destination}`, ` path: ${prov.path}`, ` commit: \"${prov.commit}\"`, ` source_commit: \"${prov.source_commit}\"`, ` hash: ${prov.hash}`);\n return `---\\n${kept.join(\"\\n\")}\\n---\\n${raw.slice(match[0].length)}`;\n}\n","import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPlanningDir } from \"../config.js\";\nimport { parsePlan } from \"../plan-parser.js\";\nimport { getQuietWindowDays } from \"./config.js\";\nimport { recorded } from \"./incidents.js\";\nimport { readPromises } from \"./registry.js\";\nimport { openMaintenancePhasesIn } from \"./reopen.js\";\n/**\n * What a closed plan is doing after it closed (day-monitor, ADR D7, D8).\n *\n * An archived plan normally reads `archived`. It is back in motion when an\n * incident appended a Maintenance phase to its impl and that phase has an\n * unchecked item — **reopened**: the plan tools list it active and it reads\n * executing that phase. Otherwise, a plan holding a behaviour promise is in\n * **monitor** while `now − max(closed, lastViolation) < window`: `closed` is\n * the date on its retrospective's \"Landed on main at …\" line (else the\n * retrospective's `date`), `lastViolation` the latest `last_seen` among its\n * promises' incidents, and `window` the quiet window. Derived from files\n * only; nothing here opens a socket.\n */\nexport const ARCHIVE_DIR = \"archive\";\nconst DAY_MS = 86_400_000;\n/** When the plan closed: its retrospective's landing date, else its `date`. Null without a retrospective. */\nexport function closedAt(planDir) {\n const path = join(planDir, \"retrospective.md\");\n if (!existsSync(path))\n return null;\n const text = readFileSync(path, \"utf-8\");\n const landed = /Landed on main at[^\\n]*?(\\d{4}-\\d{2}-\\d{2})/.exec(text)?.[1];\n if (landed)\n return new Date(`${landed}T00:00:00Z`);\n const date = matter(text).data.date;\n const d = date instanceof Date ? date : typeof date === \"string\" ? new Date(date) : null;\n return d && !Number.isNaN(d.getTime()) ? d : null;\n}\nfunction registryOf(projectRoot) {\n const read = readPromises(projectRoot);\n if (read.ok)\n return read.registry;\n return \"partial\" in read ? read.partial : null;\n}\n/** The plan's quiet window, when it is inside one. */\nexport function monitorWindow(projectRoot, name, planDir, registry, now = new Date()) {\n if (!registry)\n return null;\n const held = registry.promises.filter((p) => p.owner === name && p.kind === \"behaviour\" && p.state !== \"retired\");\n if (held.length === 0)\n return null;\n const closed = closedAt(planDir);\n if (!closed)\n return null;\n const names = new Set(held.map((p) => p.name));\n let lastViolation = null;\n for (const i of registry.incidents) {\n if (!names.has(i.promise))\n continue;\n const seen = recorded(join(registry.dir, i.file)).lastSeen ?? i.date;\n const d = new Date(seen);\n if (!Number.isNaN(d.getTime()) && (!lastViolation || d > lastViolation))\n lastViolation = d;\n }\n const restarted = lastViolation !== null && lastViolation > closed;\n const anchor = restarted && lastViolation ? lastViolation : closed;\n const windowDays = getQuietWindowDays(projectRoot);\n const elapsedDays = Math.max(0, (now.getTime() - anchor.getTime()) / DAY_MS);\n if (elapsedDays >= windowDays)\n return null;\n return {\n windowDays,\n elapsedDays: Math.round(elapsedDays * 10) / 10,\n restartedAt: restarted && lastViolation ? lastViolation.toISOString() : null,\n };\n}\nfunction archiveDir(projectRoot) {\n return join(getPlanningDir(projectRoot), ARCHIVE_DIR);\n}\n/** The archived plan's folder, or null when there is none by that name. */\nexport function archivedPlanDir(projectRoot, name) {\n const dir = join(archiveDir(projectRoot), name);\n return existsSync(join(dir)) && name !== \"\" && !name.includes(\"/\") ? dir : null;\n}\n/**\n * What an archived plan is doing, as facts: its open Maintenance phases and\n * its quiet window. The admin passes this to `derivePlanPosition` as\n * `afterClose`; `archivedPlan` builds the tools' summary from it.\n */\nexport function afterClose(projectRoot, name, planDir, registry = registryOf(projectRoot), now = new Date()) {\n const reopened = openMaintenancePhasesIn(planDir);\n return {\n reopened,\n monitor: reopened.length > 0 ? null : monitorWindow(projectRoot, name, planDir, registry, now),\n };\n}\n/** One archived plan as the plan tools report it. */\nexport function archivedPlan(projectRoot, name, registry = registryOf(projectRoot), now = new Date()) {\n const dir = archivedPlanDir(projectRoot, name);\n if (!dir)\n return null;\n const summary = parsePlan(dir);\n const { reopened, monitor } = afterClose(projectRoot, name, dir, registry, now);\n if (reopened.length > 0) {\n return {\n ...summary,\n stage: \"impl\",\n stageStatus: \"in-progress\",\n nextStep: `Work ${reopened[0]}, then check its phase off`,\n archived: true,\n reopened,\n monitor: null,\n };\n }\n if (monitor) {\n return {\n ...summary,\n stage: \"monitor\",\n stageStatus: \"monitor\",\n nextStep: `Quiet window: ${Math.floor(monitor.elapsedDays)} of ${monitor.windowDays} days${monitor.restartedAt ? `, restarted ${monitor.restartedAt.slice(0, 10)}` : \"\"}`,\n archived: true,\n reopened,\n monitor,\n };\n }\n return {\n ...summary,\n stage: \"archived\",\n stageStatus: \"archived\",\n archived: true,\n reopened,\n monitor: null,\n };\n}\n/** Archived plans that are back in motion — reopened or in monitor — the ones the active list must show. */\nexport function archivedInMotion(projectRoot) {\n const dir = archiveDir(projectRoot);\n if (!existsSync(dir))\n return [];\n const registry = registryOf(projectRoot);\n const now = new Date();\n return readdirSync(dir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => archivedPlan(projectRoot, d.name, registry, now))\n .filter((p) => p !== null && (p.reopened.length > 0 || p.monitor !== null));\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { setList, setScalar } from \"./frontmatter-edit.js\";\nimport { INCIDENTS_SUBDIR, NOT_YET_FIXED, UNWRITTEN_ROOT_CAUSE, } from \"./vocabulary.js\";\nfunction iso(d) {\n return d.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\nfunction stringList(v) {\n return Array.isArray(v) ? v.map(String) : [];\n}\n/** Trace ids and `last_seen` an incident file already records. */\nexport function recorded(path) {\n const data = matter(readFileSync(path, \"utf-8\")).data;\n const last = data.last_seen;\n return {\n traces: stringList(data.traces),\n lastSeen: last instanceof Date ? iso(last) : typeof last === \"string\" ? last : null,\n };\n}\nfunction newIncidentId(dir, promise, day) {\n const base = `i-${day}-${promise}`;\n if (!existsSync(join(dir, `${base}.md`)))\n return base;\n for (let n = 2;; n++) {\n if (!existsSync(join(dir, `${base}-${n}.md`)))\n return `${base}-${n}`;\n }\n}\n/**\n * A string that arrived on a span is untrusted input to a plan document.\n *\n * `environment` and `symptom` come from a deployed system, and they are\n * written into committed YAML and markdown. A newline in `environment` adds\n * frontmatter keys — a duplicate key makes js-yaml throw, which takes the\n * whole registry down; a newline in `symptom` forges the `## Root cause`\n * section the registry insists a person writes (A28).\n *\n * Collapsed, not escaped, and not refused: a violation is still real when the\n * system reporting it sends something odd, and losing it would be the worse\n * failure. The oddness is kept visible rather than silently dropped.\n */\nexport function oneLine(value) {\n return value.replace(/[\\r\\n\\u2028\\u2029]+/g, \" \").trim();\n}\nfunction incidentText(o) {\n const frontmatter = [\n `id: ${o.id}`,\n `promise: ${o.promise}`,\n `source: ${o.source}`,\n ...(o.environment ? [`environment: ${JSON.stringify(oneLine(o.environment))}`] : []),\n \"status: open\",\n `date: '${o.opened.slice(0, 10)}'`,\n `opened: '${o.opened}'`,\n `last_seen: '${o.lastSeen}'`,\n \"traces:\",\n ...o.traces.map((t) => ` - '${t}'`),\n ];\n return `---\\n${frontmatter.join(\"\\n\")}\\n---\\n\\n## Symptom\\n\\n${oneLine(o.symptom)}\\n\\n## Root cause\\n\\n${UNWRITTEN_ROOT_CAUSE}\\n\\n## Fix\\n\\n${NOT_YET_FIXED}\\n`;\n}\n/**\n * Record `violations` of `promise` in the registry at `registry.dir`. Returns\n * what changed, or null when every trace was already recorded.\n */\nexport function recordViolations(registry, promise, violations, source, now) {\n const dir = join(registry.dir, INCIDENTS_SUBDIR);\n const mine = registry.incidents.filter((i) => i.promise === promise.name);\n const known = new Set(mine.flatMap((i) => recorded(join(registry.dir, i.file)).traces));\n const fresh = violations.filter((v) => !known.has(v.traceId));\n if (fresh.length === 0)\n return null;\n const freshIds = [...new Set(fresh.map((v) => v.traceId))];\n const newest = fresh.reduce((a, b) => (b.at > a.at ? b : a));\n const open = mine.find((i) => i.status === \"open\");\n if (open) {\n const path = join(registry.dir, open.file);\n const before = recorded(path);\n const lastSeen = before.lastSeen && before.lastSeen > iso(newest.at) ? before.lastSeen : iso(newest.at);\n let text = readFileSync(path, \"utf-8\");\n text = setList(text, \"traces\", [...before.traces, ...freshIds]);\n text = setScalar(text, \"last_seen\", lastSeen);\n writeFileSync(path, text);\n ensurePromiseCarries(registry, promise, open.id);\n return { id: open.id, kind: \"extended\", traces: freshIds };\n }\n mkdirSync(dir, { recursive: true });\n const oldest = fresh.reduce((a, b) => (b.at < a.at ? b : a));\n const id = newIncidentId(dir, promise.name, iso(now).slice(0, 10));\n writeFileSync(join(dir, `${id}.md`), incidentText({\n id,\n promise: promise.name,\n source,\n opened: iso(oldest.at),\n lastSeen: iso(newest.at),\n traces: freshIds,\n symptom: newest.symptom ?? \"The span marked the promise violated and carried no symptom.\",\n // The newest violation's environment, and no guess when it has\n // none: one server holds staging and production, and an incident\n // that names the wrong one sends a person to the wrong logs.\n environment: newest.environment,\n }));\n ensurePromiseCarries(registry, promise, id);\n return { id, kind: \"opened\", traces: freshIds };\n}\n/** An enforced promise with an open incident moves to known-violated, and lists it. */\nfunction ensurePromiseCarries(registry, promise, id) {\n const path = join(registry.dir, promise.file);\n const before = readFileSync(path, \"utf-8\");\n let text = before;\n if (!promise.incidents.includes(id))\n text = setList(text, \"incidents\", [...promise.incidents, id]);\n if (promise.state === \"enforced\")\n text = setScalar(text, \"state\", \"known-violated\");\n if (text !== before)\n writeFileSync(path, text);\n}\n","import { existsSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shouldEmitOtelGate } from \"../config.js\";\nimport { parseImplString } from \"../impl-parser-core.js\";\nimport { appendLateRow } from \"../trajectory/append-row.js\";\n/**\n * Reopening a plan is an appended phase, in place (day-monitor, ADR D7).\n *\n * An incident sends the promise's owning plan back to work: its impl gains\n * `### Build Phase N: Maintenance — <incident>` with its gates (OTel too when\n * the project's `otel.role` asks for it, as the validator will), numbered\n * after the owner's own build phases. The plan folder never moves — dozens of\n * pointers cite archive paths — and an owner with no impl gets one holding\n * only this phase. A plan whose impl already has the incident's Maintenance\n * phase is left alone, so a second pass writes nothing.\n */\nexport const MAINTENANCE_TITLE = \"Maintenance\";\nexport function maintenanceHeadingName(incidentId) {\n return `${MAINTENANCE_TITLE} — ${incidentId}`;\n}\n/** The owner's folder: active wins over archived, as everywhere else. */\nexport function ownerDir(planRoot, owner) {\n for (const dir of [\n join(planRoot, \".indusk\", \"planning\", owner),\n join(planRoot, \".indusk\", \"planning\", \"archive\", owner),\n ]) {\n if (existsSync(dir) && statSync(dir).isDirectory())\n return dir;\n }\n return null;\n}\nfunction maintenancePhase(n, incidentId, otelGate, rowId) {\n const otel = otelGate\n ? [\n `#### Build Phase ${n} OTel`,\n \"\",\n \"- [ ] The fixed code still marks the promise on its span (`indusk.promise`, `indusk.promise.outcome`)\",\n \"\",\n ]\n : [];\n return [\n `### Build Phase ${n}: ${maintenanceHeadingName(incidentId)}`,\n \"\",\n `- [ ] Write the root cause in the incident (\\`.indusk/promises/incidents/${incidentId}.md\\`)`,\n \"- [ ] Fix: a code site, a widened test, or a revised promise\",\n \"\",\n `#### Build Phase ${n} Verification`,\n \"\",\n rowId\n ? `- [ ] ${rowId}: the test that reproduces the incident passes, and the promise is seen upheld after the fix (\\`indusk promises status\\`)`\n : \"- [ ] The promise is seen upheld after the fix (`indusk promises status`)\",\n \"\",\n ...otel,\n `#### Build Phase ${n} Context`,\n \"\",\n \"- [ ] CLAUDE.md, if the fix changes a convention\",\n \"\",\n `#### Build Phase ${n} Document`,\n \"\",\n \"- [ ] The incident's Fix section\",\n \"\",\n ].join(\"\\n\");\n}\n/** Append the incident's Maintenance phase to `owner`'s impl. */\nexport function reopenOwner(planRoot, owner, incidentId, promise, \n/** The owner's live folder when it is assigned to a worktree (day-monitor A29); else found here. */\nliveDir) {\n const dir = liveDir ?? ownerDir(planRoot, owner);\n if (!dir)\n return { reopened: false, reason: \"no-owner\" };\n const implPath = join(dir, \"impl.md\");\n // The phase carries every gate the validator will ask this project for.\n const otelGate = shouldEmitOtelGate(planRoot);\n if (!existsSync(implPath)) {\n writeFileSync(implPath, `---\\ntitle: \"${owner} — maintenance\"\\nstatus: in-progress\\n---\\n\\n# ${owner} — maintenance\\n\\n## Checklist\\n\\n${maintenancePhase(1, incidentId, otelGate, null)}`);\n return { reopened: true, impl: implPath, phase: 1 };\n }\n const text = readFileSync(implPath, \"utf-8\");\n const parsed = parseImplString(text);\n const name = maintenanceHeadingName(incidentId);\n if (parsed.phases.some((p) => p.kind === \"build\" && p.name === name)) {\n return { reopened: false, reason: \"already\" };\n }\n const next = Math.max(0, ...parsed.phases.filter((p) => p.kind === \"build\").map((p) => p.number)) + 1;\n const withRow = appendLateRow(text, next, {\n asserts: `${promise} holds again after ${incidentId}: the test that reproduces it, named by its root cause, passes`,\n why: `the test that reproduces ${incidentId} is decided by its root cause, which this Maintenance phase writes first`,\n });\n writeFileSync(implPath, `${withRow.text.replace(/\\n*$/, \"\\n\")}\\n${maintenancePhase(next, incidentId, otelGate, withRow.id)}`);\n return { reopened: true, impl: implPath, phase: next };\n}\n/** An impl with an unchecked item under a Maintenance phase — the plan is reopened. */\nexport function openMaintenancePhases(implText) {\n return parseImplString(implText)\n .phases.filter((p) => p.name.startsWith(`${MAINTENANCE_TITLE} — `) &&\n p.gates.some((g) => g.items.some((i) => !i.checked)))\n .map((p) => p.name);\n}\n/** `openMaintenancePhases` of the impl in plan folder `dir`; none when there is no impl. */\nexport function openMaintenancePhasesIn(dir) {\n const implPath = join(dir, \"impl.md\");\n return existsSync(implPath) ? openMaintenancePhases(readFileSync(implPath, \"utf-8\")) : [];\n}\n","import { execFile } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\n/**\n * The git primitives more than one subsystem needs.\n *\n * Deliberately *not* inside `verify/` or `shape/`: both of those are domain\n * modules with their own vocabulary (verify's `assertGitRepo` refuses in\n * verify's words; shape's scoping answers shape's question), and a primitive\n * that lives in one domain's folder gets copied by the next domain rather than\n * imported. That is exactly what happened here — `shape/changed.ts` carried a\n * byte-identical copy of verify's runner, down to the maxBuffer.\n */\nconst execFileAsync = promisify(execFile);\n/** Run git in `root` and return trimmed stdout. Throws on non-zero exit. */\nexport async function git(root, ...args) {\n const { stdout } = await execFileAsync(\"git\", args, { cwd: root, maxBuffer: 32 * 1024 * 1024 });\n return stdout.trim();\n}\n/**\n * HEAD of `root`. Throws when there is none — not a repository, or an unborn\n * branch. The one spelling of \"which commit is this tree at\" (A19): verify's\n * ledger, the commit cadence's record and the run loop's trailer each wrote\n * their own before it lived here.\n */\nexport async function headSha(root) {\n return git(root, \"rev-parse\", \"--verify\", \"HEAD\");\n}\n/**\n * HEAD of `root`, or null when there is nothing to name — an unborn branch\n * (a greenfield repo before its first commit) or no repository at all.\n * Nothing to attest is a fact to record as absence, not an exception to\n * throw through a tool call after the edit already applied (A17).\n */\nexport async function headShaOrNull(root) {\n try {\n return (await headSha(root)) || null;\n }\n catch {\n return null;\n }\n}\n/**\n * What changed since `sha`, keeping tracked and untracked apart.\n *\n * The untracked half is not a nicety. `git diff` reports tracked modifications\n * only, so an agent that writes real code without `git add`ing it produced a\n * diff containing nothing but the plan file — and looked, to phantom detection,\n * exactly like an agent that wrote nothing at all. Half-in/half-out on the\n * working tree is the bug; either stance is defensible, the mixture is not.\n *\n * Partitioned rather than unioned because the two halves carry different\n * evidence about *when* they happened. Committed work is placed in time by its\n * commit; untracked work has only its mtime, which is the only way to tell a\n * scratch file left by an earlier phase from this phase's real work. Callers\n * that do not care can union the two — `changedPathsSince` does exactly that.\n */\nexport async function changedPathsPartitioned(root, sha) {\n const committed = await git(root, \"diff\", \"--name-only\", sha, \"HEAD\");\n const unstaged = await git(root, \"diff\", \"--name-only\", \"HEAD\");\n const untracked = await git(root, \"ls-files\", \"--others\", \"--exclude-standard\");\n const clean = (raw) => raw\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n return {\n tracked: [...new Set([...clean(committed), ...clean(unstaged)])],\n untracked: clean(untracked),\n };\n}\nexport function snapshotPaths(root, rels) {\n const taken = new Map();\n for (const rel of rels) {\n const path = join(root, rel);\n taken.set(rel, existsSync(path) ? readFileSync(path, \"utf-8\") : null);\n }\n return taken;\n}\n/**\n * Put the tree back to a snapshot: unstage every path, then rewrite each\n * to its prior content or remove it. A path that was a staged rename's new\n * name is removed and its old name rewritten, because both were in the\n * snapshot. Lifted out of `papers/publish.ts` (writing-skill cleanup) on the\n * rule that a git primitive kept in a domain folder gets copied by the next.\n */\nexport async function restorePaths(root, taken) {\n try {\n await git(root, \"reset\", \"-q\", \"--\", ...taken.keys());\n }\n catch {\n // Nothing was staged, or a path was never tracked. The rewrite below is what matters.\n }\n for (const [rel, previous] of taken) {\n const path = join(root, rel);\n if (previous === null) {\n rmSync(path, { force: true });\n }\n else {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, previous);\n }\n }\n}\n/**\n * Every working tree of the repository `root` belongs to, main first — the\n * same answer from the trunk and from any linked worktree, because git keeps\n * the list in the shared git directory. Throws when `root` is not in a\n * repository; callers decide what that means.\n */\nexport async function listWorktrees(root) {\n return parseWorktreeList(await git(root, \"worktree\", \"list\", \"--porcelain\"));\n}\n/**\n * The one parse of `git worktree list --porcelain` output: blank-line-separated\n * blocks, each opening with `worktree <path>`; the first block is the main\n * working tree. Pure, so a caller with its own runner (`decision.ts` injects\n * one for tests) parses the same way `listWorktrees` does.\n */\n/** The value of the first `<prefix><value>` line in a porcelain block, trimmed. */\nfunction field(lines, prefix) {\n return lines\n .find((l) => l.startsWith(prefix))\n ?.slice(prefix.length)\n .trim();\n}\nexport function parseWorktreeList(porcelain) {\n const entries = [];\n for (const block of porcelain.split(/\\n\\s*\\n/)) {\n const lines = block.split(\"\\n\");\n const path = field(lines, \"worktree \");\n if (!path)\n continue;\n const ref = field(lines, \"branch \");\n entries.push({\n path,\n branch: ref ? ref.replace(/^refs\\/heads\\//, \"\") : null,\n main: entries.length === 0,\n prunable: lines.some((l) => l === \"prunable\" || l.startsWith(\"prunable \")),\n });\n }\n return entries;\n}\n","import { closeSync, openSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nconst DEFAULTS = {\n timeoutMs: 5000,\n pollIntervalMs: 25,\n staleAfterMs: 30_000,\n};\nfunction isEexistError(err) {\n return Boolean(err && typeof err === \"object\" && \"code\" in err && err.code === \"EEXIST\");\n}\nfunction tryAcquire(lockPath) {\n try {\n // O_EXCL — atomic create, fails if file exists.\n const fd = openSync(lockPath, \"wx\");\n writeFileSync(fd, `${process.pid}\\n${Date.now()}\\n`);\n closeSync(fd);\n return true;\n }\n catch (err) {\n if (isEexistError(err))\n return false;\n throw err;\n }\n}\nfunction isStale(lockPath, staleAfterMs) {\n try {\n const st = statSync(lockPath);\n return Date.now() - st.mtimeMs > staleAfterMs;\n }\n catch {\n // Lockfile vanished between checks — treat as not-stale (the holder released it).\n return false;\n }\n}\nfunction sleepSync(ms) {\n const end = Date.now() + ms;\n // Synchronous spin — acceptable since lock waits are <100ms in practice and\n // the agent CLI is single-purpose. Async lock would require restructuring\n // every call site.\n const buf = new SharedArrayBuffer(4);\n const view = new Int32Array(buf);\n Atomics.wait(view, 0, 0, Math.max(1, end - Date.now()));\n}\n/**\n * Acquire an exclusive lock on `lockPath`. Blocks (synchronously) until the\n * lock is held or `timeoutMs` elapses. Returns a release function the caller\n * must invoke in a `finally` block.\n *\n * Throws on lockfile contention exceeding `timeoutMs`.\n */\nexport function acquireLock(lockPath, opts = {}) {\n const { timeoutMs, pollIntervalMs, staleAfterMs } = { ...DEFAULTS, ...opts };\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (tryAcquire(lockPath)) {\n return () => {\n try {\n rmSync(lockPath, { force: true });\n }\n catch {\n // Best-effort release. If another process took our lock (because we\n // went stale), nothing to do.\n }\n };\n }\n if (isStale(lockPath, staleAfterMs)) {\n // Take it over.\n try {\n rmSync(lockPath, { force: true });\n }\n catch {\n // Race — someone else removed it first; fine.\n }\n continue;\n }\n sleepSync(pollIntervalMs);\n }\n throw new Error(`Timed out acquiring lock on ${lockPath} after ${timeoutMs}ms`);\n}\n/**\n * Convenience: run `fn` while holding the lock; release on return or throw.\n */\nexport function withLock(lockPath, fn, opts) {\n const release = acquireLock(lockPath, opts);\n try {\n return fn();\n }\n finally {\n release();\n }\n}\n","import { existsSync, readFileSync, realpathSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { withLock } from \"../agents/lock.js\";\nimport { isUsableSegment } from \"../path-segment.js\";\nimport { gitCommonDirOf } from \"./layout.js\";\n/**\n * The plan-worktree assignment record: where it lives, what it holds, and the\n * only code that reads or writes it.\n *\n * `indusk-plan-worktrees.json` in the repository's shared git directory —\n * common to the trunk and every worktree of this clone, per-machine, never in\n * a working tree. Writers take `<record>.lock` from the read to the write, so\n * two sessions assigning at once both land. Readers parse it structurally and\n * report a malformed file rather than guessing around it. The resolver\n * (`plan-worktrees.ts`) and the commands (`plan-worktree-commands.ts`) go\n * through here; nothing else touches the file.\n */\n/**\n * Which copy of a plan is the live one.\n *\n * Every checkout of a project holds a copy of every plan — the trunk's and\n * one per worktree — and under worktree-per-plan the one being worked is in\n * its worktree, not on the trunk. This module answers \"where is plan X's live\n * copy\" for every reader (the MCP plan tools, the admin), from any checkout.\n *\n * The answer comes from a record, never from matching names: the InDusk\n * worktree command writes an assignment when it creates or assigns a\n * worktree, and ends it at release. The record lives in the repository's\n * shared git directory — common to the trunk and every worktree of this\n * clone, per-machine like the worktrees it names, never part of any working\n * tree. Each read checks the record against git's own worktree list, and\n * every mismatch is reported as what it is: a worktree that is gone, a plan\n * with two live worktrees, a record that cannot be read. None is guessed.\n *\n * Inert in a workbench: plan documents there live at the workbench root and\n * never in a code worktree, so every plan reads from the plan root.\n */\nexport const RECORD_FILE = \"indusk-plan-worktrees.json\";\nconst RECORD_VERSION = 1;\n/** A refusal from `assignPlan`, `releasePlan` or `createPlanWorktree`: the message names what was refused. */\nexport class PlanWorktreeRefusal extends Error {\n name = \"PlanWorktreeRefusal\";\n}\n/** Why one assignment is unusable, or null. The reader and the writer share it. */\nfunction assignmentProblem(value, index) {\n if (typeof value !== \"object\" || value === null)\n return `assignment ${index} is not an object`;\n const a = value;\n for (const key of [\"plan\", \"path\", \"branch\", \"at\"]) {\n if (typeof a[key] !== \"string\" || a[key].trim() === \"\") {\n return `assignment ${index} has no ${key}`;\n }\n }\n if (!isUsableSegment(a.plan))\n return `assignment ${index} names an unusable plan \"${a.plan}\"`;\n if (!a.path.startsWith(\"/\"))\n return `assignment ${index} has a relative path \"${a.path}\"`;\n return null;\n}\nfunction parseRecord(text, file) {\n let data;\n try {\n data = JSON.parse(text);\n }\n catch (err) {\n return { ok: false, file, problem: `not valid JSON (${err.message})` };\n }\n if (typeof data !== \"object\" || data === null)\n return { ok: false, file, problem: \"not an object\" };\n const body = data;\n if (body.version !== RECORD_VERSION) {\n return {\n ok: false,\n file,\n problem: `version is ${JSON.stringify(body.version)}, expected ${RECORD_VERSION}`,\n };\n }\n if (!Array.isArray(body.assignments))\n return { ok: false, file, problem: \"assignments is not a list\" };\n for (let i = 0; i < body.assignments.length; i++) {\n const problem = assignmentProblem(body.assignments[i], i);\n if (problem)\n return { ok: false, file, problem };\n }\n return { ok: true, file, assignments: body.assignments };\n}\nfunction recordPath(anyCheckout) {\n const common = gitCommonDirOf(anyCheckout);\n if (!common)\n throw new PlanWorktreeRefusal(`${anyCheckout} is not inside a git repository`);\n // The common dir exists whenever git reports it; realpath settles /var ↔ /private/var.\n return join(realpathSync(common), RECORD_FILE);\n}\nfunction readRecordFile(file) {\n if (!existsSync(file))\n return { ok: true, file, assignments: [] };\n let text;\n try {\n text = readFileSync(file, \"utf-8\");\n }\n catch (err) {\n return { ok: false, file, problem: `unreadable (${err.message})` };\n }\n return parseRecord(text, file);\n}\nexport async function readRecord(anyCheckout) {\n return readRecordFile(recordPath(anyCheckout));\n}\n/**\n * Read the record, decide, and write it back, holding `<record>.lock` across\n * all three. Two sessions assigning at once would otherwise each write the\n * list they read and the later write would drop the earlier assignment — that\n * plan silently reading the trunk again. The lock is the project's lock for\n * processes on one machine (`lib/agents/lock.ts`); `change` returns the new\n * list, or null to write nothing.\n */\nexport function updateRecord(anyCheckout, change) {\n const file = recordPath(anyCheckout);\n return withLock(`${file}.lock`, () => {\n const record = readRecordFile(file);\n if (!record.ok)\n refuseUnreadable(record);\n const { next, result } = change(record.assignments);\n if (next)\n writeRecord(file, next);\n return result;\n });\n}\n/** Write the record atomically: a reader never sees half a file. */\nfunction writeRecord(file, assignments) {\n const temp = `${file}.${process.pid}.tmp`;\n writeFileSync(temp, `${JSON.stringify({ version: RECORD_VERSION, assignments }, null, 2)}\\n`);\n renameSync(temp, file);\n}\n/** The one refusal for a record that cannot be read — every writer and pre-check uses it. */\nexport function refuseUnreadable(record) {\n throw new PlanWorktreeRefusal(`the assignment record ${record.file} cannot be read: ${record.problem} — fix or remove it first`);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { checkRetrospectiveReadiness } from \"@infinitedusky/indusk-mcp/cleanup/gate\";\nimport {\n isFalsificationComplete,\n type LogEntry,\n readFalsificationLog,\n} from \"@infinitedusky/indusk-mcp/falsification/log\";\nimport { parseImplString } from \"@infinitedusky/indusk-mcp/impl-parser\";\nimport {\n derivePlanPosition,\n type PlanPositionState,\n} from \"@infinitedusky/indusk-mcp/lifecycle\";\nimport {\n type PaperSummary,\n type PlanDeclarations,\n parsePlan,\n readPlanDeclarations,\n} from \"@infinitedusky/indusk-mcp/planning/plan-parser\";\nimport { afterClose } from \"@infinitedusky/indusk-mcp/promises/after-close\";\nimport type { PhaseBoundaryRecord } from \"@infinitedusky/indusk-mcp/shape/boundary\";\nimport {\n parseTrajectory,\n type Trajectory,\n} from \"@infinitedusky/indusk-mcp/trajectory/parser\";\nimport type { WorkflowType } from \"@infinitedusky/indusk-mcp/workflow-types\";\nimport {\n type CopySource,\n copySource,\n type PlanCopy,\n resolvePlanCopies,\n} from \"@infinitedusky/indusk-mcp/worktree/plan-worktrees\";\nimport matter from \"gray-matter\";\nimport { type BoundaryRead, readProjectBoundaries } from \"./project-reader\";\n\n/**\n * The data layer for the admin UI. Reads `.indusk/planning/` and `.indusk/eval/`\n * directly from disk and reuses indusk-mcp's parsers (trajectory + falsification log)\n * — never duplicates parsing logic. If a parser needs a new export to support the\n * admin UI, add it to the original module rather than recreating it here.\n *\n * Server-side only (filesystem access). Designed to be called from Next.js server\n * components, not from client components.\n */\n\nexport interface DocumentFrontmatter {\n title?: string;\n status?: string;\n workflow?: string;\n date?: string;\n [key: string]: unknown;\n}\n\nexport interface ResearchData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface BriefData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface TestPlanData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface ADRData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface ImplData {\n frontmatter: DocumentFrontmatter;\n content: string;\n /** Parsed trajectory; undefined when the impl has no `## Test Trajectory` section. */\n trajectory?: Trajectory;\n}\n\nexport interface FalsificationData {\n entries: LogEntry[];\n complete: boolean;\n}\n\nexport interface RetroData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\n/**\n * A paper (a document declaring `kind: paper`) as the UI renders it: the\n * shared parser's summary — status, and staleness derived from the content\n * hash — plus the markdown body. Never re-parsed here; `parsePlan` owns it.\n */\nexport interface PaperEntry extends PaperSummary {\n content: string;\n}\n\nexport interface Plan {\n /** Folder name (kebab-case) under `.indusk/planning/` or `.indusk/planning/archive/`. */\n name: string;\n /** Effective status — pulled from impl.md, falling back to brief.md, or \"unknown\". */\n status: string;\n /** Whether the plan lives under archive/ vs the active planning dir. */\n archived: boolean;\n research?: ResearchData;\n brief?: BriefData;\n testPlan?: TestPlanData;\n adr?: ADRData;\n impl?: ImplData;\n falsification?: FalsificationData;\n retrospective?: RetroData;\n /** Every `kind: paper` document, in filename order, with its body. Absent when there are none. */\n papers?: PaperEntry[];\n /**\n * The plan's declared type, from the shared parser — `workflow:` in its\n * brief, or its research document when it has no brief. Null when nothing\n * is declared, or when the declared word is not a type (admin-plan-type).\n */\n workflow?: WorkflowType | null;\n /** The word that was declared, when it is not one of the types. */\n workflowDeclared?: string;\n /**\n * Where the plan stands in the lifecycle, derived from its documents, its\n * impl and the retrospective readiness gate (admin-ui-phase-progress).\n */\n position?: PlanPositionState;\n /** This plan's phase-boundary records, when the project's record file read cleanly. */\n boundaries?: PhaseBoundaryRecord[];\n /**\n * Set when `.indusk/phase-boundary.jsonl` has a malformed line. The reader\n * throws rather than skipping it, and the page shows the error rather than\n * guessing an active phase from a partial record.\n */\n boundaryError?: string;\n /** The worktree this plan was read from, when it is assigned to one (admin-plan-worktrees). */\n worktree?: CopySource[\"worktree\"];\n /**\n * Why the trunk copy is shown although the plan is assigned: its worktree\n * is gone, or two live worktrees claim it. `detail` names every path.\n */\n copyProblem?: CopySource[\"copyProblem\"];\n /**\n * The plan folder has moved into `archive/` in its worktree: archived on the\n * branch by the retrospective, awaiting the landing's release.\n */\n archivedInWorktree?: CopySource[\"archivedInWorktree\"];\n /**\n * Set when the assignment record cannot be read. The plan then carries its\n * name and nothing read from any copy: which copy is live is unknown, and\n * the page says so rather than drawing one.\n */\n copyError?: { file: string; problem: string };\n /** True when ANY document in the plan failed to parse (malformed YAML frontmatter). */\n malformed?: boolean;\n /**\n * For each document that failed to parse, the raw file contents (so the UI\n * can show a \"Raw\" view per T13). Only populated when `malformed: true`.\n * Keys match the document filenames (e.g., `brief.md`, `impl.md`).\n */\n rawDocuments?: Record<string, string>;\n}\n\nexport interface Scorecard {\n timestamp: string;\n changeId?: string;\n mode?: string;\n summary?: string;\n questions?: unknown;\n graphitiWrites?: number;\n /**\n * Registered project name — injected by the cross-project scorecards\n * walker (`app/scorecards/page.tsx`) after reading each project's\n * results.log. Not present in the underlying log file.\n */\n project?: string;\n [key: string]: unknown;\n}\n\nconst PLANNING_DIR = \".indusk/planning\";\nconst ARCHIVE_DIR = \"archive\";\nconst EVAL_RESULTS = \".indusk/eval/results.log\";\nconst MASTER_FILE = \"master.md\";\n\nconst DOC_FILES = [\n \"research.md\",\n \"brief.md\",\n \"test-plan.md\",\n \"adr.md\",\n \"impl.md\",\n \"falsification.md\",\n \"retrospective.md\",\n] as const;\n\n/**\n * Read a single document file and return parsed frontmatter + content. Returns\n * `null` if the file doesn't exist. Returns `{ malformed: true }` if the\n * frontmatter cannot be parsed.\n *\n * Detection strategy: gray-matter is unreliable about throwing on malformed\n * YAML — depending on the runtime / module-resolution path, js-yaml errors\n * may be swallowed and `data: {}` returned instead. We detect malformed by\n * the structural signal: a file that starts with `---\\n` and has a closing\n * `---` MUST yield a non-empty frontmatter block. If gray-matter returns a\n * truly empty `data` for such a file (and the block isn't actually empty\n * after trimming comments/whitespace), the parse silently failed.\n *\n * False-positive note: a legitimately empty frontmatter (`---\\n---\\nbody`)\n * also produces `data: {}` but with an empty block — we differentiate by\n * checking whether the block is non-trivial.\n */\nconst FRONTMATTER_RE = /^---\\n([\\s\\S]*?)\\n---/;\n\nasync function readDoc(\n planDir: string,\n filename: string,\n): Promise<\n | { frontmatter: DocumentFrontmatter; content: string }\n | { malformed: true; raw: string }\n | null\n> {\n const path = join(planDir, filename);\n if (!existsSync(path)) return null;\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return { malformed: true, raw: \"\" };\n }\n try {\n const parsed = matter(raw);\n const data = parsed.data as DocumentFrontmatter;\n const dataIsEmpty = Object.keys(data).length === 0;\n if (dataIsEmpty) {\n const fmMatch = raw.match(FRONTMATTER_RE);\n if (fmMatch) {\n const block = fmMatch[1].replace(/^\\s*#[^\\n]*$/gm, \"\").trim();\n if (block !== \"\") return { malformed: true, raw };\n }\n }\n return {\n frontmatter: data,\n content: parsed.content,\n };\n } catch {\n return { malformed: true, raw };\n }\n}\n\nfunction isMalformed(\n doc:\n | { malformed: true; raw: string }\n | { frontmatter: DocumentFrontmatter; content: string }\n | null,\n): doc is { malformed: true; raw: string } {\n return doc !== null && \"malformed\" in doc;\n}\n\nasync function readPlanFolder(\n planDir: string,\n name: string,\n archived: boolean,\n boundaries: BoundaryRead = { records: [] },\n /** The project root, for an archived plan's after-close facts (day-monitor). */\n projectRoot?: string,\n): Promise<Plan> {\n const docs = await Promise.all(DOC_FILES.map((f) => readDoc(planDir, f)));\n const [research, brief, testPlan, adr, impl, _falsification, retrospective] =\n docs;\n const malformed = docs.some((d) => isMalformed(d));\n const rawDocuments: Record<string, string> = {};\n for (let i = 0; i < docs.length; i++) {\n const doc = docs[i];\n if (doc !== null && isMalformed(doc)) {\n rawDocuments[DOC_FILES[i]] = doc.raw;\n }\n }\n\n let implData: ImplData | undefined;\n if (impl !== null && !isMalformed(impl)) {\n const trajectory = /^##\\s+Test Trajectory\\b/m.test(impl.content)\n ? parseTrajectory(impl.content)\n : undefined;\n implData = { ...impl, trajectory };\n }\n\n let falsificationData: FalsificationData | undefined;\n if (_falsification !== null) {\n try {\n const entries = readFalsificationLog(planDir);\n const complete = isFalsificationComplete(planDir);\n falsificationData = { entries, complete };\n } catch {\n // Library tolerates missing logs (returns []) but file-system errors\n // could throw. Treat as no-falsification rather than malformed-plan\n // since the file existing-but-unreadable is itself rare.\n }\n }\n\n // Papers come from the shared parser (status vocabulary, derived staleness)\n // with the body read once here for rendering.\n const parsed = parsePlan(planDir);\n const papers = parsed.papers\n ? await readPapers(planDir, parsed.papers)\n : undefined;\n\n // The plan bar's position: the same facts `list_plans` and the retrospective\n // gate read, composed by the lifecycle module rather than here.\n const readiness =\n impl !== null && !isMalformed(impl)\n ? checkRetrospectiveReadiness(\n planDir,\n readFileSync(join(planDir, \"impl.md\"), \"utf-8\"),\n )\n : null;\n // A closed plan may be back in motion — reopened by an incident, or in\n // `monitor` — derived from files by the same function `list_plans` reads.\n const after =\n archived && projectRoot\n ? afterClose(projectRoot, name, planDir)\n : undefined;\n const position = derivePlanPosition({\n summary: parsed,\n impl: implData ? parseImplString(implData.content) : null,\n readiness,\n archived,\n ...(after ? { afterClose: after } : {}),\n });\n const boundaryFields =\n \"error\" in boundaries\n ? { boundaryError: boundaries.error }\n : { boundaries: boundaries.records.filter((r) => r.plan === name) };\n\n const status =\n (implData?.frontmatter.status as string | undefined) ??\n (brief !== null && !isMalformed(brief)\n ? (brief.frontmatter.status as string | undefined)\n : undefined) ??\n // A papers-only plan reports the parser's paper-stage status rather than unknown.\n (parsed.stage === \"paper\" ? parsed.stageStatus : undefined) ??\n \"unknown\";\n\n return {\n name,\n status,\n position,\n workflow: parsed.workflow ?? null,\n ...(parsed.workflowDeclared\n ? { workflowDeclared: parsed.workflowDeclared }\n : {}),\n ...boundaryFields,\n ...(papers ? { papers } : {}),\n archived,\n research:\n research !== null && !isMalformed(research) ? research : undefined,\n brief: brief !== null && !isMalformed(brief) ? brief : undefined,\n testPlan:\n testPlan !== null && !isMalformed(testPlan) ? testPlan : undefined,\n adr: adr !== null && !isMalformed(adr) ? adr : undefined,\n impl: implData,\n falsification: falsificationData,\n retrospective:\n retrospective !== null && !isMalformed(retrospective)\n ? retrospective\n : undefined,\n ...(malformed ? { malformed: true, rawDocuments } : {}),\n };\n}\n\n/** Each paper's summary from the shared parser, plus its markdown body for rendering. */\nasync function readPapers(\n planDir: string,\n summaries: PaperSummary[],\n): Promise<PaperEntry[]> {\n return Promise.all(\n summaries.map(async (summary) => ({\n ...summary,\n content: matter(await readFile(join(planDir, summary.file), \"utf-8\"))\n .content,\n })),\n );\n}\n\nasync function listPlanFolders(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory() && e.name !== ARCHIVE_DIR)\n .map((e) => e.name)\n .sort();\n}\n\n/**\n * Read every plan in `.indusk/planning/` (skipping `archive/`). Each plan\n * gets a `Plan` object with whatever documents are present; missing optional\n * documents result in `undefined` fields rather than errors.\n */\nexport async function readActivePlans(projectRoot: string): Promise<Plan[]> {\n const resolved = await resolvePlanCopies(projectRoot);\n const root = resolved.projectRoot;\n const folders = await listPlanFolders(join(root, PLANNING_DIR));\n if (!resolved.ok) {\n const copyError = { file: resolved.file, problem: resolved.problem };\n return folders.map((name) => ({\n name,\n status: \"unknown\",\n archived: false,\n copyError,\n }));\n }\n // Boundary records are per checkout: a plan in a worktree opened its\n // phases there. Read each live root's file once.\n const boundariesByRoot = new Map<string, Promise<BoundaryRead>>();\n const boundariesOf = (at: string) => {\n let read = boundariesByRoot.get(at);\n if (!read) {\n read = readProjectBoundaries(at);\n boundariesByRoot.set(at, read);\n }\n return read;\n };\n return Promise.all(\n folders.map(async (name) => {\n const copy: PlanCopy = resolved.copies.get(name) ?? {\n plan: name,\n root,\n dir: join(root, PLANNING_DIR, name),\n source: \"trunk\",\n };\n const plan = await readPlanFolder(\n copy.dir,\n name,\n false,\n await boundariesOf(copy.root),\n );\n return { ...plan, ...copySource(copy) };\n }),\n );\n}\n\n/**\n * The project's worktrees as the sidebar shows them: those holding no plan\n * assignment, and the record's error when it cannot be read.\n */\nexport async function readProjectWorktrees(projectRoot: string): Promise<\n | {\n ok: true;\n unassigned: { name: string; path: string; branch: string | null }[];\n }\n | { ok: false; file: string; problem: string }\n> {\n const resolved = await resolvePlanCopies(projectRoot);\n if (!resolved.ok) {\n return { ok: false, file: resolved.file, problem: resolved.problem };\n }\n return { ok: true, unassigned: resolved.unassigned };\n}\n\n/**\n * Read every plan in `.indusk/planning/archive/`. Returns the same `Plan` shape\n * as `readActivePlans` but with `archived: true`.\n */\nexport async function readArchivedPlans(projectRoot: string): Promise<Plan[]> {\n const archiveDir = join(projectRoot, PLANNING_DIR, ARCHIVE_DIR);\n if (!existsSync(archiveDir)) return [];\n const entries = await readdir(archiveDir, { withFileTypes: true });\n const folders = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n const boundaries = await readProjectBoundaries(projectRoot);\n return Promise.all(\n folders.map((name) =>\n readPlanFolder(\n join(archiveDir, name),\n name,\n true,\n boundaries,\n projectRoot,\n ),\n ),\n );\n}\n\n/**\n * Parse `master.md` and return plan names in the order they appear in the\n * pipeline tables. Plan names come from markdown links like\n * `[plan-name](plan-name/brief.md)`. Plans listed by string only (no link)\n * are skipped — they don't have a folder yet.\n */\n/**\n * Plan hierarchy declarations for this project — parents, top-level order, and\n * each parent's ordered children.\n *\n * Reads frontmatter through the shared parser rather than scraping prose. The\n * previous implementation matched markdown links shaped `[name](name/doc.md)`\n * with a regex, which silently matched *nothing* against a master whose links\n * carry a `../` prefix — structure derived from prose fails quietly, which is\n * the worst way for it to fail.\n */\nexport function readPlanHierarchy(projectRoot: string): PlanDeclarations {\n return readPlanDeclarations(join(projectRoot, PLANNING_DIR));\n}\n\n/**\n * Top-level display order. Plans absent from the declaration keep today's\n * behaviour — they fall through to the \"Unordered\" group rather than vanishing.\n */\nexport function readMasterPlanOrder(projectRoot: string): string[] {\n return readPlanHierarchy(projectRoot).roadmap;\n}\n\n/**\n * The prose body of a plan's own `master.md` — a parent plan's sequence and\n * its reasoning. Returns null when the file is absent or malformed; the\n * detail view simply omits the prose. Subplan cards never depend on it —\n * declarations come from `readPlanHierarchy`, not from here.\n */\nexport async function readPlanMasterContent(\n projectRoot: string,\n planName: string,\n): Promise<string | null> {\n const doc = await readDoc(\n join(projectRoot, PLANNING_DIR, planName),\n MASTER_FILE,\n );\n if (doc === null || isMalformed(doc)) return null;\n return doc.content;\n}\n\n/**\n * Read `.indusk/eval/results.log` (jsonl, one scorecard per line) and return\n * scorecards filtered to the supplied date range. Malformed lines are skipped\n * silently — `results.log` is append-only and may contain partial entries from\n * crashed evaluator processes.\n */\nexport async function readEvalScorecards(\n projectRoot: string,\n planDateRange: { from: Date; to: Date },\n): Promise<Scorecard[]> {\n const path = join(projectRoot, EVAL_RESULTS);\n if (!existsSync(path)) return [];\n const raw = await readFile(path, \"utf-8\");\n const out: Scorecard[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const parsed = JSON.parse(trimmed) as Scorecard;\n if (typeof parsed.timestamp !== \"string\") continue;\n const ts = new Date(parsed.timestamp);\n if (Number.isNaN(ts.getTime())) continue;\n if (ts >= planDateRange.from && ts <= planDateRange.to) out.push(parsed);\n } catch {\n // Skip malformed lines — see fn doc.\n }\n }\n out.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n );\n return out;\n}\n\n// The research-directory reader (.indusk/research/) lives in\n// research-reader.ts — extracted in the dawn-ui-plan-grouping cleanup; it\n// shares nothing with plan parsing.\n","import { existsSync, readdirSync, realpathSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { getPlanningDir } from \"../config.js\";\nimport { git, listWorktrees } from \"../git.js\";\nimport { readRecord } from \"./plan-worktree-record.js\";\nimport { isWorkbench } from \"./repos.js\";\nexport { RECORD_FILE } from \"./plan-worktree-record.js\";\n/** The report fields for a copy; empty for a plain trunk copy or none. */\nexport function copySource(copy) {\n if (!copy)\n return {};\n if (copy.source === \"worktree\") {\n return copy.archivedInWorktree\n ? { worktree: copy.worktree, archivedInWorktree: true }\n : { worktree: copy.worktree };\n }\n if (\"problem\" in copy)\n return { copyProblem: { kind: copy.problem, detail: copy.detail } };\n return {};\n}\n/** Normalize a path for comparison: realpath when it exists (macOS `/var` ↔ `/private/var`), else resolved. */\n/** @internal — shared with the commands module. */\nexport function canonical(path) {\n try {\n return realpathSync(path);\n }\n catch {\n return resolve(path);\n }\n}\n/** Plan folders on the trunk, archive excluded — the inventory every copy is resolved for. */\n/** The trunk copy of `plan`: its folder in the project's planning dir (the same one `parseAllPlans` reads). */\nfunction trunkCopy(projectRoot, plan) {\n return { plan, root: projectRoot, dir: join(getPlanningDir(projectRoot), plan), source: \"trunk\" };\n}\nfunction trunkPlans(projectRoot) {\n const dir = getPlanningDir(projectRoot);\n if (!existsSync(dir))\n return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== \"archive\")\n .map((e) => e.name)\n .sort();\n}\n/**\n * The repository whose checkout `anyCheckout` is the top of, or null.\n *\n * Null when it is not in a repository, and also when it is a folder *inside*\n * one — an InDusk project nested in a larger repository. Assignments bind a\n * plan to a whole checkout, and the project's copy in a worktree would be a\n * subfolder of it; resolving that is not done here, so a nested project keeps\n * reading the folder it was asked about. Climbing to the enclosing repository\n * instead would list that repository's plans as this project's.\n */\nexport async function repositoryOf(anyCheckout) {\n let list;\n try {\n const top = await git(anyCheckout, \"rev-parse\", \"--show-toplevel\");\n if (canonical(top) !== canonical(anyCheckout))\n return null;\n list = await listWorktrees(anyCheckout);\n }\n catch {\n return null;\n }\n const main = list.find((w) => w.main);\n if (!main)\n return null;\n const linked = new Map();\n for (const w of list) {\n if (w.main || w.prunable || !existsSync(w.path))\n continue;\n const path = canonical(w.path);\n linked.set(path, { path, branch: w.branch });\n }\n return { projectRoot: canonical(main.path), linked };\n}\nfunction trunkCopies(projectRoot) {\n return new Map(trunkPlans(projectRoot).map((plan) => [plan, trunkCopy(projectRoot, plan)]));\n}\n/**\n * One plan's copy from its assignments, and the worktree paths those\n * assignments hold live. No assignment reads the trunk; one live worktree is\n * the copy; several live, or none left of those recorded, read the trunk and\n * say which.\n */\nfunction copyFor(plan, assignments, repo) {\n const trunk = trunkCopy(repo.projectRoot, plan);\n const mine = assignments.filter((a) => a.plan === plan);\n if (mine.length === 0)\n return { copy: trunk, live: [] };\n const live = mine.map((a) => canonical(a.path)).filter((path) => repo.linked.has(path));\n if (live.length === 1) {\n const [path] = live;\n const branch = repo.linked.get(path)?.branch ?? mine[0].branch;\n return { copy: worktreeCopy(plan, path, branch, trunk), live };\n }\n if (live.length > 1) {\n return {\n copy: {\n ...trunk,\n problem: \"doubled\",\n detail: `two live worktrees are assigned to ${plan}: ${live.join(\", \")}`,\n },\n live,\n };\n }\n return {\n copy: {\n ...trunk,\n problem: \"gone\",\n detail: `assigned worktree ${mine.map((a) => a.path).join(\", \")} no longer exists`,\n },\n live,\n };\n}\n/**\n * The copy in a live assigned worktree, found on disk: the active plan folder,\n * else the archived one (the retrospective moves it on the branch before the\n * landing releases), else the trunk's copy with the folder reported missing.\n */\nfunction worktreeCopy(plan, path, branch, trunk) {\n const worktree = { name: basename(path), path, branch };\n const planning = getPlanningDir(path);\n const active = join(planning, plan);\n if (existsSync(active))\n return { plan, root: path, dir: active, source: \"worktree\", worktree };\n const archived = join(planning, \"archive\", plan);\n if (existsSync(archived)) {\n return {\n plan,\n root: path,\n dir: archived,\n source: \"worktree\",\n worktree,\n archivedInWorktree: true,\n };\n }\n return {\n ...trunk,\n problem: \"missing\",\n detail: `plan folder missing in worktree ${path} (neither ${active} nor ${archived} exists)`,\n };\n}\n/**\n * Every plan's live copy, asked from any checkout of the project.\n *\n * A plan with no assignment reads from the trunk. One live assignment reads\n * from its worktree. Two live assignments, or assignments whose worktrees\n * are all gone, read from the trunk and say why. A record that cannot be\n * read is `ok: false` — there is no copy map to misuse.\n */\nexport async function resolvePlanCopies(anyCheckout) {\n const asked = canonical(anyCheckout);\n if (isWorkbench(asked)) {\n return { ok: true, projectRoot: asked, copies: trunkCopies(asked), unassigned: [] };\n }\n const repo = await repositoryOf(asked);\n if (!repo)\n return { ok: true, projectRoot: asked, copies: trunkCopies(asked), unassigned: [] };\n const record = await readRecord(asked);\n if (!record.ok)\n return { ok: false, projectRoot: repo.projectRoot, file: record.file, problem: record.problem };\n const copies = new Map();\n const claimed = new Set();\n for (const plan of trunkPlans(repo.projectRoot)) {\n const { copy, live } = copyFor(plan, record.assignments, repo);\n copies.set(plan, copy);\n for (const path of live)\n claimed.add(path);\n }\n const unassigned = [...repo.linked.values()]\n .filter((w) => !claimed.has(w.path))\n .map((w) => ({ name: basename(w.path), path: w.path, branch: w.branch }));\n return { ok: true, projectRoot: repo.projectRoot, copies, unassigned };\n}\n/** One plan's live copy. A plan with no trunk folder resolves to the trunk, where every reader already reports it missing. */\nexport async function livePlanCopy(anyCheckout, plan) {\n const all = await resolvePlanCopies(anyCheckout);\n if (!all.ok)\n return { ok: false, file: all.file, problem: all.problem };\n return {\n ok: true,\n copy: all.copies.get(plan) ?? trunkCopy(all.projectRoot, plan),\n };\n}\n"],"names":["readRawConfig","root","configPath","JSON","parse","readWorkbenchRepos","worktree","declared","Array","isArray","repos","wrapped_repo","name","seen","Set","entry","remote","has","add","path","worktrees","repo","trim","push","isWorkbench","shape","length","readSiblingParent","value","sibling_parent","readReposRoot","w","repos_root","undefined","resolveReposRoot","projectRoot","startsWith","NOT_A_WORKBENCH","repoDir","declaredRepoDirs","reposRoot","map","r","dir","variantClasses","passing","blocked","skipped","planned","writable","written","unknown","neutral","Badge","variant","className","children","rest","classes","filter","Boolean","join","BUILD_PHASE","String","raw","PHASE_HEADING","RegExp","buildPhaseHeadingFor","phase","ref","toPhaseRef","kind","number","gateHeadingFor","gate","address","phaseLabel","TEST_PHASE_HEADING","ANY_PHASE_HEADING","gateHeading","FORWARD_INTELLIGENCE_HEADING","parsePhaseHeading","line","test","exec","Number","parseInt","build","PHASE_REFERENCE","parsePhaseRef","cell","match","toLowerCase","FENCE","parseFence","m","char","info","findFenceClose","lines","start","open","i","candidate","fencedLineMask","mask","fill","close","j","unterminatedFenceLine","body","split","phaseSequence","fenced","out","heading","phaseExists","sequence","some","p","phaseOrdinal","exact","findIndex","lastBefore","WORKFLOW_TYPES","WORKFLOW_DEFINITIONS","feature","purpose","requires","skips","why","bugfix","refactor","spike","DOCUMENT_LABELS","research","brief","adr","impl","retrospective","readWorkflow","type","stringify","includes","absentDocumentNote","document","state","label","TELEMETRY_KEYWORDS","REVIEW_KEYWORDS","PLAN_REF","CANARY_KEYWORDS","FEEDBACK_KEYWORDS","classifyMitigation","row","text","mitigation","hints","hasAny","words","planRefs","matchAll","warning","auditDeferredMitigations","trajectory","deferred","findBlockedRows","rows","message","id","resolveTestIdCommand","find","backtickMatches","asserts","identifiers","keyword","s","sort","a","b","fileGlob","replace","suggestedCommand","slice","CLOSE_TERMINAL_STATES","findNonTerminalRows","passesAtKind","passesAt","auditPlanAtClose","parseTrajectory","nonTerminal","GATE_SUFFIXES","Verification","OTel","Context","Document","parseChecklistItems","items","checked","parseImplString","data","content","title","status","phases","currentPhase","currentGateType","currentGateLines","inForwardIntelligence","forwardIntelligenceLines","flushGate","gates","lineIndex","phaseMatch","ordinal","blocker","forwardIntelligence","fiMatch","gateMatch","blockerMatch","getPhaseCompletion","uncheckedByGate","implementation","verification","otel","context","totalItems","checkedItems","item","complete","getAllPhaseCompletions","parsed","findPhase","readPublishedRecord","block","published","withProvenance","prov","Error","kept","inPublishedBlock","destination","commit","source_commit","hash","copySource","copy","source","archivedInWorktree","copyProblem","problem","detail","canonical","trunkCopy","plan","trunkPlans","withFileTypes","e","isDirectory","repositoryOf","anyCheckout","list","top","main","linked","Map","prunable","set","branch","trunkCopies","copyFor","assignments","trunk","mine","live","get","worktreeCopy","planning","active","archived","resolvePlanCopies","asked","ok","copies","unassigned","record","file","claimed","values","livePlanCopy","all"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../apps/indusk-mcp/dist/lib/worktree/repos.js","../../../../../../apps/indusk-admin/src/components/ui/Badge.tsx","../../../../../../apps/indusk-mcp/dist/lib/falsification/log.js","../../../../../../apps/indusk-mcp/dist/lib/falsification/skip.js","../../../../../../apps/indusk-mcp/dist/lib/impl-headings.js","../../../../../../apps/indusk-mcp/dist/lib/workflow-types.js","../../../../../../apps/indusk-mcp/dist/lib/lifecycle.js","../../../../../../apps/indusk-mcp/dist/lib/cleanup/gate.js","../../../../../../apps/indusk-mcp/dist/lib/trajectory/audit.js","../../../../../../apps/indusk-mcp/dist/lib/trajectory/parser.js","../../../../../../apps/indusk-mcp/dist/lib/impl-parser-core.js","../../../../../../apps/indusk-mcp/dist/lib/plan-parser.js","../../../../../../apps/indusk-mcp/dist/lib/papers/summary.js","../../../../../../apps/indusk-mcp/dist/lib/papers/provenance.js","../../../../../../apps/indusk-mcp/dist/lib/promises/after-close.js","../../../../../../apps/indusk-mcp/dist/lib/promises/incidents.js","../../../../../../apps/indusk-mcp/dist/lib/promises/reopen.js","../../../../../../apps/indusk-mcp/dist/lib/git.js","../../../../../../apps/indusk-mcp/dist/lib/agents/lock.js","../../../../../../apps/indusk-mcp/dist/lib/worktree/plan-worktree-record.js","../../../../../../apps/indusk-admin/src/lib/planning-reader.ts","../../../../../../apps/indusk-mcp/dist/lib/worktree/plan-worktrees.js"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { isCleanSegment, isUsableRelPath } from \"../path-segment.js\";\nfunction readRawConfig(root) {\n const configPath = join(root, \".indusk\", \"config.json\");\n if (!existsSync(configPath))\n return null;\n try {\n return JSON.parse(readFileSync(configPath, \"utf-8\"));\n }\n catch {\n return null;\n }\n}\n/**\n * Read the declared repo set, newest shape first, singular reduced into it.\n *\n * **The reduction IS the backward-compatibility guarantee**, not a claim about\n * one: `wrapped_repo: \"numero\"` reads as `[{ name: \"numero\" }]`, so every\n * workbench written before this plan keeps working with no config edit and no\n * migration step. The same shape as `phaseOrdinal` reducing to the phase\n * number when a document has no test phase.\n *\n * Names are boundary values — sanitized before any caller can join them into a\n * path, and first-occurrence-deduped so declared order survives. A malformed\n * declaration degrades to fewer repos, never to a traversal.\n *\n * Returns `[]` for a project that declares nothing, which callers read as\n * \"not a workbench\" exactly as they read a missing `wrapped_repo` today.\n */\nexport function readWorkbenchRepos(root) {\n const worktree = readRawConfig(root)?.worktree;\n if (!worktree)\n return [];\n const declared = Array.isArray(worktree.repos)\n ? worktree.repos\n : typeof worktree.wrapped_repo === \"string\"\n ? [{ name: worktree.wrapped_repo }]\n : [];\n const seen = new Set();\n const repos = [];\n for (const entry of declared) {\n if (typeof entry !== \"object\" || entry === null)\n continue;\n const { name, remote } = entry;\n if (typeof name !== \"string\" || !isCleanSegment(name))\n continue;\n if (seen.has(name))\n continue;\n seen.add(name);\n // `path` and `worktrees` are joined into filesystem paths, so they are\n // boundary values exactly as `name` is. A declared value that is not a\n // clean segment is DROPPED, degrading to the default rather than\n // resolving to a traversal.\n const { path, worktrees } = entry;\n const repo = { name };\n if (typeof remote === \"string\" && remote.trim() !== \"\")\n repo.remote = remote;\n if (typeof path === \"string\" && isUsableRelPath(path))\n repo.path = path;\n if (typeof worktrees === \"string\" && isUsableRelPath(worktrees))\n repo.worktrees = worktrees;\n repos.push(repo);\n }\n return repos;\n}\n/**\n * Whether this project is workbench-shaped at all.\n *\n * `shape: \"workbench\"` says so explicitly; declaring repos says so just as\n * loudly. A config with `repos[]` and no `shape` flag used to read as a flat\n * project here while every other reader (`readWorkbenchRepos`, the bash lane,\n * the eval hook) treated it as a workbench — so `verify` would have judged the\n * wrapper repo's plan documents as code (workbench-trust-fixes, F6 / A14). One\n * rule, and every refusal inherits it through this function.\n */\nexport function isWorkbench(root) {\n if (readRawConfig(root)?.worktree?.shape === \"workbench\")\n return true;\n return readWorkbenchRepos(root).length > 0;\n}\n/** The parent directory the sibling clones live in, or null when undeclared. */\nexport function readSiblingParent(root) {\n const value = readRawConfig(root)?.worktree?.sibling_parent;\n return typeof value === \"string\" && value.trim() !== \"\" ? value : null;\n}\n/**\n * Where this workbench's repos live.\n *\n * `repos_root` supersedes `sibling_parent`, which named a *relationship*\n * (\"the parent of the siblings\") rather than the value. The relationship stops\n * being true the moment the repos live inside the workbench, and a field that\n * describes a layout it no longer governs is worse than a bland one.\n *\n * The old key is still read, so no existing workbench needs an edit — the same\n * reduction `wrapped_repo` gets. Returns the declared string verbatim;\n * resolving it (relative to the workbench, or absolute) belongs to the caller\n * that knows where the workbench is.\n */\nexport function readReposRoot(root) {\n const w = readRawConfig(root)?.worktree;\n for (const value of [w?.repos_root, w?.sibling_parent]) {\n if (typeof value === \"string\" && value.trim() !== \"\")\n return value;\n }\n return undefined;\n}\n/**\n * Where this workbench's repos live, as an absolute path.\n *\n * THE one definition. It had four — `workbench restore`, the health-check\n * runner, doppler's env-pull, and `indusk update`'s materialization nudge — and\n * they disagreed. The nudge read only the legacy `sibling_parent`, never\n * resolved a relative value, and looked repos up by `name` rather than declared\n * `path`, so on a nested workbench it reported every repo missing and told the\n * operator to run `workbench restore` — which would have cloned a second copy\n * beside the workbench and relinked the trunks at it.\n *\n * The first defect in this family to recommend a destructive action, and the\n * reason a copy of this rule is unacceptable rather than merely untidy.\n *\n * The rule: relative resolves against the workbench (which is what makes a\n * nested layout reproduce on a clone), absolute is used as given, absent means\n * the workbench's parent.\n */\nexport function resolveReposRoot(projectRoot) {\n const declared = readReposRoot(projectRoot);\n if (!declared)\n return resolve(projectRoot, \"..\");\n if (declared.startsWith(\"/\") || declared.startsWith(\"~\"))\n return declared;\n return resolve(projectRoot, declared);\n}\n/**\n * The refusal a command owes a project that is not a workbench.\n *\n * One string, because the *rule* it belongs to lives in bash. `worktree\n * create`/`refresh`/`preflight` all resolve which repo the user meant inside\n * `_resolve_workbench_repo`, and `worktreeCreate` deliberately passes `--repo`\n * straight through rather than deciding first — so there is exactly one\n * implementation of pick-one and it is the shell one.\n *\n * A TypeScript `resolveRepo` stood here with no caller, its docblock claiming\n * to be \"the rule the CLI surface needs in one place\" while the CLI surface\n * read the shell. Dead code that describes itself as authoritative is worse\n * than dead code; what the TS lane actually shares with bash is this message.\n */\nexport const NOT_A_WORKBENCH = 'this project is not a workbench (set worktree.shape=\"workbench\" and worktree.repos[] in .indusk/config.json, or run `indusk init --workbench`).';\n/**\n * Where this repo's checkout lives, relative to the workbench root.\n *\n * The declared `path`, or the name when nothing is declared. One function so\n * no caller re-derives it — a second copy would be the place the two answers\n * quietly diverge.\n */\nexport function repoDir(repo) {\n return repo.path ?? repo.name;\n}\n/**\n * Every declared repo with the directory its checkout lives in — the join of\n * `resolveReposRoot` (where repos live) and `repoDir` (each one's directory\n * name). Empty for a flat project.\n *\n * The one place that join is spelled (context-tiers cleanup, A24). It was\n * written at four sites — the health roots, the execution roots, the papers\n * destination and `check-pointers` — and each is the same fact, the kind\n * whose copies the layout lessons say drift silently. The hook lane's port is\n * `declaredRepoDirsAt` in `hooks/_hook-paths.js`.\n */\nexport function declaredRepoDirs(projectRoot) {\n const repos = readWorkbenchRepos(projectRoot);\n if (repos.length === 0)\n return [];\n const reposRoot = resolveReposRoot(projectRoot);\n return repos.map((r) => ({ name: r.name, dir: join(reposRoot, repoDir(r)) }));\n}\n","import type { HTMLAttributes } from \"react\";\n\n/**\n * Trajectory states from `apps/indusk-mcp/src/lib/trajectory/parser.ts`.\n * The Badge variant set mirrors them so trajectory rows map 1:1.\n */\nexport type BadgeVariant =\n | \"passing\"\n | \"blocked\"\n | \"skipped\"\n | \"planned\"\n | \"writable\"\n | \"written\"\n | \"unknown\"\n | \"neutral\";\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n variant?: BadgeVariant;\n}\n\n/**\n * Color palette per A9 visibility requirement: pass/fail status\n * legible at a glance without reading the State text.\n *\n * - passing: green (success)\n * - blocked: red (attention required)\n * - skipped: muted/yellow (intentional non-test)\n * - planned/writable/written: gray-toned (in flight)\n * - unknown: gray (parser fallback)\n * - neutral: utility variant for non-trajectory uses (status labels, etc.)\n */\nconst variantClasses: Record<BadgeVariant, string> = {\n passing: \"bg-green-100 text-green-800 ring-green-600/20\",\n blocked: \"bg-red-100 text-red-800 ring-red-600/20\",\n skipped: \"bg-yellow-100 text-yellow-800 ring-yellow-600/20\",\n planned: \"bg-gray-100 text-gray-700 ring-gray-500/20\",\n writable: \"bg-gray-200 text-gray-800 ring-gray-500/20\",\n written: \"bg-blue-50 text-blue-700 ring-blue-600/20\",\n unknown: \"bg-gray-50 text-gray-500 ring-gray-400/20\",\n neutral: \"bg-gray-100 text-gray-700 ring-gray-500/20\",\n};\n\nexport function Badge({\n variant = \"neutral\",\n className = \"\",\n children,\n ...rest\n}: BadgeProps) {\n const classes = [\n \"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ring-1 ring-inset\",\n variantClasses[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span className={classes} {...rest}>\n {children}\n </span>\n );\n}\n","import { appendFileSync, existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nconst VALID_OUTCOMES = new Set([\n \"fix-in-scope\",\n \"spawn-plan\",\n \"accept-finding\",\n]);\n/**\n * Reject multiline content at the library boundary. The log's on-disk\n * format is markdown sections with bold-labeled single-line fields; any\n * line-separator character in hypothesis / note / reason silently\n * truncates during parse (the regex uses /m mode, where $ matches before\n * LF, CR, LS, and PS). Throwing here forces callers to sanitize — either\n * collapse to a single line or split across multiple entries.\n *\n * Line separators rejected: LF (\\n), CR (\\r), LS (U+2028), PS (U+2029).\n */\nconst LINE_SEPARATOR_RE = /[\\n\\r\\u2028\\u2029]/;\nfunction assertSingleLine(field, value) {\n if (LINE_SEPARATOR_RE.test(value)) {\n throw new Error(`Falsification log ${field} must be single-line (got a value containing a line separator). Either collapse to one line (replace '\\\\n' with '; '), or split the content across multiple entries. The log's parser is line-oriented; any line-separator (LF, CR, LS, PS) would silently truncate.`);\n }\n}\nfunction logPath(planRoot) {\n return join(planRoot, \"falsification.md\");\n}\nfunction headerFor(planRoot) {\n return `# Falsification Log — ${basename(planRoot)}\\n\\nAppend-only record of the /falsify bounty hunt for this plan. Never edit in place; entries are appended via \\`appendHypothesis\\` and \\`markTerminated\\` from \\`apps/indusk-mcp/src/lib/falsification/log.ts\\`.\\n\\n`;\n}\n/**\n * Append a confirmed-hypothesis entry to the plan's falsification log.\n * Creates the log file with a header if it doesn't yet exist. Throws if\n * the log is already terminated (a new hypothesis after a terminator is a\n * sign the ritual was restarted incorrectly — see `isFalsificationComplete`\n * and start a new plan or explicitly un-terminate first).\n */\nexport function appendHypothesis(planRoot, entry) {\n assertSingleLine(\"hypothesis\", entry.hypothesis);\n if (entry.note !== undefined)\n assertSingleLine(\"note\", entry.note);\n const path = logPath(planRoot);\n const existing = existsSync(path) ? readFalsificationLog(planRoot) : [];\n if (existing.length > 0 && existing[existing.length - 1].kind === \"terminator\") {\n throw new Error(`Falsification log at ${path} is already terminated. Start a new plan or remove the terminator before appending.`);\n }\n if (!existsSync(path)) {\n writeFileSync(path, headerFor(planRoot), \"utf-8\");\n }\n const stored = {\n kind: \"hypothesis\",\n hypothesis: entry.hypothesis,\n testPath: entry.testPath,\n outcome: entry.outcome,\n note: entry.note,\n timestamp: new Date().toISOString(),\n };\n appendFileSync(path, renderHypothesis(stored), \"utf-8\");\n return stored;\n}\n/**\n * Append a terminator entry marking the falsification ritual complete for\n * this plan. No further hypotheses can be appended after this. The reason\n * is the user-confirmed rationale for termination (e.g., \"investigated\n * concurrency, race conditions, partial-write paths, and type-narrowing\n * gaps; no in-scope failure remained\").\n */\nexport function markTerminated(planRoot, reason) {\n if (!reason.trim()) {\n throw new Error(\"markTerminated requires a non-empty reason.\");\n }\n assertSingleLine(\"reason\", reason);\n const path = logPath(planRoot);\n const existing = existsSync(path) ? readFalsificationLog(planRoot) : [];\n if (existing.length > 0 && existing[existing.length - 1].kind === \"terminator\") {\n throw new Error(`Falsification log at ${path} is already terminated.`);\n }\n if (!existsSync(path)) {\n writeFileSync(path, headerFor(planRoot), \"utf-8\");\n }\n const stored = {\n kind: \"terminator\",\n reason: reason.trim(),\n timestamp: new Date().toISOString(),\n };\n appendFileSync(path, renderTerminator(stored), \"utf-8\");\n return stored;\n}\n/**\n * Read the falsification log for a plan. Returns an empty array if the log\n * file does not exist. Malformed entries are skipped (not thrown) and\n * surfaced via the optional `onMalformed` callback, matching the semantic\n * graph event log's resilience pattern.\n */\nexport function readFalsificationLog(planRoot, opts) {\n const path = logPath(planRoot);\n if (!existsSync(path))\n return [];\n const content = readFileSync(path, \"utf-8\");\n const entries = [];\n const sectionRegex = /^##\\s+(Hypothesis|Terminated)\\s+(.+?)\\s*$/gm;\n const matches = [...content.matchAll(sectionRegex)];\n for (let i = 0; i < matches.length; i++) {\n const match = matches[i];\n const [, kind, timestamp] = match;\n const start = (match.index ?? 0) + match[0].length;\n const end = i + 1 < matches.length ? (matches[i + 1].index ?? content.length) : content.length;\n const body = content.slice(start, end).trim();\n if (kind === \"Hypothesis\") {\n const entry = parseHypothesisBody(body, timestamp);\n if (\"lineNumber\" in entry) {\n opts?.onMalformed?.(entry);\n }\n else {\n entries.push(entry);\n }\n }\n else if (kind === \"Terminated\") {\n const entry = parseTerminatorBody(body, timestamp);\n if (\"lineNumber\" in entry) {\n opts?.onMalformed?.(entry);\n }\n else {\n entries.push(entry);\n }\n }\n }\n return entries;\n}\n/**\n * True iff the plan's falsification log exists AND its last entry is a\n * terminator. False for a missing log, a log with only hypotheses (ritual\n * started but not terminated), or an empty log file.\n */\nexport function isFalsificationComplete(planRoot) {\n const entries = readFalsificationLog(planRoot);\n if (entries.length === 0)\n return false;\n return entries[entries.length - 1].kind === \"terminator\";\n}\n// ---------------------------------------------------------------\n// Rendering (writing entries to markdown)\n// ---------------------------------------------------------------\nfunction renderHypothesis(entry) {\n const lines = [\n `## Hypothesis ${entry.timestamp}`,\n \"\",\n `**Hypothesis:** ${entry.hypothesis}`,\n `**Test:** ${entry.testPath ?? \"(not written)\"}`,\n `**Outcome:** ${entry.outcome}`,\n ];\n if (entry.note) {\n lines.push(`**Note:** ${entry.note}`);\n }\n lines.push(\"\", \"\");\n return lines.join(\"\\n\");\n}\nfunction renderTerminator(entry) {\n return [`## Terminated ${entry.timestamp}`, \"\", `**Reason:** ${entry.reason}`, \"\", \"\"].join(\"\\n\");\n}\n// ---------------------------------------------------------------\n// Parsing (reading entries from markdown)\n// ---------------------------------------------------------------\nfunction parseHypothesisBody(body, timestamp) {\n const hypothesisMatch = body.match(/^\\*\\*Hypothesis:\\*\\*\\s+(.+)$/m);\n const testMatch = body.match(/^\\*\\*Test:\\*\\*\\s+(.+)$/m);\n const outcomeMatch = body.match(/^\\*\\*Outcome:\\*\\*\\s+([a-z-]+)$/m);\n const noteMatch = body.match(/^\\*\\*Note:\\*\\*\\s+(.+)$/m);\n if (!hypothesisMatch || !outcomeMatch) {\n return {\n lineNumber: 0,\n content: body,\n reason: \"Hypothesis entry missing required fields (hypothesis or outcome)\",\n };\n }\n const outcome = outcomeMatch[1];\n if (!VALID_OUTCOMES.has(outcome)) {\n return {\n lineNumber: 0,\n content: body,\n reason: `Invalid outcome \"${outcome}\"; must be one of ${[...VALID_OUTCOMES].join(\", \")}`,\n };\n }\n const testRaw = testMatch?.[1]?.trim() ?? \"(not written)\";\n return {\n kind: \"hypothesis\",\n hypothesis: hypothesisMatch[1].trim(),\n testPath: testRaw === \"(not written)\" ? null : testRaw,\n outcome,\n note: noteMatch?.[1]?.trim(),\n timestamp,\n };\n}\nfunction parseTerminatorBody(body, timestamp) {\n const reasonMatch = body.match(/^\\*\\*Reason:\\*\\*\\s+(.+)$/m);\n if (!reasonMatch) {\n return {\n lineNumber: 0,\n content: body,\n reason: \"Terminator entry missing required Reason field\",\n };\n }\n return {\n kind: \"terminator\",\n reason: reasonMatch[1].trim(),\n timestamp,\n };\n}\n","import matter from \"gray-matter\";\n/**\n * Parse an impl.md body (the full file content including frontmatter) and\n * return whether the author has explicitly opted out of the falsification\n * ritual. Opt-out requires both fields in frontmatter:\n *\n * falsification: skipped\n * falsification_reason: \"a non-empty reason\"\n *\n * Returns `{ skipped: true, reason }` only if both fields are present and\n * the reason is non-empty after trimming. Any other state — missing\n * `falsification`, falsification set to anything other than `skipped`,\n * missing or empty reason — returns `{ skipped: false, reason: null }`.\n *\n * The two-field shape matches the planner skill's precedent (gate_policy\n * as a single enum value) while keeping the reason unambiguous against\n * YAML parsers. Colons inside quoted YAML strings are fragile across\n * parsers; two fields avoid that class of bug entirely.\n */\nexport function isFalsificationSkipped(implContent) {\n try {\n const { data } = matter(implContent);\n const flag = data.falsification;\n const reasonRaw = data.falsification_reason;\n if (flag !== \"skipped\")\n return { skipped: false, reason: null };\n if (typeof reasonRaw !== \"string\")\n return { skipped: false, reason: null };\n const reason = reasonRaw.trim();\n if (!reason)\n return { skipped: false, reason: null };\n return { skipped: true, reason };\n }\n catch {\n return { skipped: false, reason: null };\n }\n}\n","/**\n * The one definition of every heading an impl document uses.\n *\n * Before this module the phase-heading pattern existed in six places across\n * five files, with no test holding them together. That was survivable while\n * there was one kind of phase. It stops being survivable the moment there are\n * two: a second kind added to five copies is a plan that fails four times\n * before it works, and each failure is silent — an unrecognised heading does\n * not error, it simply isn't a phase, and every rule keyed on phases quietly\n * has nothing to say.\n *\n * `impl-headings.test.ts` asserts by counting: exactly one definition under\n * `src/`. It is the same structural shape as `shared-resolution.test.ts`, and\n * for the same reason — no behavioural test can catch a divergence that has\n * not happened yet.\n *\n * The JS hooks cannot import a `.ts` module and mirror these constants inline\n * with a deliberate-port comment, the way the trajectory hooks already do.\n * Change this file and every port together.\n */\n/**\n * The heading text every build-phase pattern is built from — declared once so\n * the \"any phase\" and \"this phase\" forms cannot drift apart.\n */\nconst BUILD_PHASE = String.raw `###\\s+(?:Build\\s+)?Phase`;\n/**\n * A build phase: `### Phase 1: Name` or `### Build Phase 1: Name`.\n *\n * The `Build ` group is optional, which is the whole backward-compatibility\n * story — every impl written before test phases existed keeps parsing, so no\n * archived plan needs editing and no reader needs to learn that two spellings\n * mean the same thing before they can read old work.\n *\n * Capture 1 is the number, capture 2 the name.\n */\nexport const PHASE_HEADING = new RegExp(`^${BUILD_PHASE}\\\\s+(\\\\d+)[:\\\\s]+(.*)`);\n/**\n * The same heading, pinned to one specific phase number — what a caller needs\n * when it is walking to *this* phase's block rather than recognising any phase.\n * `\\b` keeps Phase 1 from matching Phase 10.\n *\n * Composed from `BUILD_PHASE` rather than restating it. That matters: this\n * pattern previously lived as its own template string in `shape/impl-blocks.ts`,\n * where the escaped-in-a-template form (`\\\\s`) slipped past the\n * single-definition guard entirely. It matched `### Phase N` only, so once\n * `/planner` began emitting `### Build Phase N`, Shape found no phase at all\n * and appended its findings nowhere — silently.\n */\nexport function buildPhaseHeadingFor(phase) {\n const ref = toPhaseRef(phase);\n if (ref.kind === \"test\")\n return new RegExp(`^###\\\\s+Test\\\\s+Phase\\\\s+${ref.number}\\\\b`);\n return new RegExp(`^${BUILD_PHASE}\\\\s+${ref.number}\\\\b`);\n}\n/** `#### Phase N <Gate>` for one specific phase, in that phase's spelling. */\nexport function gateHeadingFor(phase, gate) {\n const ref = toPhaseRef(phase);\n if (ref.kind === \"test\") {\n return new RegExp(`^####\\\\s+Test\\\\s+Phase\\\\s+${ref.number}\\\\s+${gate}\\\\b`);\n }\n return new RegExp(`^####\\\\s+(?:Build\\\\s+)?Phase\\\\s+${ref.number}\\\\s+${gate}\\\\b`);\n}\nexport function toPhaseRef(address) {\n return typeof address === \"number\" ? { kind: \"build\", number: address } : address;\n}\n/** `Test Phase 1` / `Build Phase 2` — for messages and appended notes. */\nexport function phaseLabel(address) {\n const ref = toPhaseRef(address);\n return `${ref.kind === \"test\" ? \"Test\" : \"Build\"} Phase ${ref.number}`;\n}\n/**\n * A test phase: `### Test Phase 1: Name`.\n *\n * Deliberately a separate pattern rather than another optional group in\n * `PHASE_HEADING`. The two kinds must never be confusable by a regex that\n * \"helpfully\" matches both — a test phase counted as a build phase would\n * satisfy the very rules it exists to make checkable.\n *\n * Capture 1 is the number, capture 2 the name.\n */\nexport const TEST_PHASE_HEADING = /^###\\s+Test\\s+Phase\\s+(\\d+)[:\\s]+(.*)/;\n/**\n * The number-only form, for callers that need to recognise a phase heading\n * without caring which kind it is — anything scanning for \"where does this\n * phase end\" wants both, because a test phase ends a build phase just as\n * surely as another build phase does.\n */\nexport const ANY_PHASE_HEADING = /^###\\s+(?:Test\\s+|Build\\s+)?Phase\\s+(\\d+)\\b/;\n/**\n * A gate heading: `#### Phase 1 Verification`, `#### Build Phase 1 Context`,\n * `#### Test Phase 1 Verification`.\n *\n * **Capture 1 is always the phase number.** `kind` may be a single kind or an\n * alternation like `(Verification|Context|Document)`, in which case its own\n * group lands at capture 2 — group numbering is worth stating because it is\n * the one thing a caller can get silently wrong here, and a gate type read\n * from the wrong index is an undefined lookup rather than a crash.\n */\nexport function gateHeading(kind) {\n return new RegExp(`^####\\\\s+(?:Test\\\\s+|Build\\\\s+)?Phase\\\\s+(\\\\d+)\\\\s+${kind}\\\\b`);\n}\n/** `#### Phase N Forward Intelligence`, in any phase-kind spelling. */\nexport const FORWARD_INTELLIGENCE_HEADING = /^####\\s+(?:Test\\s+|Build\\s+)?Phase\\s+\\d+\\s+Forward Intelligence\\b/;\n/**\n * Parse a line as a phase heading, or `null` if it is not one.\n *\n * Test phases are tried first: `PHASE_HEADING` does not match `### Test Phase\n * 1` today, but the ordering makes that a property of this function rather\n * than a property of a regex someone might later loosen.\n */\nexport function parsePhaseHeading(line) {\n const test = TEST_PHASE_HEADING.exec(line);\n if (test) {\n return { kind: \"test\", number: Number.parseInt(test[1], 10), name: test[2].trim() };\n }\n const build = PHASE_HEADING.exec(line);\n if (build) {\n return { kind: \"build\", number: Number.parseInt(build[1], 10), name: build[2].trim() };\n }\n return null;\n}\n/**\n * A trajectory cell naming a phase: `Phase 3`, `Build Phase 3`, `Test Phase 1`.\n *\n * Anchored and whole-cell, like the pattern it replaces — a cell with prose\n * around a phase name is not a reference, it is a malformed cell, and the\n * validator has a specific error for that.\n */\nexport const PHASE_REFERENCE = /^\\s*(Test\\s+|Build\\s+)?Phase\\s+(\\d+)\\s*$/i;\n/** Parse a `Writable at` / `Passes at` cell. `null` if it is not a reference. */\nexport function parsePhaseRef(cell) {\n const match = PHASE_REFERENCE.exec(cell);\n if (!match)\n return null;\n const kind = match[1]?.trim().toLowerCase() === \"test\" ? \"test\" : \"build\";\n return { kind, number: Number.parseInt(match[2], 10) };\n}\n/** A fence marker: three or more backticks or tildes, optionally indented. */\nconst FENCE = /^\\s*(`{3,}|~{3,})(.*)$/;\nfunction parseFence(line) {\n const m = FENCE.exec(line);\n if (!m)\n return null;\n return { char: m[1][0], length: m[1].length, info: m[2].trim() };\n}\n/**\n * The index of the line closing the fence opened at `start`, or `-1`.\n *\n * CommonMark's rule, and it is the rule that matters here: a fence closes only\n * on a marker of the **same character** and **at least the same length**, with\n * nothing after it. That is what lets a carried test body contain a fence of\n * its own — you nest by lengthening the marker — and it is what stops a `~~~`\n * inside a ```` ``` ```` block from ending the block early.\n */\nfunction findFenceClose(lines, start, open) {\n for (let i = start + 1; i < lines.length; i++) {\n const candidate = parseFence(lines[i]);\n if (candidate &&\n candidate.char === open.char &&\n candidate.length >= open.length &&\n candidate.info === \"\") {\n return i;\n }\n }\n return -1;\n}\n/**\n * Mark every line that sits inside a fenced code block.\n *\n * Structure-scanning must skip these. A deferral entry in Test Phase 1 may\n * carry the deferred test's body, and that body will contain lines that look\n * exactly like checklist items and gate headings — which is the point of\n * carrying it. A parser that reads them as structure turns a piece of evidence\n * into a phantom phase.\n *\n * **An unterminated fence masks nothing.** Failing open is deliberate: the\n * alternative is that one missing backtick deletes every phase below it from\n * every parser at once, and nothing reports a problem — the zero-phase guard\n * cannot save it, because the phases *above* the fence still parse. A leaked\n * code body produces loud, confusing errors; a swallowed document produces\n * silence, and silence is the failure mode this whole plan exists to remove.\n * The validator separately refuses an impl with an unterminated fence, so the\n * confusing errors are not what the author actually sees.\n */\nexport function fencedLineMask(lines) {\n const mask = new Array(lines.length).fill(false);\n for (let i = 0; i < lines.length; i++) {\n const open = parseFence(lines[i]);\n if (!open)\n continue;\n const close = findFenceClose(lines, i, open);\n if (close === -1)\n continue; // unterminated — not a fence at all\n for (let j = i; j <= close; j++)\n mask[j] = true;\n i = close;\n }\n return mask;\n}\n/**\n * The 1-based line number of an unterminated fence, or `null` if every fence\n * in `body` is closed. Reported rather than tolerated — see `fencedLineMask`.\n */\nexport function unterminatedFenceLine(body) {\n const lines = body.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const open = parseFence(lines[i]);\n if (!open)\n continue;\n const close = findFenceClose(lines, i, open);\n if (close === -1)\n return i + 1;\n i = close;\n }\n return null;\n}\n/** The document's phases, in the order they appear. Fenced blocks ignored. */\nexport function phaseSequence(body) {\n const lines = body.split(\"\\n\");\n const fenced = fencedLineMask(lines);\n const out = [];\n for (let i = 0; i < lines.length; i++) {\n if (fenced[i])\n continue;\n const heading = parsePhaseHeading(lines[i]);\n if (heading)\n out.push({ kind: heading.kind, number: heading.number });\n }\n return out;\n}\n/**\n * Where `ref` sits on the document's single phase timeline.\n *\n * Two sequences that number independently cannot be ordered by number alone —\n * Test Phase 1 and Build Phase 1 are different phases wearing the same digit.\n * Document order is the only thing that orders them, because it is the order\n * the work actually happens in.\n *\n * **A document with no test phase reduces to the phase number**, exactly as\n * before. That is deliberate and load-bearing: every impl written before test\n * phases existed compares precisely as it always did, so backward\n * compatibility is a property of this function rather than a claim made about\n * it elsewhere. It also keeps `Phase 0` meaningful — it orders before `Phase 1`\n * by arithmetic, with no special case.\n *\n * A ref naming a phase the document does not contain is placed among phases of\n * its own kind by number, half a step past the last one smaller than it.\n * Trajectory rows legitimately name phases before those phases are written, and\n * a reference to the future should sort into the future rather than collapse to\n * zero.\n */\n/**\n * Whether `ref` names a phase this document actually contains.\n *\n * `Phase 0` is always present by definition: it means \"before any of this\n * plan's work\", which is a real moment in a plan's life and deliberately has no\n * heading. Every other reference has to point at something written down.\n *\n * The distinction matters for enforcement rather than for ordering. A row whose\n * authoring phase exists can be late; a row naming a phase nobody has written\n * yet cannot be — you cannot miss a deadline that has not been scheduled — and\n * treating the two alike turns a plan's forward reference into a false\n * accusation.\n */\nexport function phaseExists(ref, sequence) {\n if (ref.kind === \"build\" && ref.number === 0)\n return true;\n return sequence.some((p) => p.kind === ref.kind && p.number === ref.number);\n}\nexport function phaseOrdinal(ref, sequence) {\n if (!sequence.some((p) => p.kind === \"test\"))\n return ref.number;\n const exact = sequence.findIndex((p) => p.kind === ref.kind && p.number === ref.number);\n if (exact !== -1)\n return exact;\n let lastBefore = -1;\n for (let i = 0; i < sequence.length; i++) {\n const p = sequence[i];\n if (p.kind === ref.kind && p.number < ref.number)\n lastBefore = i;\n }\n return lastBefore + 0.5;\n}\n","/**\n * The one definition of a plan's type (admin-plan-type).\n *\n * A plan declares what kind it is with `workflow:` in its brief. The kind\n * decides which planning documents the plan needs, and so whether an absent\n * document is a gap or was never going to exist.\n *\n * Before this module those facts were stated twice — the planner skill's\n * Workflow Types table and the four files under `templates/workflows/` — and\n * the two disagreed: the table said a bugfix has a test plan, the bugfix\n * template listed only a brief and an impl, and a bugfix written from the\n * template closed without one. The admin needs the same facts to explain a\n * type, which would have been a third statement. This is where they live;\n * `workflow-types-parity.test.ts` pins the skill and the templates to it.\n *\n * No filesystem and no Node built-ins: the admin imports this from a browser\n * component.\n */\nexport const WORKFLOW_TYPES = [\"feature\", \"bugfix\", \"refactor\", \"spike\"];\n/**\n * The retrospective is required by every kind that ships an impl. The planner\n * skill's table lists it only for `feature` because that table describes what\n * `/planner` creates; the retrospective is `/retrospective`'s, and a bugfix or\n * a refactor is closed by one like any other plan.\n */\nexport const WORKFLOW_DEFINITIONS = {\n feature: {\n purpose: \"A new capability or a change to how the system works — the full lifecycle.\",\n requires: [\"research\", \"brief\", \"test-plan\", \"adr\", \"impl\", \"retrospective\"],\n skips: [],\n why: \"Nothing is skipped: the problem has to be explored, the direction proposed, the behaviour pinned down, and the decision recorded before code is written.\",\n },\n bugfix: {\n purpose: \"Something is broken and the fix is known.\",\n requires: [\"brief\", \"test-plan\", \"impl\", \"retrospective\"],\n skips: [\"research\", \"adr\"],\n why: \"The problem is already understood, so there is nothing to research and no architecture to decide. The test plan stays: its first assertion is the failing test that proves the bug.\",\n },\n refactor: {\n purpose: \"Existing code is restructured without changing what it does.\",\n requires: [\"brief\", \"test-plan\", \"impl\", \"retrospective\"],\n skips: [\"research\", \"adr\"],\n why: \"Behaviour does not change, so there is no problem space to research and no decision to record. The test plan stays — it is what proves the behaviour held — and the impl carries a boundary map.\",\n },\n spike: {\n purpose: \"Pure exploration, with no commitment to build anything.\",\n requires: [\"research\"],\n skips: [\"brief\", \"test-plan\", \"adr\", \"impl\", \"retrospective\"],\n why: \"A spike ends in findings. If it leads to building something, that is a new plan with its own brief.\",\n },\n};\n/** What each document is called when a person reads it. */\nexport const DOCUMENT_LABELS = {\n research: \"research\",\n brief: \"brief\",\n \"test-plan\": \"test plan\",\n adr: \"ADR\",\n impl: \"impl\",\n retrospective: \"retrospective\",\n};\n/**\n * Read a `workflow:` frontmatter value. Three outcomes, kept apart on purpose:\n * a known type; a word that is not a type (`declared` set, `type` null — shown\n * as written, never guessed at); and nothing declared.\n *\n * A non-string is still a declaration — a list, a mapping, a number — and\n * treating it as \"not declared\" would hide the mistake. `written` is the text\n * on the document's `workflow:` line, when the caller has the document: it is\n * what such a declaration is shown as.\n */\nexport function readWorkflow(value, written = null) {\n if (value === undefined || value === null)\n return { type: null, declared: null };\n // Only a plain string can be a type. A list, a mapping, a number or a\n // boolean is a declaration that is not one: `String()` of a one-element\n // list is its element's text, which read `[bugfix]` as a bugfix. It is\n // shown as it was written on the line; a value written on the lines below\n // (a block list) has no text there, and is shown as YAML read it.\n if (typeof value !== \"string\") {\n return { type: null, declared: written ?? JSON.stringify(value) };\n }\n const declared = value.trim();\n if (declared === \"\")\n return { type: null, declared: null };\n const type = WORKFLOW_TYPES.includes(declared)\n ? declared\n : null;\n return { type, declared };\n}\n/**\n * One sentence for an absent document the bar cannot leave to colour alone.\n * `missing`: the type requires it and the plan moved past it. `unknown`: the\n * absence cannot be judged — because no type is declared, or because the word\n * that was declared is not one. Those are different facts, and the sentence\n * says which: a plan that declared `hotfix` did not declare nothing.\n */\nexport function absentDocumentNote(document, state, type, \n/** The word that was declared, when it is not one of the types. */\ndeclared = null) {\n const label = DOCUMENT_LABELS[document];\n if (state === \"missing\" && type !== null) {\n return `The ${label} is missing — a ${type} requires it.`;\n }\n if (declared !== null) {\n return `The ${label} is absent, and this plan declares \"${declared}\", which is not a recognised type, so whether that matters cannot be judged.`;\n }\n return `The ${label} is absent, and this plan declares no type, so whether that matters cannot be judged.`;\n}\n","import { DOCUMENT_LABELS, WORKFLOW_DEFINITIONS } from \"./workflow-types.js\";\n/**\n * Every position, in order. `monitor` follows `archived` (day-monitor, ADR\n * D8): a closed plan holding a behaviour promise waits there until its\n * promises have been quiet for the window.\n */\nexport const PLAN_POSITIONS = [\n \"research\",\n \"brief\",\n \"test-plan\",\n \"adr\",\n \"impl-approved\",\n \"executing\",\n \"falsify\",\n \"cleanup\",\n \"retrospective\",\n \"archived\",\n \"monitor\",\n];\n/**\n * The positions that are a document on disk, in lifecycle order — the plan\n * parser's stage order. `test-plan` joins it here: every new plan has one,\n * and the old private order walked past it as if it were not a stage.\n */\nexport const DOCUMENT_POSITIONS = [\n \"research\",\n \"brief\",\n \"test-plan\",\n \"adr\",\n \"impl\",\n \"retrospective\",\n];\n/**\n * Whether a document's status says it is finished. Research documents say\n * `complete`, impls and retrospectives `completed`, the rest `accepted`. One\n * definition, because the plan bar and the plan list's next step both ask\n * it, and a copy that knew one word fewer told a finished spike to review\n * its research (admin-plan-type, A23).\n */\nexport function isFinishedDocumentStatus(status) {\n return status === \"accepted\" || status === \"complete\" || status === \"completed\";\n}\n/**\n * The positions that exist only because a plan has an impl: it is executed,\n * then falsified, then cleaned up. A type with no impl — a spike — never\n * reaches them, so they read skipped for it rather than pending forever\n * (admin-plan-type, A21).\n */\nexport const IMPL_DEPENDENT_POSITIONS = [\n \"executing\",\n \"falsify\",\n \"cleanup\",\n];\n/**\n * The close-out rituals, in order, as the word a ritual phase's title must\n * START with (`### Phase N: Falsification — …`). The retrospective readiness\n * gate matches these; the skill prose used to be the only place the order was\n * written down.\n */\nexport const RITUAL_ORDER = [\"falsification\", \"cleanup\"];\nexport const PHASE_ACTIVITIES = [\n \"authoring\",\n \"implementing\",\n \"instrumenting\",\n \"verifying\",\n \"capturing-context\",\n \"documenting\",\n \"closed\",\n \"falsifying\",\n \"cleaning-up\",\n];\n/**\n * The gate stages a phase passes through after its implementation items, in\n * the order `/work` completes them. OTel is a stage wherever the project emits\n * it; a project whose `otel.role` is `library` simply never has the block.\n */\nexport const GATE_STAGES = [\"Verification\", \"OTel\", \"Context\", \"Document\"];\n// ---------------------------------------------------------------------------\n// Segments\n// ---------------------------------------------------------------------------\n/**\n * Every bar segment is one of these (Sandy, 2026-09-16). A bar that is only\n * filled-or-empty cannot show the middle, and the middle is the whole point\n * of watching. Skipped is drawn, never omitted, so every plan's bar has the\n * same shape.\n *\n * `missing` and `unknown` are an absent document's other two readings\n * (admin-plan-type): `skipped` says the plan's type does not require it,\n * `missing` that the type requires it and the plan moved past it, `unknown`\n * that no type is declared so the absence cannot be judged. A runtime list, so\n * the admin's render-parity pin can walk it like the positions and activities.\n */\nexport const SEGMENT_STATES = [\n \"done\",\n \"active\",\n \"pending\",\n \"skipped\",\n \"missing\",\n \"unknown\",\n];\n/** \"3 of 7 days quiet\", \"window restarted 2026-09-18 — 0 of 7 days quiet\". */\nexport function monitorAwaiting(m) {\n const quiet = `${Math.floor(m.elapsedDays)} of ${m.windowDays} days quiet`;\n return m.restartedAt ? `window restarted ${m.restartedAt.slice(0, 10)} — ${quiet}` : quiet;\n}\nconst DOC_POSITION_TO_PLAN = {\n research: \"research\",\n brief: \"brief\",\n \"test-plan\": \"test-plan\",\n adr: \"adr\",\n impl: \"impl-approved\",\n retrospective: \"retrospective\",\n};\n/** The plan-level position a plan summary + impl state resolves to, and why. */\nfunction resolvePosition(input) {\n const { summary, impl, readiness, archived, afterClose } = input;\n if (archived) {\n if (afterClose && afterClose.reopened.length > 0) {\n return { position: \"executing\", awaiting: `executing ${afterClose.reopened[0]}` };\n }\n if (afterClose?.monitor) {\n return { position: \"monitor\", awaiting: monitorAwaiting(afterClose.monitor) };\n }\n return { position: \"archived\", awaiting: null };\n }\n const stage = summary.stage;\n const status = summary.stageStatus;\n if (stage === \"retrospective\") {\n return { position: \"retrospective\", awaiting: \"retrospective written, awaiting archive\" };\n }\n if (stage === \"impl\") {\n if (status === \"completed\") {\n // A29: the bar may never say \"cleaned\" over a fact it does not hold.\n // No readiness means the impl could not be read — say so; a\n // non-terminal row means the gate would refuse — name it.\n if (readiness === null) {\n return {\n position: \"falsify\",\n awaiting: \"impl complete — readiness unknown (impl unreadable)\",\n };\n }\n const missing = readiness.missing;\n if (missing.includes(\"falsification\")) {\n return { position: \"falsify\", awaiting: \"impl complete, awaiting /falsify\" };\n }\n if (missing.includes(\"cleanup\")) {\n return { position: \"cleanup\", awaiting: \"falsified, awaiting /cleanup\" };\n }\n if (missing.includes(\"rows\")) {\n const rows = readiness.nonTerminalRows.join(\", \");\n return {\n position: \"retrospective\",\n awaiting: `rows not terminal — retrospective blocked${rows ? ` (${rows})` : \"\"}`,\n };\n }\n return { position: \"retrospective\", awaiting: \"cleaned, awaiting /retrospective\" };\n }\n if (status === \"in-progress\") {\n // A33: the active segment always carries the message. Normally the\n // active phase speaks; when nothing is open there is no active\n // phase, so the position itself says what it is waiting for.\n const nothingOpen = impl !== null &&\n impl.phases.length > 0 &&\n impl.phases.every((p) => p.gates.every((g) => g.items.every((i) => i.checked)));\n return {\n position: \"executing\",\n awaiting: nothingOpen ? \"every item checked — impl status is still in-progress\" : null,\n };\n }\n if (status === \"approved\")\n return { position: \"impl-approved\", awaiting: \"impl approved, awaiting /work\" };\n return { position: \"impl-approved\", awaiting: `impl ${status}, awaiting approval` };\n }\n if (stage === \"research\" || stage === \"brief\" || stage === \"test-plan\" || stage === \"adr\") {\n const noun = DOCUMENT_LABELS[stage];\n if (isFinishedDocumentStatus(status)) {\n // A22: the active label never claims a fact the reader does not\n // hold. A spike's finished research awaits nothing.\n const type = summary.workflow ?? null;\n if (type !== null && nextRequiredDocument(stage, type) === undefined) {\n return { position: stage, awaiting: `${noun} finished — a ${type} ends here` };\n }\n return { position: stage, awaiting: `${noun} accepted, awaiting the next document` };\n }\n if (status === \"proposed\" || status === \"draft\" || status === \"in-progress\") {\n return { position: stage, awaiting: `${noun} ${status}, awaiting acceptance` };\n }\n return { position: stage, awaiting: `${noun} ${status}` };\n }\n // `paper`, `unknown`, `malformed`: no lifecycle position has been reached.\n return { position: \"research\", awaiting: `${stage} — no lifecycle document yet` };\n}\n/**\n * The plan bar. Positions before the current one are done, the current one is\n * active, later ones pending. Archived has no active segment: nothing is\n * happening. `monitor` is pending unless a closed plan is inside its quiet\n * window, when it is active and `archived` behind it is done.\n *\n * A document position whose file is absent is judged by the plan's declared\n * type, never by the absence alone (admin-plan-type). This function used to\n * call every absent earlier document `skipped`, so a bugfix that never needed\n * research and a bugfix that closed without its test plan drew the same\n * segment — a judgment asserted that nothing had made.\n */\nexport function derivePlanPosition(input) {\n const { position, awaiting } = resolvePosition(input);\n const currentIndex = PLAN_POSITIONS.indexOf(position);\n const docs = new Set(input.summary.documents);\n const type = input.summary.workflow ?? null;\n const required = type === null ? null : new Set(WORKFLOW_DEFINITIONS[type].requires);\n const segments = {};\n for (const [index, candidate] of PLAN_POSITIONS.entries()) {\n if (index === currentIndex) {\n segments[candidate] = position === \"archived\" ? \"done\" : \"active\";\n continue;\n }\n segments[candidate] = restingState(candidate, index < currentIndex, docs, required);\n }\n const monitor = position === \"monitor\" ? input.afterClose?.monitor : null;\n return {\n position,\n segments,\n awaiting: position === \"archived\" ? null : awaiting,\n ...(monitor ? { monitor } : {}),\n };\n}\n/**\n * The state of a position the plan is not at — one it has passed, or one\n * ahead of it. The type speaks first: a position that exists only because of\n * an impl is skipped for a type that has none. Then the document, if the\n * position is one. Then where the plan stands.\n */\nfunction restingState(position, passed, docs, required) {\n if (required !== null && !required.has(\"impl\") && IMPL_DEPENDENT_POSITIONS.includes(position)) {\n return \"skipped\";\n }\n const doc = documentFor(position);\n if (doc !== null && !docs.has(`${doc}.md`))\n return absentDocumentState(doc, required, passed);\n return passed ? \"done\" : \"pending\";\n}\n/**\n * The next document a plan's declared type requires after `stage`, or\n * undefined when there is none. With no declared type every document is next\n * in turn, as before the type existed.\n *\n * One definition, read by the active label, the plan list's next step and\n * `advance_plan`. Each once answered \"what comes next\" by lifecycle order\n * alone, so a bugfix with an accepted test plan was told to create the ADR\n * its type skips (admin-plan-type, A23 and A25).\n */\nexport function nextRequiredDocument(stage, type) {\n const index = DOCUMENT_POSITIONS.indexOf(stage);\n return DOCUMENT_POSITIONS.slice(index + 1).find((doc) => type === null || WORKFLOW_DEFINITIONS[type].requires.includes(doc));\n}\n/**\n * What an absent document reads as. `required` is the declared type's document\n * list, or null when the plan declares no type.\n *\n * - Not required by the type: `skipped`, whether the plan has passed it or not\n * — the bar says up front what this kind of plan will never have.\n * - Required and passed: `missing`.\n * - Required and not reached: `pending`, like any step ahead.\n * - No type, and passed: `unknown` — where this used to say skipped.\n * - No type, and not reached: `pending`. Nothing is in question yet.\n */\nfunction absentDocumentState(doc, required, passed) {\n if (required === null)\n return passed ? \"unknown\" : \"pending\";\n if (!required.has(doc))\n return \"skipped\";\n return passed ? \"missing\" : \"pending\";\n}\n/** The document a position stands for, or null for a position that is not a document (`executing`, `archived`, …). */\nexport function documentFor(position) {\n for (const doc of DOCUMENT_POSITIONS) {\n if (DOC_POSITION_TO_PLAN[doc] === position)\n return doc;\n }\n return null;\n}\n// ---------------------------------------------------------------------------\n// Deriving a phase's activity\n// ---------------------------------------------------------------------------\n/** `(none needed — asked: \"…\" — user: \"…\")` — the conversation proof a skipped gate carries. */\nconst OPT_OUT = /\\(\\s*(?:none needed|not applicable)\\b[^)]*asked:\\s*\"[^\"]+\"[^)]*user:\\s*\"[^\"]+\"/i;\nconst STAGE_VERB = {\n implementation: \"implementing\",\n Verification: \"verifying\",\n OTel: \"instrumenting\",\n Context: \"capturing-context\",\n Document: \"documenting\",\n};\nconst GATE_TYPE_TO_STAGE = {\n implementation: \"implementation\",\n verification: \"Verification\",\n otel: \"OTel\",\n context: \"Context\",\n document: \"Document\",\n};\n/**\n * The phase bar: the implementation items, then each gate the phase carries,\n * in `GATE_STAGES` order. A stage is done when every item is checked,\n * opted-out when its only checked item is a conversation-proof skip, pending\n * otherwise. The first stage that is not done is active and names the verb —\n * `authoring` for a test phase's items, `falsifying` / `cleaning-up` for a\n * ritual phase's, `implementing` for everything else. All done ⇒ `closed`.\n */\nexport function derivePhaseActivity(phase, boundary) {\n const byStage = new Map();\n for (const gate of phase.gates) {\n const stage = GATE_TYPE_TO_STAGE[gate.type];\n if (!stage)\n continue;\n byStage.set(stage, [...(byStage.get(stage) ?? []), ...gate.items]);\n }\n const order = [\"implementation\", ...GATE_STAGES];\n const stages = [];\n let activeFound = false;\n let activity = \"closed\";\n for (const kind of order) {\n const items = byStage.get(kind);\n // A32: a stage with nothing in it is not a stage — the parser always\n // emits an implementation gate, and an empty one read \"implementing\n // 0 of 0\" forever while the active-phase rule said nothing was open.\n if (!items || items.length === 0)\n continue;\n const checked = items.filter((i) => i.checked).length;\n const total = items.length;\n const optOut = items.find((i) => i.checked && OPT_OUT.test(i.text));\n let state;\n if (total > 0 && checked === total) {\n state = optOut ? \"opted-out\" : \"done\";\n }\n else if (!activeFound) {\n state = \"active\";\n activeFound = true;\n activity = verbFor(phase, kind);\n }\n else {\n state = \"pending\";\n }\n stages.push({\n kind,\n state,\n checked,\n total,\n ...(optOut ? { proof: optOut.text } : {}),\n });\n }\n return { activity, stages, openedAt: boundary?.timestamp ?? null };\n}\nfunction verbFor(phase, stage) {\n if (stage !== \"implementation\")\n return STAGE_VERB[stage];\n if (phase.kind === \"test\")\n return \"authoring\";\n const title = phase.name.toLowerCase();\n if (title.startsWith(RITUAL_ORDER[0]))\n return \"falsifying\";\n if (title.startsWith(RITUAL_ORDER[1]))\n return \"cleaning-up\";\n return \"implementing\";\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { isFalsificationComplete } from \"../falsification/log.js\";\nimport { isFalsificationSkipped } from \"../falsification/skip.js\";\nimport { parsePhaseHeading } from \"../impl-headings.js\";\nimport { RITUAL_ORDER } from \"../lifecycle.js\";\nimport { findNonTerminalRows } from \"../trajectory/audit.js\";\nimport { parseTrajectory } from \"../trajectory/parser.js\";\n/**\n * Parse an impl.md's frontmatter and return whether the author has explicitly\n * opted out of the cleanup ritual. Opt-out requires both fields:\n *\n * cleanup: skipped\n * cleanup_reason: \"a non-empty reason\"\n *\n * Near-clone of `isFalsificationSkipped` — the two-field shape avoids the\n * quoted-YAML-colon fragility that a single `skip-reason:` field would carry.\n */\nexport function isCleanupSkipped(implContent) {\n try {\n const { data } = matter(implContent);\n if (data.cleanup !== \"skipped\")\n return { skipped: false, reason: null };\n const reasonRaw = data.cleanup_reason;\n if (typeof reasonRaw !== \"string\")\n return { skipped: false, reason: null };\n const reason = reasonRaw.trim();\n if (!reason)\n return { skipped: false, reason: null };\n return { skipped: true, reason };\n }\n catch {\n return { skipped: false, reason: null };\n }\n}\n/**\n * True iff `implContent` has a `### Phase N: <RitualWord>…` phase whose every\n * checklist item is checked. The title must **start** with the ritual word\n * (after `Phase N:`) — matching a substring would misdetect a topic-named phase\n * like \"The /cleanup skill\" as the ritual phase (found by the cleanup-ritual\n * falsification, H1). Returns false when no such phase exists or any item under\n * it is unchecked. Gate sub-headers (`#### Phase N Verification`, etc.) do NOT\n * reset the phase — their items belong to it.\n */\nfunction isRitualPhaseTerminal(implContent, ritualWord) {\n const startsWith = new RegExp(`^${ritualWord}\\\\b`, \"i\");\n const lines = implContent.split(\"\\n\");\n let inPhase = false;\n let found = false;\n let itemCount = 0;\n for (const line of lines) {\n const phaseMatch = parsePhaseHeading(line);\n if (phaseMatch) {\n if (startsWith.test(phaseMatch.name)) {\n inPhase = true;\n found = true;\n }\n else {\n inPhase = false;\n }\n continue;\n }\n if (!inPhase)\n continue;\n // Leading whitespace allowed — nested sub-items count (round-2 F3; the\n // old column-0 anchor made an indented unchecked item invisible).\n if (/^\\s*-\\s+\\[[ xX]\\]/.test(line)) {\n itemCount++;\n if (/^\\s*-\\s+\\[ \\]/.test(line)) {\n return false; // an unchecked item inside the ritual phase\n }\n }\n }\n // An empty ritual phase is NOT terminal — a bare heading with zero items\n // must not vacuously satisfy the retrospective gate (round-2 F2).\n return found && itemCount > 0;\n}\n/** True iff a terminal `### Phase N: Cleanup …` phase exists in the impl body. */\nexport function isCleanupPhaseTerminal(implContent) {\n return isRitualPhaseTerminal(implContent, RITUAL_ORDER[1]);\n}\n/** True iff a terminal `### Phase N: Falsification …` phase exists — the default\n * phase-authoring falsify flow leaves one (no legacy log, not skipped). */\nexport function isFalsificationPhaseTerminal(implContent) {\n return isRitualPhaseTerminal(implContent, RITUAL_ORDER[0]);\n}\n/**\n * True iff the plan's impl.md has a terminal Cleanup phase. Reads\n * `<planRoot>/impl.md`; returns false when the file is absent.\n */\nexport function isCleanupComplete(planRoot) {\n const implPath = join(planRoot, \"impl.md\");\n if (!existsSync(implPath))\n return false;\n return isCleanupPhaseTerminal(readFileSync(implPath, \"utf-8\"));\n}\n/**\n * The composed retrospective Step 0 readiness check: a plan is ready to close\n * only when BOTH rituals are satisfied — each either complete or explicitly\n * skipped. This is the single source of truth the retrospective skill's Step 0\n * gate references for the cleanup requirement.\n *\n * Falsification is satisfied by a completed log OR the skip frontmatter; the\n * retrospective skill independently also honors the \"all falsification phases\n * terminal\" path from the phase-authoring falsify flow.\n */\nexport function checkRetrospectiveReadiness(planRoot, implContent) {\n const falsificationOk = isFalsificationComplete(planRoot) ||\n isFalsificationSkipped(implContent).skipped ||\n isFalsificationPhaseTerminal(implContent);\n const cleanupOk = isCleanupComplete(planRoot) || isCleanupSkipped(implContent).skipped;\n // The check the skill's text always promised and the code never performed:\n // a row left `written` after its phase closed is invisible to the two\n // ritual walks above, which see checkboxes and not rows.\n const body = matter(implContent).content;\n const nonTerminalRows = findNonTerminalRows(parseTrajectory(body), body).map((f) => f.row.id);\n const rowsOk = nonTerminalRows.length === 0;\n const missing = [];\n if (!falsificationOk)\n missing.push(\"falsification\");\n if (!cleanupOk)\n missing.push(\"cleanup\");\n if (!rowsOk)\n missing.push(\"rows\");\n return {\n falsificationOk,\n cleanupOk,\n rowsOk,\n nonTerminalRows,\n passes: missing.length === 0,\n missing,\n };\n}\n","import { phaseExists, phaseSequence } from \"../impl-headings.js\";\nimport { parseTrajectory } from \"./parser.js\";\nconst TELEMETRY_KEYWORDS = [\"alert\", \"metric\", \"otel\", \"dash0\", \"grafana\", \"threshold\"];\nconst REVIEW_KEYWORDS = [\n \"weekly\",\n \"monthly\",\n \"quarterly\",\n \"daily\",\n \"review\",\n \"spot-check\",\n \"audit\",\n];\nconst PLAN_REF = /\\b[a-z][a-z0-9-]*-[a-z][a-z0-9-]*\\b/g; // kebab-case identifiers that look like plan slugs\nconst CANARY_KEYWORDS = [\"staging\", \"canary\", \"smoke\", \"preflight\", \"pre-release\"];\nconst FEEDBACK_KEYWORDS = [\"feedback\", \"ticket\", \"support\", \"channel\", \"signal\"];\nfunction classifyMitigation(row) {\n const text = row.mitigation.toLowerCase();\n const hints = [];\n const hasAny = (words) => words.some((w) => text.includes(w));\n let kind = \"unclassified\";\n if (hasAny(TELEMETRY_KEYWORDS))\n kind = \"telemetry-alert\";\n else if (hasAny(CANARY_KEYWORDS))\n kind = \"canary-or-staging\";\n else if (hasAny(FEEDBACK_KEYWORDS))\n kind = \"feedback-signal\";\n else if (hasAny(REVIEW_KEYWORDS))\n kind = \"scheduled-review\";\n const planRefs = [...row.mitigation.matchAll(PLAN_REF)].map((m) => m[0]);\n if (planRefs.length > 0) {\n hints.push(...planRefs);\n if (kind === \"unclassified\")\n kind = \"downstream-plan\";\n }\n let warning = null;\n if (kind === \"unclassified\" || row.mitigation.trim().length < 20) {\n warning = `Mitigation for \"${row.name}\" is vague — expected a specific alert, review cadence, plan reference, or procedure. Got: \"${row.mitigation}\"`;\n }\n return { row, kind, hints, warning };\n}\n/**\n * Retrospective audit: classify every Deferred Verification row, flag any\n * whose mitigation text is too vague or unclassifiable. Returns findings\n * suitable for inclusion in a retrospective's \"What We Learned\" section\n * or a Graphiti audit episode.\n */\nexport function auditDeferredMitigations(trajectory) {\n return trajectory.deferred.map(classifyMitigation);\n}\n/**\n * Retrospective audit: surface trajectory rows that ended the plan in\n * `blocked` state. Blocked means \"was writable/written but regressed or\n * changed\" — if the plan closes with blocked rows, they should be\n * explicitly resolved (fix, move passesAt, or move to Deferred Verification).\n */\nexport function findBlockedRows(trajectory) {\n return trajectory.rows\n .filter((row) => row.state === \"blocked\")\n .map((row) => ({\n row,\n message: `Row ${row.id} ended plan in 'blocked' state — needs resolution (fix, reschedule, or move to Deferred Verification)`,\n }));\n}\n/**\n * Derive a concrete test file glob + runnable command from a trajectory\n * row's asserts text. Heuristic: extract identifiers (camelCase, kebab-case,\n * backtick-quoted code) and use the longest as a filename hint. Returns a\n * fallback `pnpm test` with a `-t` name filter if no filename hint found.\n *\n * The verify skill uses this to resolve phase-Verification items that say\n * \"T3 passes (...)\" into a real invocation. Best-effort — human can override.\n */\nexport function resolveTestIdCommand(trajectory, id) {\n const row = trajectory.rows.find((r) => r.id === id);\n if (!row)\n return null;\n const backtickMatches = [...row.asserts.matchAll(/`([^`]+)`/g)].map((m) => m[1]);\n const identifiers = [...row.asserts.matchAll(/\\b[a-zA-Z][a-zA-Z0-9_]{3,}\\b/g)].map((m) => m[0]);\n const keyword = backtickMatches.filter((s) => /[a-zA-Z]/.test(s)).sort((a, b) => b.length - a.length)[0] ??\n identifiers.sort((a, b) => b.length - a.length)[0] ??\n null;\n const fileGlob = keyword\n ? `**/*${keyword.toLowerCase().replace(/[^a-z0-9]/g, \"\")}*.test.ts`\n : null;\n const suggestedCommand = keyword\n ? `pnpm test -t \"${keyword}\"`\n : `pnpm test -t \"${row.asserts.slice(0, 40)}\"`;\n return { id, asserts: row.asserts, fileGlob, suggestedCommand };\n}\n/**\n * The states a row may end a plan in. `check-gates` uses the same three for\n * Gate B; `TERMINAL_STATES` in the parser is a different set (it includes\n * `written` — \"authored\", which is terminal for the test-first duty and not\n * for a close).\n */\nconst CLOSE_TERMINAL_STATES = new Set([\"passing\", \"skipped\", \"blocked\"]);\n/**\n * Close-out audit: rows that are not terminal although the phase they pass at\n * exists in the document.\n *\n * workbench-trust-fixes closed with two rows still `written` after eight phase\n * closes; `checkRetrospectiveReadiness` walked the ritual phases' checkboxes\n * and `auditPlanAtClose` reported `blocked` rows only, so a row left `written`\n * was invisible to both — while the retrospective skill promised \"every row\n * terminal\". A row whose `Passes at` names a phase the document does not have\n * is a forward reference and is not counted, the same rule `check-gates`\n * applies (carried into dawn-workbench-execution from hook-cwd-independence).\n */\nexport function findNonTerminalRows(trajectory, body) {\n const sequence = phaseSequence(body);\n return trajectory.rows\n .filter((row) => phaseExists({ kind: row.passesAtKind, number: row.passesAt }, sequence) &&\n !CLOSE_TERMINAL_STATES.has(row.state))\n .map((row) => ({\n row,\n message: `Row ${row.id} is still '${row.state}' although ${row.passesAtKind === \"test\" ? \"Test \" : \"\"}Phase ${row.passesAt} exists — a plan does not close with a row that never reached passing, skipped or blocked`,\n }));\n}\n/** Convenience — parse + audit + resolve in one call for the retrospective skill. */\nexport function auditPlanAtClose(body) {\n const trajectory = parseTrajectory(body);\n return {\n deferred: auditDeferredMitigations(trajectory),\n blocked: findBlockedRows(trajectory),\n nonTerminal: findNonTerminalRows(trajectory, body),\n };\n}\n","import { parsePhaseRef } from \"../impl-headings.js\";\nconst TRAJECTORY_HEADING = /^##\\s+Test Trajectory\\b/;\nconst DEFERRED_HEADING = /^###\\s+Deferred Verification\\b/;\nconst NEXT_SECTION_HEADING = /^#{1,3}\\s+/;\nconst VALID_STATES = new Set([\n \"planned\",\n \"writable\",\n \"written\",\n \"passing\",\n \"blocked\",\n \"skipped\",\n \"unknown\",\n]);\n/**\n * States in which a row's authoring obligation is discharged.\n *\n * **One definition on purpose.** The phase-close probe (`run/probe.ts`) and\n * `atdawn verify` (`verify/detect.ts`) both ask \"has this row been dealt with?\",\n * and if they answered differently a phase would close in one lane and not the\n * other. A second copy would not be a duplicated line — it would be a silent\n * divergence between two enforcement lanes, so the rule lives here.\n *\n * `hooks/check-gates.js` encodes the same set inline; that JS port is a\n * deliberate mirror under the existing hook-port convention (change the TS and\n * every JS port together), not an accidental third copy.\n */\nexport const TERMINAL_STATES = new Set([\n \"written\",\n \"passing\",\n \"skipped\",\n \"blocked\",\n]);\nconst VALID_KINDS = new Set([\n \"example\",\n \"property\",\n \"contract\",\n \"approval\",\n \"formal\",\n]);\nconst VALID_SCOPES = new Set([\"unit\", \"integration\", \"e2e\"]);\n/**\n * Parse a markdown table row into cells. Strips the leading/trailing pipe and\n * splits on unescaped pipes. Does NOT handle escaped pipes inside cell content\n * — the trajectory asserts column does not contain pipes in practice.\n */\nfunction parseTableRow(line) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"|\") || !trimmed.endsWith(\"|\"))\n return [];\n return trimmed\n .slice(1, -1)\n .split(\"|\")\n .map((cell) => cell.trim());\n}\n/**\n * Normalize a header cell to a canonical key. Case-insensitive, trims\n * whitespace, maps variant spellings to canonical names.\n */\nfunction normalizeHeader(header) {\n const normalized = header.toLowerCase().replace(/\\s+/g, \" \").trim();\n const aliases = {\n id: \"id\",\n asserts: \"asserts\",\n \"writable at\": \"writableAt\",\n \"passes at\": \"passesAt\",\n state: \"state\",\n kind: \"kind\",\n scope: \"scope\",\n };\n return aliases[normalized] ?? normalized;\n}\n/**\n * Parse a \"Phase N\" / \"Build Phase N\" / \"Test Phase N\" cell into a number.\n * Returns NaN if the cell is not a valid phase reference — the validator\n * catches NaN and emits a specific error.\n */\nfunction parsePhaseReference(cell) {\n return parsePhaseRef(cell)?.number ?? Number.NaN;\n}\n/** Which sequence a phase cell counts in. Build when unspecified. */\nfunction parsePhaseReferenceKind(cell) {\n return parsePhaseRef(cell)?.kind ?? \"build\";\n}\nfunction parseState(cell) {\n const normalized = cell.toLowerCase().trim();\n if (VALID_STATES.has(normalized)) {\n return normalized;\n }\n return \"unknown\";\n}\nfunction parseOptionalKind(cell) {\n const normalized = cell.toLowerCase().trim();\n if (!normalized)\n return undefined;\n if (VALID_KINDS.has(normalized)) {\n return normalized;\n }\n return undefined;\n}\n/**\n * Parse the optional `Test` cell into file paths. Comma-separated, backticks\n * stripped (authors write them as code spans). Empty → undefined, which the\n * verifier reads as \"not checkable\", distinct from \"checked and passed\".\n */\nfunction parseOptionalTestRefs(cell) {\n const refs = cell\n .split(\",\")\n .map((ref) => ref\n .trim()\n .replace(/^`+|`+$/g, \"\")\n .trim())\n .filter((ref) => ref.length > 0);\n return refs.length > 0 ? refs : undefined;\n}\nfunction parseOptionalScope(cell) {\n const normalized = cell.toLowerCase().trim();\n if (!normalized)\n return undefined;\n if (VALID_SCOPES.has(normalized)) {\n return normalized;\n }\n return undefined;\n}\n/**\n * Extract the block of lines under `## Test Trajectory` up to the next\n * heading of equal or lesser depth. The trajectory block includes the\n * main table; the `### Deferred Verification` subsection is extracted\n * separately.\n */\nfunction extractTrajectoryBlock(lines) {\n let inTrajectory = false;\n let inDeferred = false;\n const tableLines = [];\n const deferredLines = [];\n for (const line of lines) {\n if (TRAJECTORY_HEADING.test(line)) {\n inTrajectory = true;\n inDeferred = false;\n continue;\n }\n if (!inTrajectory)\n continue;\n if (DEFERRED_HEADING.test(line)) {\n inDeferred = true;\n continue;\n }\n // A new top-level or second-level heading ends the trajectory block.\n // A third-level heading other than Deferred Verification also ends it.\n if (NEXT_SECTION_HEADING.test(line) && !DEFERRED_HEADING.test(line)) {\n const depth = line.match(/^(#{1,6})/)?.[1].length ?? 0;\n if (depth <= 2)\n break;\n // A ### heading that isn't Deferred Verification ends the trajectory\n if (depth === 3)\n break;\n }\n if (inDeferred) {\n deferredLines.push(line);\n }\n else {\n tableLines.push(line);\n }\n }\n return { tableLines, deferredLines };\n}\n/**\n * Parse a Deferred Verification block. Each deferred item is a top-level\n * bullet with three sub-bullets:\n *\n * - **{name}**\n * - reason: {text}\n * - would require: {text}\n * - mitigation: {text}\n */\nfunction parseDeferredBlock(lines) {\n const rows = [];\n let current = null;\n const flush = () => {\n if (current && current.name !== undefined) {\n rows.push({\n name: current.name,\n reason: current.reason ?? \"\",\n wouldRequire: current.wouldRequire ?? \"\",\n mitigation: current.mitigation ?? \"\",\n });\n }\n current = null;\n };\n for (const rawLine of lines) {\n const line = rawLine.replace(/\\s+$/, \"\");\n // Top-level bullet with bolded name: - **{name}**\n const nameMatch = line.match(/^-\\s+\\*\\*(.+?)\\*\\*\\s*(?:—\\s*(.*))?$/);\n if (nameMatch) {\n flush();\n current = { name: nameMatch[1].trim() };\n // Handle inline one-line form: - **Name** — reason: X — would require: Y — mitigation: Z\n const rest = nameMatch[2];\n if (rest) {\n const reasonMatch = rest.match(/reason:\\s*([^—]+?)(?:\\s*—|$)/i);\n const wrMatch = rest.match(/would require:\\s*([^—]+?)(?:\\s*—|$)/i);\n const mitMatch = rest.match(/mitigation:\\s*(.+)$/i);\n if (reasonMatch)\n current.reason = reasonMatch[1].trim();\n if (wrMatch)\n current.wouldRequire = wrMatch[1].trim();\n if (mitMatch)\n current.mitigation = mitMatch[1].trim();\n }\n continue;\n }\n if (!current)\n continue;\n // Sub-bullet: - reason: / - would require: / - mitigation:\n const subMatch = line.match(/^\\s+-\\s+(reason|would require|mitigation):\\s*(.*)$/i);\n if (subMatch) {\n const key = subMatch[1].toLowerCase();\n const value = subMatch[2].trim();\n if (key === \"reason\")\n current.reason = value;\n else if (key === \"would require\")\n current.wouldRequire = value;\n else if (key === \"mitigation\")\n current.mitigation = value;\n }\n }\n flush();\n return rows;\n}\n/**\n * Parse the trajectory table. Returns rows parsed from the GFM table under\n * `## Test Trajectory`. Unknown column headers are ignored. Rows with\n * missing required columns are skipped — the validator catches structural\n * problems and surfaces them.\n */\nfunction parseTrajectoryTable(lines) {\n const tableLines = lines.filter((line) => line.trim().startsWith(\"|\"));\n if (tableLines.length < 2)\n return [];\n const headerCells = parseTableRow(tableLines[0]);\n const separator = parseTableRow(tableLines[1]);\n // Separator row looks like |----|----|... — every cell is dashes/colons\n const isSeparator = separator.every((cell) => /^:?-+:?$/.test(cell));\n if (!isSeparator)\n return [];\n const columnKeys = headerCells.map(normalizeHeader);\n const rows = [];\n for (let i = 2; i < tableLines.length; i++) {\n const cells = parseTableRow(tableLines[i]);\n if (cells.length !== columnKeys.length)\n continue;\n const record = {};\n for (let j = 0; j < columnKeys.length; j++) {\n record[columnKeys[j]] = cells[j];\n }\n const id = record.id?.trim();\n const asserts = record.asserts?.trim();\n if (!id || !asserts)\n continue;\n rows.push({\n id,\n asserts,\n writableAt: parsePhaseReference(record.writableAt ?? \"\"),\n passesAt: parsePhaseReference(record.passesAt ?? \"\"),\n writableAtKind: parsePhaseReferenceKind(record.writableAt ?? \"\"),\n passesAtKind: parsePhaseReferenceKind(record.passesAt ?? \"\"),\n state: parseState(record.state ?? \"\"),\n kind: parseOptionalKind(record.kind ?? \"\"),\n scope: parseOptionalScope(record.scope ?? \"\"),\n test: parseOptionalTestRefs(record.test ?? \"\"),\n });\n }\n return rows;\n}\n/**\n * Parse a Test Trajectory from an impl.md body (the content after the\n * frontmatter). Returns an empty trajectory with `present: false` when the\n * `## Test Trajectory` section is absent. Never throws — errors are surfaced\n * by the validator, not the parser.\n */\nexport function parseTrajectory(body) {\n const lines = body.split(\"\\n\");\n const hasTrajectory = lines.some((line) => TRAJECTORY_HEADING.test(line));\n if (!hasTrajectory) {\n return { rows: [], deferred: [], present: false };\n }\n const { tableLines, deferredLines } = extractTrajectoryBlock(lines);\n const rows = parseTrajectoryTable(tableLines);\n const deferred = parseDeferredBlock(deferredLines);\n return { rows, deferred, present: true };\n}\n","/**\n * The impl.md parser — string in, phases out. No filesystem.\n *\n * Split from `impl-parser.ts` (admin-ui-phase-progress, Build Phase 3) so the\n * admin UI's browser components can import the one parser: the browser test\n * runtime externalizes `node:fs`, and a static `existsSync` import makes the\n * whole module unloadable there. `impl-parser.ts` re-exports everything here\n * and adds the two functions that touch disk (`resolveImplPath`, `parseImpl`);\n * the `@infinitedusky/indusk-mcp/impl-parser` subpath points at this file.\n */\nimport matter from \"gray-matter\";\nimport { FORWARD_INTELLIGENCE_HEADING, fencedLineMask, gateHeading, parsePhaseHeading, } from \"./impl-headings.js\";\n// One entry per `GateKind` in `impl-headings.ts`. OTel was missing here while\n// the hook-side port (`hooks/_impl-phases.js`) recognised it — the two walks\n// disagreed about a gate kind until admin-ui-phase-progress (A5).\nconst GATE_SUFFIXES = {\n Verification: \"verification\",\n OTel: \"otel\",\n Context: \"context\",\n Document: \"document\",\n};\nfunction parseChecklistItems(lines) {\n const items = [];\n for (const line of lines) {\n const match = line.match(/^-\\s+\\[([ x])\\]\\s+(.*)/);\n if (match) {\n items.push({\n checked: match[1] === \"x\",\n text: match[2].trim(),\n });\n }\n }\n return items;\n}\nexport function parseImplString(raw) {\n // gray-matter touches `Buffer` on every call, which the admin's browser\n // runtime does not have. The admin always hands this a frontmatter-free\n // body (its reader split the frontmatter server-side), so only a document\n // that actually opens with a fence goes through gray-matter — identical\n // output for every Node caller, and loadable in the browser.\n const { data, content } = raw.startsWith(\"---\")\n ? matter(raw)\n : { data: {}, content: raw };\n const title = data.title ?? \"\";\n const status = data.status ?? \"\";\n const lines = content.split(\"\\n\");\n const phases = [];\n let currentPhase = null;\n let currentGateType = \"implementation\";\n let currentGateLines = [];\n let inForwardIntelligence = false;\n let forwardIntelligenceLines = [];\n function flushGate() {\n if (!currentPhase)\n return;\n const items = parseChecklistItems(currentGateLines);\n if (items.length > 0) {\n currentPhase.gates.push({ type: currentGateType, items });\n }\n currentGateLines = [];\n }\n // Fenced blocks are content, not structure: a Test Phase 1 deferral may\n // carry the deferred test's body, and that body contains lines shaped\n // exactly like checklist items and gate headings.\n const fenced = fencedLineMask(lines);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n const line = lines[lineIndex];\n if (fenced[lineIndex])\n continue;\n // Phase header: ### Phase N / ### Build Phase N / ### Test Phase N\n const phaseMatch = parsePhaseHeading(line);\n if (phaseMatch) {\n flushGate();\n if (currentPhase)\n phases.push(currentPhase);\n currentPhase = {\n number: phaseMatch.number,\n kind: phaseMatch.kind,\n ordinal: phases.length,\n name: phaseMatch.name,\n gates: [],\n blocker: null,\n forwardIntelligence: null,\n };\n currentGateType = \"implementation\";\n currentGateLines = [];\n continue;\n }\n // Forward Intelligence header: #### Phase N Forward Intelligence\n const fiMatch = line.match(FORWARD_INTELLIGENCE_HEADING);\n if (fiMatch) {\n flushGate();\n inForwardIntelligence = true;\n forwardIntelligenceLines = [];\n continue;\n }\n // Gate header: #### Phase N Verification|OTel|Context|Document\n const gateMatch = line.match(gateHeading(\"(Verification|OTel|Context|Document)\"));\n if (gateMatch) {\n if (inForwardIntelligence && currentPhase) {\n currentPhase.forwardIntelligence = forwardIntelligenceLines.join(\"\\n\").trim() || null;\n inForwardIntelligence = false;\n }\n flushGate();\n // [1] is the phase number, [2] the gate kind — see gateHeading().\n currentGateType = GATE_SUFFIXES[gateMatch[2]];\n continue;\n }\n // Blocker line: blocker: description\n if (currentPhase) {\n const blockerMatch = line.match(/^blocker:\\s+(.*)/);\n if (blockerMatch) {\n currentPhase.blocker = blockerMatch[1].trim();\n continue;\n }\n }\n if (inForwardIntelligence) {\n forwardIntelligenceLines.push(line);\n }\n else {\n currentGateLines.push(line);\n }\n }\n // Flush last forward intelligence, gate, and phase\n if (inForwardIntelligence && currentPhase) {\n currentPhase.forwardIntelligence = forwardIntelligenceLines.join(\"\\n\").trim() || null;\n }\n flushGate();\n if (currentPhase)\n phases.push(currentPhase);\n return { title, status, phases };\n}\nexport function getPhaseCompletion(phase) {\n const uncheckedByGate = {\n implementation: [],\n verification: [],\n otel: [],\n context: [],\n document: [],\n };\n let totalItems = 0;\n let checkedItems = 0;\n for (const gate of phase.gates) {\n for (const item of gate.items) {\n totalItems++;\n if (item.checked) {\n checkedItems++;\n }\n else {\n uncheckedByGate[gate.type].push(item.text);\n }\n }\n }\n return {\n phase: phase.number,\n ref: { kind: phase.kind, number: phase.number },\n ordinal: phase.ordinal,\n name: phase.name,\n complete: checkedItems === totalItems,\n totalItems,\n checkedItems,\n uncheckedByGate,\n };\n}\nexport function getAllPhaseCompletions(parsed) {\n return parsed.phases.map(getPhaseCompletion);\n}\n/**\n * The phase a `{kind, number}` reference names, or undefined. Number alone is\n * ambiguous once an impl has two sequences (Test Phase 1 / Build Phase 1);\n * every reader that keys a phase should key it this way.\n */\nexport function findPhase(parsed, ref) {\n return parsed.phases.find((p) => p.kind === ref.kind && p.number === ref.number);\n}\n","import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPlanningDir } from \"./config.js\";\nimport { fencedLineMask } from \"./impl-headings.js\";\nimport { DOCUMENT_POSITIONS, isFinishedDocumentStatus, nextRequiredDocument } from \"./lifecycle.js\";\nimport { leastAdvancedPaperStatus, paperNextStep, readPaper, } from \"./papers/summary.js\";\nimport { isCleanSegment } from \"./path-segment.js\";\nimport { readWorkflow } from \"./workflow-types.js\";\n// Re-exported so the `planning/plan-parser` subpath and existing imports keep\n// working; the definitions live in papers/summary.ts.\nexport { PAPER_STATUSES, paperContentHash, paperIsStale, } from \"./papers/summary.js\";\nfunction parseFrontmatter(filePath) {\n if (!existsSync(filePath))\n return { frontmatter: null };\n const raw = readFileSync(filePath, \"utf-8\");\n try {\n const { data } = matter(raw);\n return {\n frontmatter: {\n title: data.title ?? \"\",\n date: data.date ?? \"\",\n status: data.status ?? \"\",\n },\n };\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] frontmatter parse error in ${filePath}: ${message}\\n`);\n return {\n frontmatter: null,\n parseError: { file: filePath, message },\n };\n }\n}\n/**\n * One rule for what a plan's stage is when papers are present: a lifecycle\n * document always wins, and only a folder with no lifecycle document at all\n * is a paper-stage plan. So no existing plan changes stage by gaining a\n * paper, and a folder of papers stops reading as unknown / \"Create a brief\".\n */\nfunction resolvePlanStage(walked, papers, type) {\n if (walked.stage === \"unknown\" && papers.length > 0) {\n return {\n stage: \"paper\",\n stageStatus: leastAdvancedPaperStatus(papers),\n nextStep: paperNextStep(papers),\n };\n }\n return {\n stage: walked.stage,\n stageStatus: walked.stageStatus,\n nextStep: determineNextStep(walked.stage, walked.stageStatus, type, walked.parseError),\n };\n}\nfunction parseDependsOn(filePath) {\n if (!existsSync(filePath))\n return [];\n const content = readFileSync(filePath, \"utf-8\");\n const depsMatch = content.match(/## Depends On\\s*\\n([\\s\\S]*?)(?=\\n## |\\n$|$)/);\n if (!depsMatch)\n return [];\n const deps = [];\n for (const line of depsMatch[1].split(\"\\n\")) {\n const match = line.match(/^-\\s+`?(?:\\.indusk\\/)?planning\\/([^/`]+)\\/?`?/);\n if (match) {\n deps.push(match[1]);\n }\n }\n return deps;\n}\nfunction determineStage(planDir, docs) {\n // Walk stages in reverse to find the most advanced document\n for (let i = DOCUMENT_POSITIONS.length - 1; i >= 0; i--) {\n const stage = DOCUMENT_POSITIONS[i];\n const file = `${stage}.md`;\n if (docs.includes(file)) {\n const result = parseFrontmatter(join(planDir, file));\n if (result.parseError) {\n return {\n stage: \"malformed\",\n stageStatus: \"parse-error\",\n parseError: result.parseError,\n };\n }\n return { stage, stageStatus: result.frontmatter?.status ?? \"unknown\" };\n }\n }\n return { stage: \"unknown\", stageStatus: \"unknown\" };\n}\nfunction determineNextStep(stage, stageStatus, type, parseError) {\n if (stage === \"malformed\" && parseError) {\n return `Fix YAML frontmatter at ${parseError.file}: ${parseError.message}`;\n }\n if (stage === \"unknown\")\n return \"Create a brief\";\n if (isFinishedDocumentStatus(stageStatus)) {\n const next = nextRequiredDocument(stage, type);\n if (next)\n return `Create ${next}`;\n return \"Done\";\n }\n if (stageStatus === \"in-progress\") {\n return `Continue ${stage}`;\n }\n return `Review ${stage} (status: ${stageStatus})`;\n}\n/**\n * The plan's `workflow:` declaration. It lives in the brief; a research-only\n * plan has no brief, so its research document is read instead. Unreadable\n * frontmatter reads as nothing declared — the parse error is reported by the\n * stage walk, and a second report here would name the same file twice.\n */\nfunction readDeclaredWorkflow(planDir, documents) {\n const source = documents.includes(\"brief.md\")\n ? \"brief.md\"\n : documents.includes(\"research.md\")\n ? \"research.md\"\n : null;\n if (source === null)\n return readWorkflow(undefined);\n try {\n const raw = readFileSync(join(planDir, source), \"utf-8\");\n return readWorkflow(matter(raw).data.workflow, rawFrontmatterValue(raw, \"workflow\"));\n }\n catch {\n return readWorkflow(undefined);\n }\n}\n/**\n * The text written after `key:` on its own line of a document's frontmatter,\n * or null when that line is empty or absent (a value written on the lines\n * below it, like a block list, has no text there). Line-anchored, so a longer\n * key that ends in this one is not it.\n */\nfunction rawFrontmatterValue(raw, key) {\n const block = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(raw)?.[1];\n if (block === undefined)\n return null;\n const text = new RegExp(`^${key}:[ \\\\t]*(.*)$`, \"m\").exec(block)?.[1]?.trim();\n return text ? text : null;\n}\nexport function parsePlan(planDir) {\n const name = planDir.split(\"/\").pop() ?? \"\";\n const entries = readdirSync(planDir).filter((f) => f.endsWith(\".md\"));\n const walked = determineStage(planDir, entries);\n const dependencies = parseDependsOn(join(planDir, \"brief.md\"));\n const papers = entries\n .map((file) => readPaper(planDir, file))\n .filter((p) => p !== null);\n const declared = readDeclaredWorkflow(planDir, entries);\n const { stage, stageStatus, nextStep } = resolvePlanStage(walked, papers, declared.type);\n return {\n name,\n stage,\n stageStatus,\n nextStep,\n dependencies,\n documents: entries,\n ...(declared.type !== null && { workflow: declared.type }),\n ...(declared.type === null &&\n declared.declared !== null && { workflowDeclared: declared.declared }),\n ...(papers.length > 0 && { papers }),\n ...(walked.parseError && { parseError: walked.parseError }),\n };\n}\n// A declaration name must be a single clean path segment — it gets joined into\n// filesystem paths (`join(planningDir, name, \"master.md\")`) and rendered\n// verbatim in the sidebar. The guard now lives in `lib/path-segment.ts`,\n// shared with workbench repo names, which are the same question.\n/**\n * Read a frontmatter key as a string array; anything else yields [].\n * Non-string entries, non-segment names, and duplicates are dropped —\n * duplicates collapse to first occurrence so declared order is preserved.\n */\nfunction stringArray(data, key) {\n const value = data[key];\n if (!Array.isArray(value))\n return [];\n const out = [];\n for (const entry of value) {\n if (typeof entry !== \"string\" || !isCleanSegment(entry))\n continue;\n if (out.includes(entry))\n continue;\n out.push(entry);\n }\n return out;\n}\n/**\n * Frontmatter of a `master.md`, or null when absent/unreadable/malformed.\n *\n * gray-matter throws on malformed YAML in plain Node but returns `data: {}`\n * inside vitest — so this treats both the throw and the empty-object case as\n * \"no declaration\", which is the same safe outcome either way.\n */\nfunction readMasterFrontmatter(masterPath) {\n if (!existsSync(masterPath))\n return null;\n try {\n return matter(readFileSync(masterPath, \"utf-8\")).data;\n }\n catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] master frontmatter unreadable in ${masterPath}: ${message}\\n`);\n return null;\n }\n}\n/**\n * Read the plan hierarchy declarations from a planning directory.\n *\n * Never throws and never reports a plan: a missing file, absent key, or\n * malformed YAML each degrade to empty, which renders as today's flat list.\n * Losing structure is acceptable; losing a plan is not.\n */\nexport function readPlanDeclarations(planningDir) {\n const empty = { parents: [], roadmap: [], subplans: {} };\n if (!existsSync(planningDir))\n return empty;\n const rootPath = join(planningDir, \"master.md\");\n const rootData = readMasterFrontmatter(rootPath);\n const parents = rootData ? stringArray(rootData, \"parents\") : [];\n const roadmap = rootData ? stringArray(rootData, \"roadmap\") : [];\n const root = existsSync(rootPath)\n ? { name: \"master\", title: rootTitle(rootPath, rootData) }\n : undefined;\n // A folder's own master.md is what makes it a parent in practice, so read\n // every candidate — those named in `parents:` plus any plan carrying a\n // master.md — and let the presence of children decide. This keeps a stale\n // `parents:` entry from suppressing a real declaration, and vice versa.\n const candidates = new Set(parents);\n try {\n for (const entry of readdirSync(planningDir, { withFileTypes: true })) {\n if (entry.isDirectory() && existsSync(join(planningDir, entry.name, \"master.md\"))) {\n candidates.add(entry.name);\n }\n }\n }\n catch {\n // Unreadable planning dir — fall through with whatever `parents:` gave us.\n }\n const subplans = {};\n for (const parent of candidates) {\n const data = readMasterFrontmatter(join(planningDir, parent, \"master.md\"));\n if (!data)\n continue;\n subplans[parent] = stringArray(data, \"subplans\");\n }\n return { ...(root ? { root } : {}), parents, roadmap, subplans };\n}\nexport function parseAllPlans(projectRoot) {\n const planningDir = getPlanningDir(projectRoot);\n if (!existsSync(planningDir))\n return [];\n return readdirSync(planningDir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => {\n try {\n return parsePlan(join(planningDir, d.name));\n }\n catch (err) {\n // Defense-in-depth: parsePlan should never throw post-1.31.6 (the\n // frontmatter parser catches YAML errors and returns a malformed\n // PlanSummary). If something else fails (filesystem race,\n // unexpected I/O error), return a placeholder so one broken plan\n // doesn't take down the whole list.\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`[plan-parser] error parsing ${d.name}: ${message}\\n`);\n return {\n name: d.name,\n stage: \"malformed\",\n stageStatus: \"parse-error\",\n nextStep: `Fix plan directory ${d.name}: ${message}`,\n dependencies: [],\n documents: [],\n parseError: { file: join(planningDir, d.name), message },\n };\n }\n })\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n/**\n * The root master's display title: frontmatter `title`, else the first `# `\n * heading of the document BODY, else \"master\".\n *\n * The body, not the raw file (A28): the real root master carries `# …` YAML\n * comment lines inside its frontmatter, and a raw-file match titled the whole\n * sidebar with the first of them. Fenced code is masked for the same reason —\n * a shell comment is not a heading.\n */\nfunction rootTitle(masterPath, data) {\n const declared = data?.title;\n if (typeof declared === \"string\" && declared.trim() !== \"\")\n return declared.trim();\n try {\n const raw = readFileSync(masterPath, \"utf-8\");\n const body = raw.startsWith(\"---\") ? matter(raw).content : raw;\n const lines = body.split(\"\\n\");\n const fenced = fencedLineMask(lines);\n for (const [i, line] of lines.entries()) {\n if (fenced[i])\n continue;\n const heading = line.match(/^#\\s+(.+)$/);\n if (heading)\n return heading[1].trim();\n }\n }\n catch {\n // unreadable — fall through to the folder-ish default\n }\n return \"master\";\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { readPublishedRecord } from \"./provenance.js\";\n/**\n * Papers as documents: the status vocabulary, the summary the parser reports,\n * the content hash a publish records, and staleness derived from it.\n *\n * Lifted out of `plan-parser.ts` (writing-skill cleanup) because the parser\n * is the lifecycle-document parser and this is a different document kind;\n * the parser re-exports the public names so the `planning/plan-parser`\n * subpath and every existing import keep working. This module must not\n * import the parser.\n */\n/**\n * Papers — prose documents that declare `kind: paper` in frontmatter and live\n * beside (or instead of) the lifecycle documents. Declared, never inferred\n * from filenames: a thesis, a shape document, and an essay can all be papers,\n * and nothing about a name says which. See `.indusk/planning/writing-skill/adr.md`.\n */\nexport const PAPER_STATUSES = [\"draft\", \"accepted\", \"published\"];\n/**\n * The content hash a publish records and staleness compares against.\n *\n * Hashes the document with `status` and the `published` block removed, so\n * writing either back after a publish does not change what it hashes; a\n * publish computes this on the pre-write content and the next read computes\n * it on the written file and gets the same answer.\n */\nexport function paperContentHash(raw) {\n const { data, content } = matter(raw);\n const rest = { ...data };\n delete rest.published;\n delete rest.status;\n return `sha256:${createHash(\"sha256\").update(matter.stringify(content, rest)).digest(\"hex\")}`;\n}\nfunction isPaperStatus(value) {\n return typeof value === \"string\" && PAPER_STATUSES.includes(value);\n}\n/**\n * One document read as a paper; null when it does not declare `kind: paper`.\n * A document whose frontmatter cannot be parsed cannot declare anything, so\n * it is not a paper here — the lifecycle walk reports it if it is a lifecycle\n * document, and the admin's raw view shows it either way.\n */\nexport function readPaper(planDir, file) {\n const raw = readFileSync(join(planDir, file), \"utf-8\");\n let data;\n try {\n data = matter(raw).data;\n }\n catch {\n return null;\n }\n if (data.kind !== \"paper\")\n return null;\n const status = isPaperStatus(data.status) ? data.status : \"malformed\";\n return {\n file,\n title: typeof data.title === \"string\" ? data.title : file,\n status,\n stale: paperIsStale(raw, data, status),\n };\n}\n/**\n * The staleness policy, in one place: a published paper is stale when its\n * current content hash differs from the one the publish recorded, or when\n * no hash was recorded at all (published by hand; cannot be confirmed\n * current, so it reads stale rather than reassuringly fresh). Anything not\n * yet published has nothing to be stale against. The publish step records\n * exactly what this compares, so the two must agree here, not in two places.\n */\nexport function paperIsStale(raw, data, status) {\n if (status !== \"published\")\n return false;\n const published = readPublishedRecord(data);\n const recorded = typeof published?.hash === \"string\" ? published.hash : null;\n return recorded === null || recorded !== paperContentHash(raw);\n}\nconst PAPER_STATUS_ORDER = {\n malformed: 0,\n draft: 1,\n accepted: 2,\n published: 3,\n};\n/** The least-advanced paper's status; `malformed` outranks everything so it surfaces. */\nexport function leastAdvancedPaperStatus(papers) {\n return papers.reduce((least, p) => (PAPER_STATUS_ORDER[p.status] < PAPER_STATUS_ORDER[least] ? p.status : least), \"published\");\n}\nexport function paperNextStep(papers) {\n const malformed = papers.find((p) => p.status === \"malformed\");\n if (malformed) {\n return `Fix paper status in ${malformed.file} (expected ${PAPER_STATUSES.join(\" | \")})`;\n }\n const draft = papers.find((p) => p.status === \"draft\");\n if (draft)\n return `Review paper: ${draft.file}`;\n const owed = papers.filter((p) => p.status === \"accepted\" || p.stale).length;\n if (owed > 0)\n return `Publish ${owed} paper(s)`;\n return \"Done\";\n}\n","/**\n * The provenance a publish writes back into the paper's frontmatter.\n *\n * A text edit, deliberately not a gray-matter round trip: the round trip\n * re-dumps every key, and js-yaml turns an unquoted `date: 2026-09-09` into\n * an ISO timestamp and strips quotes it considers unnecessary. Every publish\n * would then rewrite lines it has no business touching. This replaces only\n * the `status` line and the `published` block, and leaves every other byte\n * of the frontmatter where it was.\n */\n/** The `published` block of a parsed frontmatter, or undefined when absent or not an object. */\nexport function readPublishedRecord(data) {\n const block = data.published;\n return block !== null && typeof block === \"object\" ? block : undefined;\n}\nconst FENCE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n/;\nexport function withProvenance(raw, prov) {\n const match = FENCE.exec(raw);\n if (!match)\n throw new Error(\"paper has no frontmatter fence to write provenance into\");\n const kept = [];\n let inPublishedBlock = false;\n for (const line of match[1].split(/\\r?\\n/)) {\n if (/^status:/.test(line)) {\n inPublishedBlock = false;\n continue;\n }\n if (/^published:/.test(line)) {\n inPublishedBlock = true;\n continue;\n }\n if (inPublishedBlock && /^[ \\t]/.test(line))\n continue;\n inPublishedBlock = false;\n kept.push(line);\n }\n // Commits are quoted: an all-digit short sha would otherwise read as a number.\n kept.push(\"status: published\", \"published:\", ` destination: ${prov.destination}`, ` path: ${prov.path}`, ` commit: \"${prov.commit}\"`, ` source_commit: \"${prov.source_commit}\"`, ` hash: ${prov.hash}`);\n return `---\\n${kept.join(\"\\n\")}\\n---\\n${raw.slice(match[0].length)}`;\n}\n","import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { getPlanningDir } from \"../config.js\";\nimport { parsePlan } from \"../plan-parser.js\";\nimport { getQuietWindowDays } from \"./config.js\";\nimport { recorded } from \"./incidents.js\";\nimport { readPromises } from \"./registry.js\";\nimport { openMaintenancePhasesIn } from \"./reopen.js\";\n/**\n * What a closed plan is doing after it closed (day-monitor, ADR D7, D8).\n *\n * An archived plan normally reads `archived`. It is back in motion when an\n * incident appended a Maintenance phase to its impl and that phase has an\n * unchecked item — **reopened**: the plan tools list it active and it reads\n * executing that phase. Otherwise, a plan holding a behaviour promise is in\n * **monitor** while `now − max(closed, lastViolation) < window`: `closed` is\n * the date on its retrospective's \"Landed on main at …\" line (else the\n * retrospective's `date`), `lastViolation` the latest `last_seen` among its\n * promises' incidents, and `window` the quiet window. Derived from files\n * only; nothing here opens a socket.\n */\nexport const ARCHIVE_DIR = \"archive\";\nconst DAY_MS = 86_400_000;\n/** When the plan closed: its retrospective's landing date, else its `date`. Null without a retrospective. */\nexport function closedAt(planDir) {\n const path = join(planDir, \"retrospective.md\");\n if (!existsSync(path))\n return null;\n const text = readFileSync(path, \"utf-8\");\n const landed = /Landed on main at[^\\n]*?(\\d{4}-\\d{2}-\\d{2})/.exec(text)?.[1];\n if (landed)\n return new Date(`${landed}T00:00:00Z`);\n const date = matter(text).data.date;\n const d = date instanceof Date ? date : typeof date === \"string\" ? new Date(date) : null;\n return d && !Number.isNaN(d.getTime()) ? d : null;\n}\nfunction registryOf(projectRoot) {\n const read = readPromises(projectRoot);\n if (read.ok)\n return read.registry;\n return \"partial\" in read ? read.partial : null;\n}\n/** The plan's quiet window, when it is inside one. */\nexport function monitorWindow(projectRoot, name, planDir, registry, now = new Date()) {\n if (!registry)\n return null;\n const held = registry.promises.filter((p) => p.owner === name && p.kind === \"behaviour\" && p.state !== \"retired\");\n if (held.length === 0)\n return null;\n const closed = closedAt(planDir);\n if (!closed)\n return null;\n const names = new Set(held.map((p) => p.name));\n let lastViolation = null;\n for (const i of registry.incidents) {\n if (!names.has(i.promise))\n continue;\n const seen = recorded(join(registry.dir, i.file)).lastSeen ?? i.date;\n const d = new Date(seen);\n if (!Number.isNaN(d.getTime()) && (!lastViolation || d > lastViolation))\n lastViolation = d;\n }\n const restarted = lastViolation !== null && lastViolation > closed;\n const anchor = restarted && lastViolation ? lastViolation : closed;\n const windowDays = getQuietWindowDays(projectRoot);\n const elapsedDays = Math.max(0, (now.getTime() - anchor.getTime()) / DAY_MS);\n if (elapsedDays >= windowDays)\n return null;\n return {\n windowDays,\n elapsedDays: Math.round(elapsedDays * 10) / 10,\n restartedAt: restarted && lastViolation ? lastViolation.toISOString() : null,\n };\n}\nfunction archiveDir(projectRoot) {\n return join(getPlanningDir(projectRoot), ARCHIVE_DIR);\n}\n/** The archived plan's folder, or null when there is none by that name. */\nexport function archivedPlanDir(projectRoot, name) {\n const dir = join(archiveDir(projectRoot), name);\n return existsSync(join(dir)) && name !== \"\" && !name.includes(\"/\") ? dir : null;\n}\n/**\n * What an archived plan is doing, as facts: its open Maintenance phases and\n * its quiet window. The admin passes this to `derivePlanPosition` as\n * `afterClose`; `archivedPlan` builds the tools' summary from it.\n */\nexport function afterClose(projectRoot, name, planDir, registry = registryOf(projectRoot), now = new Date()) {\n const reopened = openMaintenancePhasesIn(planDir);\n return {\n reopened,\n monitor: reopened.length > 0 ? null : monitorWindow(projectRoot, name, planDir, registry, now),\n };\n}\n/** One archived plan as the plan tools report it. */\nexport function archivedPlan(projectRoot, name, registry = registryOf(projectRoot), now = new Date()) {\n const dir = archivedPlanDir(projectRoot, name);\n if (!dir)\n return null;\n const summary = parsePlan(dir);\n const { reopened, monitor } = afterClose(projectRoot, name, dir, registry, now);\n if (reopened.length > 0) {\n return {\n ...summary,\n stage: \"impl\",\n stageStatus: \"in-progress\",\n nextStep: `Work ${reopened[0]}, then check its phase off`,\n archived: true,\n reopened,\n monitor: null,\n };\n }\n if (monitor) {\n return {\n ...summary,\n stage: \"monitor\",\n stageStatus: \"monitor\",\n nextStep: `Quiet window: ${Math.floor(monitor.elapsedDays)} of ${monitor.windowDays} days${monitor.restartedAt ? `, restarted ${monitor.restartedAt.slice(0, 10)}` : \"\"}`,\n archived: true,\n reopened,\n monitor,\n };\n }\n return {\n ...summary,\n stage: \"archived\",\n stageStatus: \"archived\",\n archived: true,\n reopened,\n monitor: null,\n };\n}\n/** Archived plans that are back in motion — reopened or in monitor — the ones the active list must show. */\nexport function archivedInMotion(projectRoot) {\n const dir = archiveDir(projectRoot);\n if (!existsSync(dir))\n return [];\n const registry = registryOf(projectRoot);\n const now = new Date();\n return readdirSync(dir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => archivedPlan(projectRoot, d.name, registry, now))\n .filter((p) => p !== null && (p.reopened.length > 0 || p.monitor !== null));\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport matter from \"gray-matter\";\nimport { setList, setScalar } from \"./frontmatter-edit.js\";\nimport { INCIDENTS_SUBDIR, NOT_YET_FIXED, UNWRITTEN_ROOT_CAUSE, } from \"./vocabulary.js\";\nfunction iso(d) {\n return d.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\nfunction stringList(v) {\n return Array.isArray(v) ? v.map(String) : [];\n}\n/** Trace ids and `last_seen` an incident file already records. */\nexport function recorded(path) {\n const data = matter(readFileSync(path, \"utf-8\")).data;\n const last = data.last_seen;\n return {\n traces: stringList(data.traces),\n lastSeen: last instanceof Date ? iso(last) : typeof last === \"string\" ? last : null,\n };\n}\n/**\n * The next free incident id for a promise on a day. An id is taken when its\n * file exists OR when `avoid` names it — the ids the owner's Maintenance\n * phases already carry. A phase outlives its incident file, and a deleted\n * file once freed its id for the next incident to collide with\n * (watch-reopen-collision).\n */\nfunction newIncidentId(dir, promise, day, avoid) {\n const free = (id) => !avoid.has(id) && !existsSync(join(dir, `${id}.md`));\n const base = `i-${day}-${promise}`;\n if (free(base))\n return base;\n for (let n = 2;; n++) {\n if (free(`${base}-${n}`))\n return `${base}-${n}`;\n }\n}\n/**\n * A string that arrived on a span is untrusted input to a plan document.\n *\n * `environment` and `symptom` come from a deployed system, and they are\n * written into committed YAML and markdown. A newline in `environment` adds\n * frontmatter keys — a duplicate key makes js-yaml throw, which takes the\n * whole registry down; a newline in `symptom` forges the `## Root cause`\n * section the registry insists a person writes (A28).\n *\n * Collapsed, not escaped, and not refused: a violation is still real when the\n * system reporting it sends something odd, and losing it would be the worse\n * failure. The oddness is kept visible rather than silently dropped.\n */\nexport function oneLine(value) {\n return value.replace(/[\\r\\n\\u2028\\u2029]+/g, \" \").trim();\n}\nfunction incidentText(o) {\n const frontmatter = [\n `id: ${o.id}`,\n `promise: ${o.promise}`,\n `source: ${o.source}`,\n ...(o.environment ? [`environment: ${JSON.stringify(oneLine(o.environment))}`] : []),\n \"status: open\",\n `date: '${o.opened.slice(0, 10)}'`,\n `opened: '${o.opened}'`,\n `last_seen: '${o.lastSeen}'`,\n \"traces:\",\n ...o.traces.map((t) => ` - '${t}'`),\n ];\n return `---\\n${frontmatter.join(\"\\n\")}\\n---\\n\\n## Symptom\\n\\n${oneLine(o.symptom)}\\n\\n## Root cause\\n\\n${UNWRITTEN_ROOT_CAUSE}\\n\\n## Fix\\n\\n${NOT_YET_FIXED}\\n`;\n}\n/**\n * Record `violations` of `promise` in the registry at `registry.dir`. Returns\n * what changed, or null when every trace was already recorded.\n */\nexport function recordViolations(registry, promise, violations, source, now, \n/** Ids an opened incident must not take: those the owner's Maintenance phases name. */\navoid = new Set()) {\n const dir = join(registry.dir, INCIDENTS_SUBDIR);\n const mine = registry.incidents.filter((i) => i.promise === promise.name);\n const known = new Set(mine.flatMap((i) => recorded(join(registry.dir, i.file)).traces));\n const fresh = violations.filter((v) => !known.has(v.traceId));\n if (fresh.length === 0)\n return null;\n const freshIds = [...new Set(fresh.map((v) => v.traceId))];\n const newest = fresh.reduce((a, b) => (b.at > a.at ? b : a));\n const open = mine.find((i) => i.status === \"open\");\n if (open) {\n const path = join(registry.dir, open.file);\n const before = recorded(path);\n const lastSeen = before.lastSeen && before.lastSeen > iso(newest.at) ? before.lastSeen : iso(newest.at);\n let text = readFileSync(path, \"utf-8\");\n text = setList(text, \"traces\", [...before.traces, ...freshIds]);\n text = setScalar(text, \"last_seen\", lastSeen);\n writeFileSync(path, text);\n ensurePromiseCarries(registry, promise, open.id);\n return { id: open.id, kind: \"extended\", traces: freshIds };\n }\n mkdirSync(dir, { recursive: true });\n const oldest = fresh.reduce((a, b) => (b.at < a.at ? b : a));\n const id = newIncidentId(dir, promise.name, iso(now).slice(0, 10), avoid);\n writeFileSync(join(dir, `${id}.md`), incidentText({\n id,\n promise: promise.name,\n source,\n opened: iso(oldest.at),\n lastSeen: iso(newest.at),\n traces: freshIds,\n symptom: newest.symptom ?? \"The span marked the promise violated and carried no symptom.\",\n // The newest violation's environment, and no guess when it has\n // none: one server holds staging and production, and an incident\n // that names the wrong one sends a person to the wrong logs.\n environment: newest.environment,\n }));\n ensurePromiseCarries(registry, promise, id);\n return { id, kind: \"opened\", traces: freshIds };\n}\n/** An enforced promise with an open incident moves to known-violated, and lists it. */\nfunction ensurePromiseCarries(registry, promise, id) {\n const path = join(registry.dir, promise.file);\n const before = readFileSync(path, \"utf-8\");\n let text = before;\n if (!promise.incidents.includes(id))\n text = setList(text, \"incidents\", [...promise.incidents, id]);\n if (promise.state === \"enforced\")\n text = setScalar(text, \"state\", \"known-violated\");\n if (text !== before)\n writeFileSync(path, text);\n}\n","import { existsSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shouldEmitOtelGate } from \"../config.js\";\nimport { parseImplString } from \"../impl-parser-core.js\";\nimport { appendLateRow } from \"../trajectory/append-row.js\";\n/**\n * Reopening a plan is an appended phase, in place (day-monitor, ADR D7).\n *\n * An incident sends the promise's owning plan back to work: its impl gains\n * `### Build Phase N: Maintenance — <incident>` with its gates (OTel too when\n * the project's `otel.role` asks for it, as the validator will), numbered\n * after the owner's own build phases. The plan folder never moves — dozens of\n * pointers cite archive paths — and an owner with no impl gets one holding\n * only this phase. A plan whose impl already has the incident's Maintenance\n * phase is left alone, so a second pass writes nothing.\n */\nexport const MAINTENANCE_TITLE = \"Maintenance\";\nexport function maintenanceHeadingName(incidentId) {\n return `${MAINTENANCE_TITLE} — ${incidentId}`;\n}\n/**\n * The incident ids an impl's Build-phase Maintenance headings already name —\n * the inverse of `maintenanceHeadingName`. The id allocator avoids them\n * (watch-reopen-collision): a phase outlives its incident file, so a deleted\n * file must not free its id for the next incident to collide with.\n */\nexport function maintenanceIncidentIds(implText) {\n const prefix = maintenanceHeadingName(\"\");\n return new Set(parseImplString(implText)\n .phases.filter((p) => p.kind === \"build\" && p.name.startsWith(prefix))\n .map((p) => p.name.slice(prefix.length).trim())\n .filter((id) => id !== \"\"));\n}\n/** The owner's folder: active wins over archived, as everywhere else. */\nexport function ownerDir(planRoot, owner) {\n for (const dir of [\n join(planRoot, \".indusk\", \"planning\", owner),\n join(planRoot, \".indusk\", \"planning\", \"archive\", owner),\n ]) {\n if (existsSync(dir) && statSync(dir).isDirectory())\n return dir;\n }\n return null;\n}\nfunction maintenancePhase(n, incidentId, otelGate, rowId) {\n const otel = otelGate\n ? [\n `#### Build Phase ${n} OTel`,\n \"\",\n \"- [ ] The fixed code still marks the promise on its span (`indusk.promise`, `indusk.promise.outcome`)\",\n \"\",\n ]\n : [];\n return [\n `### Build Phase ${n}: ${maintenanceHeadingName(incidentId)}`,\n \"\",\n `- [ ] Write the root cause in the incident (\\`.indusk/promises/incidents/${incidentId}.md\\`)`,\n \"- [ ] Fix: a code site, a widened test, or a revised promise\",\n \"\",\n `#### Build Phase ${n} Verification`,\n \"\",\n rowId\n ? `- [ ] ${rowId}: the test that reproduces the incident passes, and the promise is seen upheld after the fix (\\`indusk promises status\\`)`\n : \"- [ ] The promise is seen upheld after the fix (`indusk promises status`)\",\n \"\",\n ...otel,\n `#### Build Phase ${n} Context`,\n \"\",\n \"- [ ] CLAUDE.md, if the fix changes a convention\",\n \"\",\n `#### Build Phase ${n} Document`,\n \"\",\n \"- [ ] The incident's Fix section\",\n \"\",\n ].join(\"\\n\");\n}\n/** Append the incident's Maintenance phase to `owner`'s impl. */\nexport function reopenOwner(planRoot, owner, incidentId, promise, \n/** The owner's live folder when it is assigned to a worktree (day-monitor A29); else found here. */\nliveDir, \n/** Whether the incident was just opened or extended; an existing heading means different things. */\nkind = \"extended\") {\n const dir = liveDir ?? ownerDir(planRoot, owner);\n if (!dir)\n return { reopened: false, reason: \"no-owner\" };\n const implPath = join(dir, \"impl.md\");\n // The phase carries every gate the validator will ask this project for.\n const otelGate = shouldEmitOtelGate(planRoot);\n if (!existsSync(implPath)) {\n writeFileSync(implPath, `---\\ntitle: \"${owner} — maintenance\"\\nstatus: in-progress\\n---\\n\\n# ${owner} — maintenance\\n\\n## Checklist\\n\\n${maintenancePhase(1, incidentId, otelGate, null)}`);\n return { reopened: true, impl: implPath, phase: 1 };\n }\n const text = readFileSync(implPath, \"utf-8\");\n const parsed = parseImplString(text);\n const name = maintenanceHeadingName(incidentId);\n // The same question `watch`'s unowned retry asks: the reopen's dedup and\n // the retry must agree about whether the owner carries this incident.\n if (maintenanceIncidentIds(text).has(incidentId)) {\n return kind === \"opened\"\n ? { reopened: false, reason: \"collision\", heading: name }\n : { reopened: false, reason: \"already\" };\n }\n const next = Math.max(0, ...parsed.phases.filter((p) => p.kind === \"build\").map((p) => p.number)) + 1;\n const withRow = appendLateRow(text, next, {\n asserts: `${promise} holds again after ${incidentId}: the test that reproduces it, named by its root cause, passes`,\n why: `the test that reproduces ${incidentId} is decided by its root cause, which this Maintenance phase writes first`,\n });\n writeFileSync(implPath, `${withRow.text.replace(/\\n*$/, \"\\n\")}\\n${maintenancePhase(next, incidentId, otelGate, withRow.id)}`);\n return { reopened: true, impl: implPath, phase: next };\n}\n/** An impl with an unchecked item under a Maintenance phase — the plan is reopened. */\nexport function openMaintenancePhases(implText) {\n return parseImplString(implText)\n .phases.filter((p) => p.name.startsWith(`${MAINTENANCE_TITLE} — `) &&\n p.gates.some((g) => g.items.some((i) => !i.checked)))\n .map((p) => p.name);\n}\n/** `openMaintenancePhases` of the impl in plan folder `dir`; none when there is no impl. */\nexport function openMaintenancePhasesIn(dir) {\n const implPath = join(dir, \"impl.md\");\n return existsSync(implPath) ? openMaintenancePhases(readFileSync(implPath, \"utf-8\")) : [];\n}\n","import { execFile } from \"node:child_process\";\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { promisify } from \"node:util\";\n/**\n * The git primitives more than one subsystem needs.\n *\n * Deliberately *not* inside `verify/` or `shape/`: both of those are domain\n * modules with their own vocabulary (verify's `assertGitRepo` refuses in\n * verify's words; shape's scoping answers shape's question), and a primitive\n * that lives in one domain's folder gets copied by the next domain rather than\n * imported. That is exactly what happened here — `shape/changed.ts` carried a\n * byte-identical copy of verify's runner, down to the maxBuffer.\n */\nconst execFileAsync = promisify(execFile);\n/** Run git in `root` and return trimmed stdout. Throws on non-zero exit. */\nexport async function git(root, ...args) {\n const { stdout } = await execFileAsync(\"git\", args, { cwd: root, maxBuffer: 32 * 1024 * 1024 });\n return stdout.trim();\n}\n/**\n * HEAD of `root`. Throws when there is none — not a repository, or an unborn\n * branch. The one spelling of \"which commit is this tree at\" (A19): verify's\n * ledger, the commit cadence's record and the run loop's trailer each wrote\n * their own before it lived here.\n */\nexport async function headSha(root) {\n return git(root, \"rev-parse\", \"--verify\", \"HEAD\");\n}\n/**\n * HEAD of `root`, or null when there is nothing to name — an unborn branch\n * (a greenfield repo before its first commit) or no repository at all.\n * Nothing to attest is a fact to record as absence, not an exception to\n * throw through a tool call after the edit already applied (A17).\n */\nexport async function headShaOrNull(root) {\n try {\n return (await headSha(root)) || null;\n }\n catch {\n return null;\n }\n}\n/**\n * What changed since `sha`, keeping tracked and untracked apart.\n *\n * The untracked half is not a nicety. `git diff` reports tracked modifications\n * only, so an agent that writes real code without `git add`ing it produced a\n * diff containing nothing but the plan file — and looked, to phantom detection,\n * exactly like an agent that wrote nothing at all. Half-in/half-out on the\n * working tree is the bug; either stance is defensible, the mixture is not.\n *\n * Partitioned rather than unioned because the two halves carry different\n * evidence about *when* they happened. Committed work is placed in time by its\n * commit; untracked work has only its mtime, which is the only way to tell a\n * scratch file left by an earlier phase from this phase's real work. Callers\n * that do not care can union the two — `changedPathsSince` does exactly that.\n */\nexport async function changedPathsPartitioned(root, sha) {\n const committed = await git(root, \"diff\", \"--name-only\", sha, \"HEAD\");\n const unstaged = await git(root, \"diff\", \"--name-only\", \"HEAD\");\n const untracked = await git(root, \"ls-files\", \"--others\", \"--exclude-standard\");\n const clean = (raw) => raw\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n return {\n tracked: [...new Set([...clean(committed), ...clean(unstaged)])],\n untracked: clean(untracked),\n };\n}\nexport function snapshotPaths(root, rels) {\n const taken = new Map();\n for (const rel of rels) {\n const path = join(root, rel);\n taken.set(rel, existsSync(path) ? readFileSync(path, \"utf-8\") : null);\n }\n return taken;\n}\n/**\n * Put the tree back to a snapshot: unstage every path, then rewrite each\n * to its prior content or remove it. A path that was a staged rename's new\n * name is removed and its old name rewritten, because both were in the\n * snapshot. Lifted out of `papers/publish.ts` (writing-skill cleanup) on the\n * rule that a git primitive kept in a domain folder gets copied by the next.\n */\nexport async function restorePaths(root, taken) {\n try {\n await git(root, \"reset\", \"-q\", \"--\", ...taken.keys());\n }\n catch {\n // Nothing was staged, or a path was never tracked. The rewrite below is what matters.\n }\n for (const [rel, previous] of taken) {\n const path = join(root, rel);\n if (previous === null) {\n rmSync(path, { force: true });\n }\n else {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, previous);\n }\n }\n}\n/**\n * Every working tree of the repository `root` belongs to, main first — the\n * same answer from the trunk and from any linked worktree, because git keeps\n * the list in the shared git directory. Throws when `root` is not in a\n * repository; callers decide what that means.\n */\nexport async function listWorktrees(root) {\n return parseWorktreeList(await git(root, \"worktree\", \"list\", \"--porcelain\"));\n}\n/**\n * The one parse of `git worktree list --porcelain` output: blank-line-separated\n * blocks, each opening with `worktree <path>`; the first block is the main\n * working tree. Pure, so a caller with its own runner (`decision.ts` injects\n * one for tests) parses the same way `listWorktrees` does.\n */\n/** The value of the first `<prefix><value>` line in a porcelain block, trimmed. */\nfunction field(lines, prefix) {\n return lines\n .find((l) => l.startsWith(prefix))\n ?.slice(prefix.length)\n .trim();\n}\nexport function parseWorktreeList(porcelain) {\n const entries = [];\n for (const block of porcelain.split(/\\n\\s*\\n/)) {\n const lines = block.split(\"\\n\");\n const path = field(lines, \"worktree \");\n if (!path)\n continue;\n const ref = field(lines, \"branch \");\n entries.push({\n path,\n branch: ref ? ref.replace(/^refs\\/heads\\//, \"\") : null,\n main: entries.length === 0,\n prunable: lines.some((l) => l === \"prunable\" || l.startsWith(\"prunable \")),\n });\n }\n return entries;\n}\n","import { closeSync, openSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nconst DEFAULTS = {\n timeoutMs: 5000,\n pollIntervalMs: 25,\n staleAfterMs: 30_000,\n};\nfunction isEexistError(err) {\n return Boolean(err && typeof err === \"object\" && \"code\" in err && err.code === \"EEXIST\");\n}\nfunction tryAcquire(lockPath) {\n try {\n // O_EXCL — atomic create, fails if file exists.\n const fd = openSync(lockPath, \"wx\");\n writeFileSync(fd, `${process.pid}\\n${Date.now()}\\n`);\n closeSync(fd);\n return true;\n }\n catch (err) {\n if (isEexistError(err))\n return false;\n throw err;\n }\n}\nfunction isStale(lockPath, staleAfterMs) {\n try {\n const st = statSync(lockPath);\n return Date.now() - st.mtimeMs > staleAfterMs;\n }\n catch {\n // Lockfile vanished between checks — treat as not-stale (the holder released it).\n return false;\n }\n}\nfunction sleepSync(ms) {\n const end = Date.now() + ms;\n // Synchronous spin — acceptable since lock waits are <100ms in practice and\n // the agent CLI is single-purpose. Async lock would require restructuring\n // every call site.\n const buf = new SharedArrayBuffer(4);\n const view = new Int32Array(buf);\n Atomics.wait(view, 0, 0, Math.max(1, end - Date.now()));\n}\n/**\n * Acquire an exclusive lock on `lockPath`. Blocks (synchronously) until the\n * lock is held or `timeoutMs` elapses. Returns a release function the caller\n * must invoke in a `finally` block.\n *\n * Throws on lockfile contention exceeding `timeoutMs`.\n */\nexport function acquireLock(lockPath, opts = {}) {\n const { timeoutMs, pollIntervalMs, staleAfterMs } = { ...DEFAULTS, ...opts };\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (tryAcquire(lockPath)) {\n return () => {\n try {\n rmSync(lockPath, { force: true });\n }\n catch {\n // Best-effort release. If another process took our lock (because we\n // went stale), nothing to do.\n }\n };\n }\n if (isStale(lockPath, staleAfterMs)) {\n // Take it over.\n try {\n rmSync(lockPath, { force: true });\n }\n catch {\n // Race — someone else removed it first; fine.\n }\n continue;\n }\n sleepSync(pollIntervalMs);\n }\n throw new Error(`Timed out acquiring lock on ${lockPath} after ${timeoutMs}ms`);\n}\n/**\n * Convenience: run `fn` while holding the lock; release on return or throw.\n */\nexport function withLock(lockPath, fn, opts) {\n const release = acquireLock(lockPath, opts);\n try {\n return fn();\n }\n finally {\n release();\n }\n}\n","import { existsSync, readFileSync, realpathSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { withLock } from \"../agents/lock.js\";\nimport { isUsableSegment } from \"../path-segment.js\";\nimport { gitCommonDirOf } from \"./layout.js\";\n/**\n * The plan-worktree assignment record: where it lives, what it holds, and the\n * only code that reads or writes it.\n *\n * `indusk-plan-worktrees.json` in the repository's shared git directory —\n * common to the trunk and every worktree of this clone, per-machine, never in\n * a working tree. Writers take `<record>.lock` from the read to the write, so\n * two sessions assigning at once both land. Readers parse it structurally and\n * report a malformed file rather than guessing around it. The resolver\n * (`plan-worktrees.ts`) and the commands (`plan-worktree-commands.ts`) go\n * through here; nothing else touches the file.\n */\n/**\n * Which copy of a plan is the live one.\n *\n * Every checkout of a project holds a copy of every plan — the trunk's and\n * one per worktree — and under worktree-per-plan the one being worked is in\n * its worktree, not on the trunk. This module answers \"where is plan X's live\n * copy\" for every reader (the MCP plan tools, the admin), from any checkout.\n *\n * The answer comes from a record, never from matching names: the InDusk\n * worktree command writes an assignment when it creates or assigns a\n * worktree, and ends it at release. The record lives in the repository's\n * shared git directory — common to the trunk and every worktree of this\n * clone, per-machine like the worktrees it names, never part of any working\n * tree. Each read checks the record against git's own worktree list, and\n * every mismatch is reported as what it is: a worktree that is gone, a plan\n * with two live worktrees, a record that cannot be read. None is guessed.\n *\n * Inert in a workbench: plan documents there live at the workbench root and\n * never in a code worktree, so every plan reads from the plan root.\n */\nexport const RECORD_FILE = \"indusk-plan-worktrees.json\";\nconst RECORD_VERSION = 1;\n/** A refusal from `assignPlan`, `releasePlan` or `createPlanWorktree`: the message names what was refused. */\nexport class PlanWorktreeRefusal extends Error {\n name = \"PlanWorktreeRefusal\";\n}\n/** Why one assignment is unusable, or null. The reader and the writer share it. */\nfunction assignmentProblem(value, index) {\n if (typeof value !== \"object\" || value === null)\n return `assignment ${index} is not an object`;\n const a = value;\n for (const key of [\"plan\", \"path\", \"branch\", \"at\"]) {\n if (typeof a[key] !== \"string\" || a[key].trim() === \"\") {\n return `assignment ${index} has no ${key}`;\n }\n }\n if (!isUsableSegment(a.plan))\n return `assignment ${index} names an unusable plan \"${a.plan}\"`;\n if (!a.path.startsWith(\"/\"))\n return `assignment ${index} has a relative path \"${a.path}\"`;\n return null;\n}\nfunction parseRecord(text, file) {\n let data;\n try {\n data = JSON.parse(text);\n }\n catch (err) {\n return { ok: false, file, problem: `not valid JSON (${err.message})` };\n }\n if (typeof data !== \"object\" || data === null)\n return { ok: false, file, problem: \"not an object\" };\n const body = data;\n if (body.version !== RECORD_VERSION) {\n return {\n ok: false,\n file,\n problem: `version is ${JSON.stringify(body.version)}, expected ${RECORD_VERSION}`,\n };\n }\n if (!Array.isArray(body.assignments))\n return { ok: false, file, problem: \"assignments is not a list\" };\n for (let i = 0; i < body.assignments.length; i++) {\n const problem = assignmentProblem(body.assignments[i], i);\n if (problem)\n return { ok: false, file, problem };\n }\n return { ok: true, file, assignments: body.assignments };\n}\nfunction recordPath(anyCheckout) {\n const common = gitCommonDirOf(anyCheckout);\n if (!common)\n throw new PlanWorktreeRefusal(`${anyCheckout} is not inside a git repository`);\n // The common dir exists whenever git reports it; realpath settles /var ↔ /private/var.\n return join(realpathSync(common), RECORD_FILE);\n}\nfunction readRecordFile(file) {\n if (!existsSync(file))\n return { ok: true, file, assignments: [] };\n let text;\n try {\n text = readFileSync(file, \"utf-8\");\n }\n catch (err) {\n return { ok: false, file, problem: `unreadable (${err.message})` };\n }\n return parseRecord(text, file);\n}\nexport async function readRecord(anyCheckout) {\n return readRecordFile(recordPath(anyCheckout));\n}\n/**\n * Read the record, decide, and write it back, holding `<record>.lock` across\n * all three. Two sessions assigning at once would otherwise each write the\n * list they read and the later write would drop the earlier assignment — that\n * plan silently reading the trunk again. The lock is the project's lock for\n * processes on one machine (`lib/agents/lock.ts`); `change` returns the new\n * list, or null to write nothing.\n */\nexport function updateRecord(anyCheckout, change) {\n const file = recordPath(anyCheckout);\n return withLock(`${file}.lock`, () => {\n const record = readRecordFile(file);\n if (!record.ok)\n refuseUnreadable(record);\n const { next, result } = change(record.assignments);\n if (next)\n writeRecord(file, next);\n return result;\n });\n}\n/** Write the record atomically: a reader never sees half a file. */\nfunction writeRecord(file, assignments) {\n const temp = `${file}.${process.pid}.tmp`;\n writeFileSync(temp, `${JSON.stringify({ version: RECORD_VERSION, assignments }, null, 2)}\\n`);\n renameSync(temp, file);\n}\n/** The one refusal for a record that cannot be read — every writer and pre-check uses it. */\nexport function refuseUnreadable(record) {\n throw new PlanWorktreeRefusal(`the assignment record ${record.file} cannot be read: ${record.problem} — fix or remove it first`);\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { checkRetrospectiveReadiness } from \"@infinitedusky/indusk-mcp/cleanup/gate\";\nimport {\n isFalsificationComplete,\n type LogEntry,\n readFalsificationLog,\n} from \"@infinitedusky/indusk-mcp/falsification/log\";\nimport { parseImplString } from \"@infinitedusky/indusk-mcp/impl-parser\";\nimport {\n derivePlanPosition,\n type PlanPositionState,\n} from \"@infinitedusky/indusk-mcp/lifecycle\";\nimport {\n type PaperSummary,\n type PlanDeclarations,\n parsePlan,\n readPlanDeclarations,\n} from \"@infinitedusky/indusk-mcp/planning/plan-parser\";\nimport { afterClose } from \"@infinitedusky/indusk-mcp/promises/after-close\";\nimport type { PhaseBoundaryRecord } from \"@infinitedusky/indusk-mcp/shape/boundary\";\nimport {\n parseTrajectory,\n type Trajectory,\n} from \"@infinitedusky/indusk-mcp/trajectory/parser\";\nimport type { WorkflowType } from \"@infinitedusky/indusk-mcp/workflow-types\";\nimport {\n type CopySource,\n copySource,\n type PlanCopy,\n resolvePlanCopies,\n} from \"@infinitedusky/indusk-mcp/worktree/plan-worktrees\";\nimport matter from \"gray-matter\";\nimport { type BoundaryRead, readProjectBoundaries } from \"./project-reader\";\n\n/**\n * The data layer for the admin UI. Reads `.indusk/planning/` and `.indusk/eval/`\n * directly from disk and reuses indusk-mcp's parsers (trajectory + falsification log)\n * — never duplicates parsing logic. If a parser needs a new export to support the\n * admin UI, add it to the original module rather than recreating it here.\n *\n * Server-side only (filesystem access). Designed to be called from Next.js server\n * components, not from client components.\n */\n\nexport interface DocumentFrontmatter {\n title?: string;\n status?: string;\n workflow?: string;\n date?: string;\n [key: string]: unknown;\n}\n\nexport interface ResearchData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface BriefData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface TestPlanData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface ADRData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\nexport interface ImplData {\n frontmatter: DocumentFrontmatter;\n content: string;\n /** Parsed trajectory; undefined when the impl has no `## Test Trajectory` section. */\n trajectory?: Trajectory;\n}\n\nexport interface FalsificationData {\n entries: LogEntry[];\n complete: boolean;\n}\n\nexport interface RetroData {\n frontmatter: DocumentFrontmatter;\n content: string;\n}\n\n/**\n * A paper (a document declaring `kind: paper`) as the UI renders it: the\n * shared parser's summary — status, and staleness derived from the content\n * hash — plus the markdown body. Never re-parsed here; `parsePlan` owns it.\n */\nexport interface PaperEntry extends PaperSummary {\n content: string;\n}\n\nexport interface Plan {\n /** Folder name (kebab-case) under `.indusk/planning/` or `.indusk/planning/archive/`. */\n name: string;\n /** Effective status — pulled from impl.md, falling back to brief.md, or \"unknown\". */\n status: string;\n /** Whether the plan lives under archive/ vs the active planning dir. */\n archived: boolean;\n research?: ResearchData;\n brief?: BriefData;\n testPlan?: TestPlanData;\n adr?: ADRData;\n impl?: ImplData;\n falsification?: FalsificationData;\n retrospective?: RetroData;\n /** Every `kind: paper` document, in filename order, with its body. Absent when there are none. */\n papers?: PaperEntry[];\n /**\n * The plan's declared type, from the shared parser — `workflow:` in its\n * brief, or its research document when it has no brief. Null when nothing\n * is declared, or when the declared word is not a type (admin-plan-type).\n */\n workflow?: WorkflowType | null;\n /** The word that was declared, when it is not one of the types. */\n workflowDeclared?: string;\n /**\n * Where the plan stands in the lifecycle, derived from its documents, its\n * impl and the retrospective readiness gate (admin-ui-phase-progress).\n */\n position?: PlanPositionState;\n /** This plan's phase-boundary records, when the project's record file read cleanly. */\n boundaries?: PhaseBoundaryRecord[];\n /**\n * Set when `.indusk/phase-boundary.jsonl` has a malformed line. The reader\n * throws rather than skipping it, and the page shows the error rather than\n * guessing an active phase from a partial record.\n */\n boundaryError?: string;\n /** The worktree this plan was read from, when it is assigned to one (admin-plan-worktrees). */\n worktree?: CopySource[\"worktree\"];\n /**\n * Why the trunk copy is shown although the plan is assigned: its worktree\n * is gone, or two live worktrees claim it. `detail` names every path.\n */\n copyProblem?: CopySource[\"copyProblem\"];\n /**\n * The plan folder has moved into `archive/` in its worktree: archived on the\n * branch by the retrospective, awaiting the landing's release.\n */\n archivedInWorktree?: CopySource[\"archivedInWorktree\"];\n /**\n * Set when the assignment record cannot be read. The plan then carries its\n * name and nothing read from any copy: which copy is live is unknown, and\n * the page says so rather than drawing one.\n */\n copyError?: { file: string; problem: string };\n /** True when ANY document in the plan failed to parse (malformed YAML frontmatter). */\n malformed?: boolean;\n /**\n * For each document that failed to parse, the raw file contents (so the UI\n * can show a \"Raw\" view per T13). Only populated when `malformed: true`.\n * Keys match the document filenames (e.g., `brief.md`, `impl.md`).\n */\n rawDocuments?: Record<string, string>;\n}\n\nexport interface Scorecard {\n timestamp: string;\n changeId?: string;\n mode?: string;\n summary?: string;\n questions?: unknown;\n graphitiWrites?: number;\n /**\n * Registered project name — injected by the cross-project scorecards\n * walker (`app/scorecards/page.tsx`) after reading each project's\n * results.log. Not present in the underlying log file.\n */\n project?: string;\n [key: string]: unknown;\n}\n\nconst PLANNING_DIR = \".indusk/planning\";\nconst ARCHIVE_DIR = \"archive\";\nconst EVAL_RESULTS = \".indusk/eval/results.log\";\nconst MASTER_FILE = \"master.md\";\n\nconst DOC_FILES = [\n \"research.md\",\n \"brief.md\",\n \"test-plan.md\",\n \"adr.md\",\n \"impl.md\",\n \"falsification.md\",\n \"retrospective.md\",\n] as const;\n\n/**\n * Read a single document file and return parsed frontmatter + content. Returns\n * `null` if the file doesn't exist. Returns `{ malformed: true }` if the\n * frontmatter cannot be parsed.\n *\n * Detection strategy: gray-matter is unreliable about throwing on malformed\n * YAML — depending on the runtime / module-resolution path, js-yaml errors\n * may be swallowed and `data: {}` returned instead. We detect malformed by\n * the structural signal: a file that starts with `---\\n` and has a closing\n * `---` MUST yield a non-empty frontmatter block. If gray-matter returns a\n * truly empty `data` for such a file (and the block isn't actually empty\n * after trimming comments/whitespace), the parse silently failed.\n *\n * False-positive note: a legitimately empty frontmatter (`---\\n---\\nbody`)\n * also produces `data: {}` but with an empty block — we differentiate by\n * checking whether the block is non-trivial.\n */\nconst FRONTMATTER_RE = /^---\\n([\\s\\S]*?)\\n---/;\n\nasync function readDoc(\n planDir: string,\n filename: string,\n): Promise<\n | { frontmatter: DocumentFrontmatter; content: string }\n | { malformed: true; raw: string }\n | null\n> {\n const path = join(planDir, filename);\n if (!existsSync(path)) return null;\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch {\n return { malformed: true, raw: \"\" };\n }\n try {\n const parsed = matter(raw);\n const data = parsed.data as DocumentFrontmatter;\n const dataIsEmpty = Object.keys(data).length === 0;\n if (dataIsEmpty) {\n const fmMatch = raw.match(FRONTMATTER_RE);\n if (fmMatch) {\n const block = fmMatch[1].replace(/^\\s*#[^\\n]*$/gm, \"\").trim();\n if (block !== \"\") return { malformed: true, raw };\n }\n }\n return {\n frontmatter: data,\n content: parsed.content,\n };\n } catch {\n return { malformed: true, raw };\n }\n}\n\nfunction isMalformed(\n doc:\n | { malformed: true; raw: string }\n | { frontmatter: DocumentFrontmatter; content: string }\n | null,\n): doc is { malformed: true; raw: string } {\n return doc !== null && \"malformed\" in doc;\n}\n\nasync function readPlanFolder(\n planDir: string,\n name: string,\n archived: boolean,\n boundaries: BoundaryRead = { records: [] },\n /** The project root, for an archived plan's after-close facts (day-monitor). */\n projectRoot?: string,\n): Promise<Plan> {\n const docs = await Promise.all(DOC_FILES.map((f) => readDoc(planDir, f)));\n const [research, brief, testPlan, adr, impl, _falsification, retrospective] =\n docs;\n const malformed = docs.some((d) => isMalformed(d));\n const rawDocuments: Record<string, string> = {};\n for (let i = 0; i < docs.length; i++) {\n const doc = docs[i];\n if (doc !== null && isMalformed(doc)) {\n rawDocuments[DOC_FILES[i]] = doc.raw;\n }\n }\n\n let implData: ImplData | undefined;\n if (impl !== null && !isMalformed(impl)) {\n const trajectory = /^##\\s+Test Trajectory\\b/m.test(impl.content)\n ? parseTrajectory(impl.content)\n : undefined;\n implData = { ...impl, trajectory };\n }\n\n let falsificationData: FalsificationData | undefined;\n if (_falsification !== null) {\n try {\n const entries = readFalsificationLog(planDir);\n const complete = isFalsificationComplete(planDir);\n falsificationData = { entries, complete };\n } catch {\n // Library tolerates missing logs (returns []) but file-system errors\n // could throw. Treat as no-falsification rather than malformed-plan\n // since the file existing-but-unreadable is itself rare.\n }\n }\n\n // Papers come from the shared parser (status vocabulary, derived staleness)\n // with the body read once here for rendering.\n const parsed = parsePlan(planDir);\n const papers = parsed.papers\n ? await readPapers(planDir, parsed.papers)\n : undefined;\n\n // The plan bar's position: the same facts `list_plans` and the retrospective\n // gate read, composed by the lifecycle module rather than here.\n const readiness =\n impl !== null && !isMalformed(impl)\n ? checkRetrospectiveReadiness(\n planDir,\n readFileSync(join(planDir, \"impl.md\"), \"utf-8\"),\n )\n : null;\n // A closed plan may be back in motion — reopened by an incident, or in\n // `monitor` — derived from files by the same function `list_plans` reads.\n const after =\n archived && projectRoot\n ? afterClose(projectRoot, name, planDir)\n : undefined;\n const position = derivePlanPosition({\n summary: parsed,\n impl: implData ? parseImplString(implData.content) : null,\n readiness,\n archived,\n ...(after ? { afterClose: after } : {}),\n });\n const boundaryFields =\n \"error\" in boundaries\n ? { boundaryError: boundaries.error }\n : { boundaries: boundaries.records.filter((r) => r.plan === name) };\n\n const status =\n (implData?.frontmatter.status as string | undefined) ??\n (brief !== null && !isMalformed(brief)\n ? (brief.frontmatter.status as string | undefined)\n : undefined) ??\n // A papers-only plan reports the parser's paper-stage status rather than unknown.\n (parsed.stage === \"paper\" ? parsed.stageStatus : undefined) ??\n \"unknown\";\n\n return {\n name,\n status,\n position,\n workflow: parsed.workflow ?? null,\n ...(parsed.workflowDeclared\n ? { workflowDeclared: parsed.workflowDeclared }\n : {}),\n ...boundaryFields,\n ...(papers ? { papers } : {}),\n archived,\n research:\n research !== null && !isMalformed(research) ? research : undefined,\n brief: brief !== null && !isMalformed(brief) ? brief : undefined,\n testPlan:\n testPlan !== null && !isMalformed(testPlan) ? testPlan : undefined,\n adr: adr !== null && !isMalformed(adr) ? adr : undefined,\n impl: implData,\n falsification: falsificationData,\n retrospective:\n retrospective !== null && !isMalformed(retrospective)\n ? retrospective\n : undefined,\n ...(malformed ? { malformed: true, rawDocuments } : {}),\n };\n}\n\n/** Each paper's summary from the shared parser, plus its markdown body for rendering. */\nasync function readPapers(\n planDir: string,\n summaries: PaperSummary[],\n): Promise<PaperEntry[]> {\n return Promise.all(\n summaries.map(async (summary) => ({\n ...summary,\n content: matter(await readFile(join(planDir, summary.file), \"utf-8\"))\n .content,\n })),\n );\n}\n\nasync function listPlanFolders(dir: string): Promise<string[]> {\n if (!existsSync(dir)) return [];\n const entries = await readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory() && e.name !== ARCHIVE_DIR)\n .map((e) => e.name)\n .sort();\n}\n\n/**\n * Read every plan in `.indusk/planning/` (skipping `archive/`). Each plan\n * gets a `Plan` object with whatever documents are present; missing optional\n * documents result in `undefined` fields rather than errors.\n */\nexport async function readActivePlans(projectRoot: string): Promise<Plan[]> {\n const resolved = await resolvePlanCopies(projectRoot);\n const root = resolved.projectRoot;\n const folders = await listPlanFolders(join(root, PLANNING_DIR));\n if (!resolved.ok) {\n const copyError = { file: resolved.file, problem: resolved.problem };\n return folders.map((name) => ({\n name,\n status: \"unknown\",\n archived: false,\n copyError,\n }));\n }\n // Boundary records are per checkout: a plan in a worktree opened its\n // phases there. Read each live root's file once.\n const boundariesByRoot = new Map<string, Promise<BoundaryRead>>();\n const boundariesOf = (at: string) => {\n let read = boundariesByRoot.get(at);\n if (!read) {\n read = readProjectBoundaries(at);\n boundariesByRoot.set(at, read);\n }\n return read;\n };\n return Promise.all(\n folders.map(async (name) => {\n const copy: PlanCopy = resolved.copies.get(name) ?? {\n plan: name,\n root,\n dir: join(root, PLANNING_DIR, name),\n source: \"trunk\",\n };\n const plan = await readPlanFolder(\n copy.dir,\n name,\n false,\n await boundariesOf(copy.root),\n );\n return { ...plan, ...copySource(copy) };\n }),\n );\n}\n\n/**\n * The project's worktrees as the sidebar shows them: those holding no plan\n * assignment, and the record's error when it cannot be read.\n */\nexport async function readProjectWorktrees(projectRoot: string): Promise<\n | {\n ok: true;\n unassigned: { name: string; path: string; branch: string | null }[];\n }\n | { ok: false; file: string; problem: string }\n> {\n const resolved = await resolvePlanCopies(projectRoot);\n if (!resolved.ok) {\n return { ok: false, file: resolved.file, problem: resolved.problem };\n }\n return { ok: true, unassigned: resolved.unassigned };\n}\n\n/**\n * Read every plan in `.indusk/planning/archive/`. Returns the same `Plan` shape\n * as `readActivePlans` but with `archived: true`.\n */\nexport async function readArchivedPlans(projectRoot: string): Promise<Plan[]> {\n const archiveDir = join(projectRoot, PLANNING_DIR, ARCHIVE_DIR);\n if (!existsSync(archiveDir)) return [];\n const entries = await readdir(archiveDir, { withFileTypes: true });\n const folders = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n const boundaries = await readProjectBoundaries(projectRoot);\n return Promise.all(\n folders.map((name) =>\n readPlanFolder(\n join(archiveDir, name),\n name,\n true,\n boundaries,\n projectRoot,\n ),\n ),\n );\n}\n\n/**\n * Parse `master.md` and return plan names in the order they appear in the\n * pipeline tables. Plan names come from markdown links like\n * `[plan-name](plan-name/brief.md)`. Plans listed by string only (no link)\n * are skipped — they don't have a folder yet.\n */\n/**\n * Plan hierarchy declarations for this project — parents, top-level order, and\n * each parent's ordered children.\n *\n * Reads frontmatter through the shared parser rather than scraping prose. The\n * previous implementation matched markdown links shaped `[name](name/doc.md)`\n * with a regex, which silently matched *nothing* against a master whose links\n * carry a `../` prefix — structure derived from prose fails quietly, which is\n * the worst way for it to fail.\n */\nexport function readPlanHierarchy(projectRoot: string): PlanDeclarations {\n return readPlanDeclarations(join(projectRoot, PLANNING_DIR));\n}\n\n/**\n * Top-level display order. Plans absent from the declaration keep today's\n * behaviour — they fall through to the \"Unordered\" group rather than vanishing.\n */\nexport function readMasterPlanOrder(projectRoot: string): string[] {\n return readPlanHierarchy(projectRoot).roadmap;\n}\n\n/**\n * The prose body of a plan's own `master.md` — a parent plan's sequence and\n * its reasoning. Returns null when the file is absent or malformed; the\n * detail view simply omits the prose. Subplan cards never depend on it —\n * declarations come from `readPlanHierarchy`, not from here.\n */\nexport async function readPlanMasterContent(\n projectRoot: string,\n planName: string,\n): Promise<string | null> {\n const doc = await readDoc(\n join(projectRoot, PLANNING_DIR, planName),\n MASTER_FILE,\n );\n if (doc === null || isMalformed(doc)) return null;\n return doc.content;\n}\n\n/**\n * Read `.indusk/eval/results.log` (jsonl, one scorecard per line) and return\n * scorecards filtered to the supplied date range. Malformed lines are skipped\n * silently — `results.log` is append-only and may contain partial entries from\n * crashed evaluator processes.\n */\nexport async function readEvalScorecards(\n projectRoot: string,\n planDateRange: { from: Date; to: Date },\n): Promise<Scorecard[]> {\n const path = join(projectRoot, EVAL_RESULTS);\n if (!existsSync(path)) return [];\n const raw = await readFile(path, \"utf-8\");\n const out: Scorecard[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const parsed = JSON.parse(trimmed) as Scorecard;\n if (typeof parsed.timestamp !== \"string\") continue;\n const ts = new Date(parsed.timestamp);\n if (Number.isNaN(ts.getTime())) continue;\n if (ts >= planDateRange.from && ts <= planDateRange.to) out.push(parsed);\n } catch {\n // Skip malformed lines — see fn doc.\n }\n }\n out.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n );\n return out;\n}\n\n// The research-directory reader (.indusk/research/) lives in\n// research-reader.ts — extracted in the dawn-ui-plan-grouping cleanup; it\n// shares nothing with plan parsing.\n","import { existsSync, readdirSync, realpathSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { getPlanningDir } from \"../config.js\";\nimport { git, listWorktrees } from \"../git.js\";\nimport { readRecord } from \"./plan-worktree-record.js\";\nimport { isWorkbench } from \"./repos.js\";\nexport { RECORD_FILE } from \"./plan-worktree-record.js\";\n/** The report fields for a copy; empty for a plain trunk copy or none. */\nexport function copySource(copy) {\n if (!copy)\n return {};\n if (copy.source === \"worktree\") {\n return copy.archivedInWorktree\n ? { worktree: copy.worktree, archivedInWorktree: true }\n : { worktree: copy.worktree };\n }\n if (\"problem\" in copy)\n return { copyProblem: { kind: copy.problem, detail: copy.detail } };\n return {};\n}\n/** Normalize a path for comparison: realpath when it exists (macOS `/var` ↔ `/private/var`), else resolved. */\n/** @internal — shared with the commands module. */\nexport function canonical(path) {\n try {\n return realpathSync(path);\n }\n catch {\n return resolve(path);\n }\n}\n/** Plan folders on the trunk, archive excluded — the inventory every copy is resolved for. */\n/** The trunk copy of `plan`: its folder in the project's planning dir (the same one `parseAllPlans` reads). */\nfunction trunkCopy(projectRoot, plan) {\n return { plan, root: projectRoot, dir: join(getPlanningDir(projectRoot), plan), source: \"trunk\" };\n}\nfunction trunkPlans(projectRoot) {\n const dir = getPlanningDir(projectRoot);\n if (!existsSync(dir))\n return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name !== \"archive\")\n .map((e) => e.name)\n .sort();\n}\n/**\n * The repository whose checkout `anyCheckout` is the top of, or null.\n *\n * Null when it is not in a repository, and also when it is a folder *inside*\n * one — an InDusk project nested in a larger repository. Assignments bind a\n * plan to a whole checkout, and the project's copy in a worktree would be a\n * subfolder of it; resolving that is not done here, so a nested project keeps\n * reading the folder it was asked about. Climbing to the enclosing repository\n * instead would list that repository's plans as this project's.\n */\nexport async function repositoryOf(anyCheckout) {\n let list;\n try {\n const top = await git(anyCheckout, \"rev-parse\", \"--show-toplevel\");\n if (canonical(top) !== canonical(anyCheckout))\n return null;\n list = await listWorktrees(anyCheckout);\n }\n catch {\n return null;\n }\n const main = list.find((w) => w.main);\n if (!main)\n return null;\n const linked = new Map();\n for (const w of list) {\n if (w.main || w.prunable || !existsSync(w.path))\n continue;\n const path = canonical(w.path);\n linked.set(path, { path, branch: w.branch });\n }\n return { projectRoot: canonical(main.path), linked };\n}\nfunction trunkCopies(projectRoot) {\n return new Map(trunkPlans(projectRoot).map((plan) => [plan, trunkCopy(projectRoot, plan)]));\n}\n/**\n * One plan's copy from its assignments, and the worktree paths those\n * assignments hold live. No assignment reads the trunk; one live worktree is\n * the copy; several live, or none left of those recorded, read the trunk and\n * say which.\n */\nfunction copyFor(plan, assignments, repo) {\n const trunk = trunkCopy(repo.projectRoot, plan);\n const mine = assignments.filter((a) => a.plan === plan);\n if (mine.length === 0)\n return { copy: trunk, live: [] };\n const live = mine.map((a) => canonical(a.path)).filter((path) => repo.linked.has(path));\n if (live.length === 1) {\n const [path] = live;\n const branch = repo.linked.get(path)?.branch ?? mine[0].branch;\n return { copy: worktreeCopy(plan, path, branch, trunk), live };\n }\n if (live.length > 1) {\n return {\n copy: {\n ...trunk,\n problem: \"doubled\",\n detail: `two live worktrees are assigned to ${plan}: ${live.join(\", \")}`,\n },\n live,\n };\n }\n return {\n copy: {\n ...trunk,\n problem: \"gone\",\n detail: `assigned worktree ${mine.map((a) => a.path).join(\", \")} no longer exists`,\n },\n live,\n };\n}\n/**\n * The copy in a live assigned worktree, found on disk: the active plan folder,\n * else the archived one (the retrospective moves it on the branch before the\n * landing releases), else the trunk's copy with the folder reported missing.\n */\nfunction worktreeCopy(plan, path, branch, trunk) {\n const worktree = { name: basename(path), path, branch };\n const planning = getPlanningDir(path);\n const active = join(planning, plan);\n if (existsSync(active))\n return { plan, root: path, dir: active, source: \"worktree\", worktree };\n const archived = join(planning, \"archive\", plan);\n if (existsSync(archived)) {\n return {\n plan,\n root: path,\n dir: archived,\n source: \"worktree\",\n worktree,\n archivedInWorktree: true,\n };\n }\n return {\n ...trunk,\n problem: \"missing\",\n detail: `plan folder missing in worktree ${path} (neither ${active} nor ${archived} exists)`,\n };\n}\n/**\n * Every plan's live copy, asked from any checkout of the project.\n *\n * A plan with no assignment reads from the trunk. One live assignment reads\n * from its worktree. Two live assignments, or assignments whose worktrees\n * are all gone, read from the trunk and say why. A record that cannot be\n * read is `ok: false` — there is no copy map to misuse.\n */\nexport async function resolvePlanCopies(anyCheckout) {\n const asked = canonical(anyCheckout);\n if (isWorkbench(asked)) {\n return { ok: true, projectRoot: asked, copies: trunkCopies(asked), unassigned: [] };\n }\n const repo = await repositoryOf(asked);\n if (!repo)\n return { ok: true, projectRoot: asked, copies: trunkCopies(asked), unassigned: [] };\n const record = await readRecord(asked);\n if (!record.ok)\n return { ok: false, projectRoot: repo.projectRoot, file: record.file, problem: record.problem };\n const copies = new Map();\n const claimed = new Set();\n for (const plan of trunkPlans(repo.projectRoot)) {\n const { copy, live } = copyFor(plan, record.assignments, repo);\n copies.set(plan, copy);\n for (const path of live)\n claimed.add(path);\n }\n const unassigned = [...repo.linked.values()]\n .filter((w) => !claimed.has(w.path))\n .map((w) => ({ name: basename(w.path), path: w.path, branch: w.branch }));\n return { ok: true, projectRoot: repo.projectRoot, copies, unassigned };\n}\n/** One plan's live copy. A plan with no trunk folder resolves to the trunk, where every reader already reports it missing. */\nexport async function livePlanCopy(anyCheckout, plan) {\n const all = await resolvePlanCopies(anyCheckout);\n if (!all.ok)\n return { ok: false, file: all.file, problem: all.problem };\n return {\n ok: true,\n copy: all.copies.get(plan) ?? trunkCopy(all.projectRoot, plan),\n };\n}\n"],"names":["readRawConfig","root","configPath","JSON","parse","readWorkbenchRepos","worktree","declared","Array","isArray","repos","wrapped_repo","name","seen","Set","entry","remote","has","add","path","worktrees","repo","trim","push","isWorkbench","shape","length","readSiblingParent","value","sibling_parent","readReposRoot","w","repos_root","undefined","resolveReposRoot","projectRoot","startsWith","NOT_A_WORKBENCH","repoDir","declaredRepoDirs","reposRoot","map","r","dir","variantClasses","passing","blocked","skipped","planned","writable","written","unknown","neutral","Badge","variant","className","children","rest","classes","filter","Boolean","join","BUILD_PHASE","String","raw","PHASE_HEADING","RegExp","buildPhaseHeadingFor","phase","ref","toPhaseRef","kind","number","gateHeadingFor","gate","address","phaseLabel","TEST_PHASE_HEADING","ANY_PHASE_HEADING","gateHeading","FORWARD_INTELLIGENCE_HEADING","parsePhaseHeading","line","test","exec","Number","parseInt","build","PHASE_REFERENCE","parsePhaseRef","cell","match","toLowerCase","FENCE","parseFence","m","char","info","findFenceClose","lines","start","open","i","candidate","fencedLineMask","mask","fill","close","j","unterminatedFenceLine","body","split","phaseSequence","fenced","out","heading","phaseExists","sequence","some","p","phaseOrdinal","exact","findIndex","lastBefore","WORKFLOW_TYPES","WORKFLOW_DEFINITIONS","feature","purpose","requires","skips","why","bugfix","refactor","spike","DOCUMENT_LABELS","research","brief","adr","impl","retrospective","readWorkflow","type","stringify","includes","absentDocumentNote","document","state","label","TELEMETRY_KEYWORDS","REVIEW_KEYWORDS","PLAN_REF","CANARY_KEYWORDS","FEEDBACK_KEYWORDS","classifyMitigation","row","text","mitigation","hints","hasAny","words","planRefs","matchAll","warning","auditDeferredMitigations","trajectory","deferred","findBlockedRows","rows","message","id","resolveTestIdCommand","find","backtickMatches","asserts","identifiers","keyword","s","sort","a","b","fileGlob","replace","suggestedCommand","slice","CLOSE_TERMINAL_STATES","findNonTerminalRows","passesAtKind","passesAt","auditPlanAtClose","parseTrajectory","nonTerminal","GATE_SUFFIXES","Verification","OTel","Context","Document","parseChecklistItems","items","checked","parseImplString","data","content","title","status","phases","currentPhase","currentGateType","currentGateLines","inForwardIntelligence","forwardIntelligenceLines","flushGate","gates","lineIndex","phaseMatch","ordinal","blocker","forwardIntelligence","fiMatch","gateMatch","blockerMatch","getPhaseCompletion","uncheckedByGate","implementation","verification","otel","context","totalItems","checkedItems","item","complete","getAllPhaseCompletions","parsed","findPhase","readPublishedRecord","block","published","withProvenance","prov","Error","kept","inPublishedBlock","destination","commit","source_commit","hash","copySource","copy","source","archivedInWorktree","copyProblem","problem","detail","canonical","trunkCopy","plan","trunkPlans","withFileTypes","e","isDirectory","repositoryOf","anyCheckout","list","top","main","linked","Map","prunable","set","branch","trunkCopies","copyFor","assignments","trunk","mine","live","get","worktreeCopy","planning","active","archived","resolvePlanCopies","asked","ok","copies","unassigned","record","file","claimed","values","livePlanCopy","all"],"mappings":"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"}