@ybotok/loam 0.1.0-beta.2 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/core/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["/** Read-only installation/repository diagnostics for `loam doctor`. */\nimport { constants, existsSync } from \"node:fs\";\nimport { access, readFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { CONFIG_FILENAME, configPath, parseConfig, type LoamConfig } from \"./config.js\";\nimport { LOAM_VERSION } from \"./version.js\";\nimport { agentsPath, docsRepoState, landscapePath, listFeatures, listServices } from \"./repo.js\";\nimport { loadFile } from \"./likec4.js\";\nimport { conflictMarkerLines } from \"./fleet-context.js\";\nimport { AGENT_TOOLS, DELIVERIES, plannedCommandFiles, type Delivery } from \"./agent.js\";\nimport {\n agentsStaleFinding,\n agentsStampLine,\n agentsStampVersion,\n versionTrails,\n} from \"./agents-stamp.js\";\nimport { scanWritePathResidue, type WritePathResidue } from \"./staging.js\";\n\nexport type DoctorSeverity = \"blocker\" | \"warning\";\n\nexport interface DoctorFinding {\n severity: DoctorSeverity;\n code: string;\n message: string;\n /**\n * The next command or edit that clears this finding. Mandatory, not optional:\n * a diagnostic that names a problem without naming its fix is a diagnostic\n * the reader has to research, and `doctor` is what someone runs precisely\n * because they do not yet know what loam wants from them.\n */\n fix: string;\n}\n\nexport interface DoctorReport {\n healthy: boolean;\n runtime: {\n package: \"@ybotok/loam\";\n version: string;\n node: string;\n platform: NodeJS.Platform;\n arch: string;\n };\n config: {\n path: string;\n status: \"missing\" | \"invalid\" | \"valid\";\n error: string | null;\n };\n docs: {\n path: string | null;\n exists: boolean;\n readable: boolean;\n writable: boolean;\n servicesDir: boolean;\n landscape: boolean;\n };\n counts: { services: number; activeFeatures: number };\n currentService: {\n configured: string | null;\n status: \"unbound\" | \"matched\" | \"unknown\";\n };\n /** The agent surface of this repo — see `inspectAgentSurface`. */\n agents: AgentSurface;\n /**\n * What a write that did not finish left in the docs repo, or null when\n * `docsDir` never resolved. Reported as state, like `docs` and `counts`: a\n * lock somebody is legitimately holding right now is a fact, not a complaint.\n */\n writePath: WritePathResidue | null;\n findings: DoctorFinding[];\n}\n\nexport interface AgentSurface {\n /** Tool ids this repo has an agent surface for. Empty is legal (`init --no-commands`). */\n tools: string[];\n /** Whether `tools` came from loam.json's own record or from what is on disk. */\n toolsSource: \"config\" | \"disk\";\n /** How many files the running binary would lay down for `tools`. */\n plannedFiles: number;\n /** Repo-relative paths of those files that are not there. */\n missingFiles: string[];\n /**\n * Repo-relative paths of the ones that ARE there carrying no version stamp,\n * or one older than this binary — see `staleAgentFiles`.\n */\n staleFiles: string[];\n stamp: {\n /** Where the docs repo's AGENTS.md is, or null when `docsDir` did not resolve. */\n path: string | null;\n /** Whether it could be read at all — absent and unstamped are different facts. */\n present: boolean;\n /** The version the stamp names, null when there is none (or an unparseable one). */\n version: string | null;\n /** Whether `agents.stale` fired: the stamp is missing or trails this binary. */\n stale: boolean;\n };\n}\n\ninterface ConfigInspection {\n status: DoctorReport[\"config\"][\"status\"];\n config: LoamConfig | null;\n error: string | null;\n}\n\n/**\n * Read and validate the config the way every other command does — through\n * `parseConfig`, never through a second implementation.\n *\n * doctor used to carry its own validator. It agreed with `loadConfig` on the\n * fields both happened to check and disagreed everywhere else: doctor accepted\n * a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright, so\n * `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about the same file, and the one the user reads\n * is never the one the commands obey.\n */\nasync function inspectConfig(cwd: string): Promise<ConfigInspection> {\n const path = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return { status: \"missing\", config: null, error: null };\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n try {\n return { status: \"valid\", error: null, config: parseConfig(raw, dirname(path)) };\n } catch (error) {\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function canAccess(path: string, mode: number): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the landscape, don't just stat it. `doctor` reported `landscape: yes`\n * for a file full of conflict markers — the check answered \"is there a file\",\n * which is not the question anyone runs doctor to ask.\n */\nasync function inspectLandscape(path: string, findings: DoctorFinding[]): Promise<void> {\n let source: string;\n try {\n source = await readFile(path, \"utf8\");\n } catch (error) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-unreadable\",\n message: `${path} exists but could not be read: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check permissions on ${path}.`,\n });\n return;\n }\n\n // The three-way merge left its markers in the file. Checked BEFORE the\n // parser, because a conflicted landscape does parse sometimes — both sides of\n // a conflict can be syntactically valid LikeC4 — and \"your map contains two\n // halves of two different maps\" is a more useful sentence than any parser\n // error. This is the failure mode of onboarding a fleet: ten people adopt ten\n // services into one landscape.likec4 in the same week.\n //\n // The scan itself is fleet-context's, which is where the same rule already\n // grades every other document loam reads. doctor kept a second copy of it,\n // and a second copy of a rule is a second chance to spell it differently;\n // what doctor owns is what a conflicted landscape COSTS, which is the finding\n // below and not the search.\n const conflicted = conflictMarkerLines(source);\n if (conflicted.length > 0) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-merge-conflict\",\n message:\n `architecture/landscape.likec4 still contains merge conflict markers ` +\n `(line${conflicted.length === 1 ? \"\" : \"s\"} ${conflicted.join(\", \")}).`,\n fix: `Resolve the conflict in ${path} — keep BOTH services' elements and edges, then re-run \\`loam doctor\\`.`,\n });\n return;\n }\n\n const doc = await loadFile(path);\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-invalid\",\n message:\n `architecture/landscape.likec4 does not parse: ${first.message}` +\n (first.line === undefined ? \"\" : ` (line ${first.line})`) +\n (doc.errors.length > 1 ? ` — and ${doc.errors.length - 1} more` : \"\"),\n fix: `Fix ${path}; every fleet-wide check is blind until it parses.`,\n });\n }\n}\n\n/**\n * The tool ids loam.json records `init` as having written files for, or null\n * when it records none we can plan for.\n *\n * `parseConfig` checks `agentTools`' SHAPE and deliberately not its contents —\n * a config written by a newer binary may name a tool this one has never heard\n * of, and refusing to load it would break every command, not just this check.\n * So the registry filter belongs here: `plannedCommandFiles` throws on an id it\n * does not know, and a config from the future must not turn the read-only\n * preflight into a crash. A tool we cannot plan files for is a tool we have\n * nothing to say about.\n */\nfunction recordedTools(config: LoamConfig | null): string[] | null {\n const ids = (config?.agentTools ?? []).filter((t) => t in AGENT_TOOLS);\n return ids.length === 0 ? null : ids;\n}\n\n/**\n * Which tools this repo has agent files for, asked of the layout itself: a tool\n * counts when any file it would be given is already there.\n *\n * The fallback for a loam.json written before `agentTools` existed, and the\n * only answer a hand-written one can have — so its absence must never be the\n * thing that reports a problem. This asks a narrower question than `init`'s\n * own scan, and on purpose: init scans for a tool's dot-directory to decide\n * what a repo would LIKE, while doctor grades what loam has already put there.\n * A repo that runs Claude Code and took `--no-commands --no-skills` has chosen\n * to have no loam files, and is not behind the binary for it.\n */\nfunction detectedTools(repoRoot: string): string[] {\n return Object.keys(AGENT_TOOLS).filter((id) =>\n plannedCommandFiles(repoRoot, [id]).some((f) => existsSync(f.path)));\n}\n\n/**\n * Which deliveries this repo actually HOLDS for one tool — a delivery counts\n * when at least one of its files is on disk.\n *\n * `loam init --no-skills` (or `--no-commands`) records the tool in `agentTools`\n * and writes one delivery, so planning both and subtracting what exists reported\n * every skill file as missing on a brand-new repo — under a message saying it\n * \"was initialized by an older loam\" and a `fix` that re-runs init without the\n * flag that was the whole point. Which deliveries a repo asked for is written\n * down nowhere, and must not be: the files ARE the record (there is no state\n * file loam can disagree with). So the question is asked of them.\n *\n * A tool with NO files at all is the other story — recorded, then deleted — and\n * the caller falls back to the full plan there, which is what the finding's\n * \"or they were deleted\" already says.\n */\nfunction heldDeliveries(repoRoot: string, tool: string): readonly Delivery[] {\n const held = DELIVERIES.filter((d) =>\n plannedCommandFiles(repoRoot, [tool], [d]).some((f) => existsSync(f.path)));\n return held.length > 0 ? held : DELIVERIES;\n}\n\n/**\n * Of the planned files that ARE here, the ones whose stamp is missing or older\n * than this binary.\n *\n * The stamp is the ONLY thing read out of a generated file — never the body.\n * Comparing bodies would yield \"your file differs from the template\", one step\n * from offering to rewrite it, which is the thing loam refuses: a team's edits\n * to a generated file outrank ours. A stamp is a different claim — it says\n * which loam wrote these instructions — and it can be false while every edit\n * around it is legitimate. Same doctrine as AGENTS.md (agents-stamp.ts), same\n * repair: a human looks, and bumps the stamp or deletes the file so a re-run of\n * `loam init` lays down the current one.\n *\n * Absence of a stamp counts, because that is what every file written before\n * stamping existed looks like — precisely the \"initialized by an older loam\"\n * population this check is for.\n */\nasync function staleAgentFiles(\n repoRoot: string,\n planned: Array<{ path: string }>,\n): Promise<string[]> {\n // Read them together rather than one after another. `planned` is bounded by\n // the tool registry — every delivery of every tool loam knows, a couple of\n // hundred small files — so there is nothing here worth a concurrency cap, and\n // `Promise.all` preserves input order, which is the only property the caller\n // asks of the list it gets back.\n const graded = await Promise.all(planned.map(async (file): Promise<string | null> => {\n let text: string;\n try {\n text = await readFile(file.path, \"utf8\");\n } catch {\n // Unreadable is not stale: it is either the missing-file finding above or\n // a permissions problem, and neither is answered by editing a stamp.\n return null;\n }\n const stamp = agentsStampVersion(text);\n if (stamp === null || versionTrails(stamp, LOAM_VERSION)) {\n return relative(repoRoot, file.path).split(/[\\\\/]/).join(\"/\");\n }\n return null;\n }));\n return graded.filter((p) => p !== null);\n}\n\n/**\n * What the running binary would write for this repo's tools that is not here,\n * and what is here under an older loam's name.\n *\n * Presence is `existsSync`, the same probe `init` uses to compute its `skipped`\n * list, so doctor cannot disagree with init about which files init would write.\n */\nasync function inspectAgentSurface(\n repoRoot: string,\n config: LoamConfig | null,\n docsDir: string | null,\n findings: DoctorFinding[],\n): Promise<AgentSurface> {\n const recorded = recordedTools(config);\n const tools = recorded ?? detectedTools(repoRoot);\n const planned = tools.flatMap((id) =>\n plannedCommandFiles(repoRoot, [id], heldDeliveries(repoRoot, id)));\n const present = planned.filter((f) => existsSync(f.path));\n const missingFiles = planned\n .filter((f) => !existsSync(f.path))\n .map((f) => relative(repoRoot, f.path).split(/[\\\\/]/).join(\"/\"));\n const staleFiles = await staleAgentFiles(repoRoot, present);\n\n if (missingFiles.length > 0) {\n // A warning, never a blocker: an out-of-date command set is a repo whose\n // agents have fewer entry points than they could, not one where anything\n // refuses to run. doctor's blockers are for the second kind.\n //\n // The fix spells `--docs` and `--tools` and deliberately NOT `--service`:\n // `init` spreads the committed config forward, and a `--docs`-less re-run\n // keeps the pointer the repo already commits, so the binding survives\n // untouched. Every interpolation here stands for exactly one argument —\n // test/agent-commands-runnable.test.ts parses this string, and a `${flags}`\n // holding three of them is a command it cannot check.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-missing\",\n message:\n `${missingFiles.length} of the ${planned.length} command and skill files loam v${LOAM_VERSION} `\n + `lays down for ${tools.join(\", \")} are not in this repo — it was initialized by an older loam, `\n + `or they were deleted: ${missingFiles.join(\", \")}`,\n fix:\n `Re-run \\`loam init --docs ${config?.docsDirAsWritten ?? \"<dir>\"} --tools ${tools.join(\",\")}\\` `\n + \"here — it writes only the files that are absent and leaves every existing one untouched, and \"\n + \"it keeps this repo's service binding. Nothing is regenerated, so leaving this standing costs \"\n + \"entry points and nothing else.\",\n });\n }\n\n if (staleFiles.length > 0) {\n // Also a warning, and for a stronger reason than the one above: these files\n // are the team's now. Only a person can say whether their edits still mean\n // what the current binary's tables say, so loam reports and stops.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-stale\",\n message:\n `${staleFiles.length} of the ${planned.length} command and skill files here carry no `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\` stamp, or one older than this loam — the protocol and `\n + `code tables they instruct an agent with may describe a loam that no longer exists: `\n + `${staleFiles.join(\", \")}`,\n fix:\n \"Read each against the body this loam writes, then set its stamp line to \"\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`; or delete the file and re-run \\`loam init\\` to lay `\n + \"down the current one. loam never rewrites a file that exists — your edits outrank the \"\n + \"template, which is why the stamp is a human's claim and not a refresh.\",\n });\n }\n\n // AGENTS.md lives in the DOCS repo, not here: it travels with the thing it\n // describes. The comparison is `agentsStaleFinding`'s, not a second copy of\n // it — doctor only adds the `fix` column its own findings owe the reader.\n const agentsFile = docsDir === null ? null : agentsPath(docsDir);\n let agentsText: string | null = null;\n if (agentsFile !== null) {\n try {\n agentsText = await readFile(agentsFile, \"utf8\");\n } catch {\n // Unreadable reads as absent, exactly as `validate --all` treats it: no\n // file, no contract to have drifted. An unreadable docs repo is already\n // its own finding.\n }\n }\n const stale = agentsStaleFinding(agentsText, LOAM_VERSION);\n if (stale !== null) {\n findings.push({\n severity: \"warning\",\n code: stale.code,\n message: stale.message,\n fix:\n `Review ${agentsFile} against the current \\`loam --help\\`, then set its stamp line to `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`. loam never rewrites that file — bumping the stamp IS `\n + \"the record that somebody looked.\",\n });\n }\n\n return {\n tools,\n toolsSource: recorded === null ? \"disk\" : \"config\",\n plannedFiles: planned.length,\n missingFiles,\n staleFiles,\n stamp: {\n path: agentsFile,\n present: agentsText !== null,\n version: agentsText === null ? null : agentsStampVersion(agentsText),\n stale: stale !== null,\n },\n };\n}\n\n/**\n * Grade what a write that did not finish left in the docs repo.\n *\n * Three of the four are blockers, which is unusual for doctor and deliberate:\n * they are not \"you have fewer entry points than you could\", they are \"the\n * living docs may be half-written and the next reader cannot tell\". Before this\n * existed, a SIGKILL between two of archive's renames left `doctor: healthy:\n * true` over merged spec.md + openapi.yaml and an unmerged landscape, and the\n * next `loam archive` reported loam's own half-merge as the author's bug.\n *\n * The temp files are the exception: a `.loam-*.tmp` was never linked into place,\n * so nothing reads it and nothing depends on it. It is litter, not damage.\n */\nfunction gradeWritePathResidue(\n docsDir: string,\n residue: WritePathResidue,\n findings: DoctorFinding[],\n): void {\n if (residue.lock !== null) {\n const lockFile = join(docsDir, residue.lock.path);\n findings.push({\n // Held by a live process is a fact about right now — wait and re-run.\n // Held by a process that no longer exists is damage: nothing will ever\n // release it, and every archive and unarchive refuses `docs-busy` until\n // somebody deletes it.\n severity: residue.lock.stale ? \"blocker\" : \"warning\",\n code: \"doctor.docs-locked\",\n message: residue.lock.stale\n ? `${residue.lock.path} is held by ${residue.lock.holder}, a process that no longer exists on this host — every \\`loam archive\\` and \\`loam unarchive\\` will refuse with \\`docs-busy\\` until it is gone.`\n : `${residue.lock.path} is held by ${residue.lock.holder}; another archive or unarchive is running against this docs repo.`,\n fix: residue.lock.stale\n ? `Delete ${lockFile} — its holder is dead, so nothing is going to release it. Check \\`loam doctor\\` again afterwards for an interrupted commit.`\n : \"Wait for it to finish and re-run; nothing is read or written while it is held.\",\n });\n }\n\n if (residue.intentUnreadable) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.commit-unreadable\",\n message: `${docsDir} holds a .loam-commit that cannot be read — a commit was interrupted and the one record of which files it had already written is unreadable.`,\n fix: \"Hand this to a human: compare the living docs against version control before running any loam command that writes. `loam archive` and `loam unarchive` both refuse with `commit-interrupted` while it is there.\",\n });\n } else if (residue.intent !== null) {\n const i = residue.intent;\n // The command is spelled out per branch rather than interpolated into one\n // sentence: `loam ${i.command} ${i.feature}` reads fine to a person and is\n // opaque to test/agent-commands-runnable.test.ts, which parses the commands\n // loam prints against the real program. Same discipline as spelling `code:`\n // literally — be visible to your own guard.\n const repair = i.command === \"archive\"\n ? `\\`loam archive ${i.feature}\\` — it recovers first, under the lock, putting the half-written files back from the snapshot`\n : `\\`loam unarchive ${i.feature}\\` — it recovers first, under the lock, finishing the restore (an interrupted restore is FINISHED, never undone: the merged text it was replacing is written down nowhere)`;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.commit-interrupted\",\n message:\n `A ${i.command === \"archive\" ? `\\`loam archive ${i.feature}\\`` : `\\`loam unarchive ${i.feature}\\``} `\n + `was killed mid-commit (${i.host}, pid ${i.pid}, ${i.at}) — `\n + `${i.files.length} file(s) may be half-written: ${i.files.map((f) => f.path).join(\", \")}`,\n fix: `Re-run ${repair}, and refuses with \\`commit-interrupted\\` rather than guessing if a file has been edited since.`,\n });\n }\n\n if (residue.temps.length > 0) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.staging-temps\",\n message: `${residue.temps.length} orphaned staging file(s) under ${docsDir}: ${residue.temps.join(\", \")} — a killed writer's scratch, never linked into place.`,\n fix: \"The next `loam archive` or `loam unarchive` removes its own; delete any that remain. Nothing reads them, so they cost disk and nothing else.\",\n });\n }\n}\n\nexport async function diagnose(cwd = process.cwd()): Promise<DoctorReport> {\n const inspected = await inspectConfig(cwd);\n const path = configPath(cwd);\n const findings: DoctorFinding[] = [];\n if (inspected.status === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-missing\",\n message: `No ${CONFIG_FILENAME} found at or above ${cwd}.`,\n fix: \"Run `loam init --docs <path-to-docs-repo>` here (add `--create` to make a new docs repo).\",\n });\n } else if (inspected.status === \"invalid\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-invalid\",\n message: `Invalid ${CONFIG_FILENAME}: ${inspected.error ?? \"unknown error\"}`,\n fix: `Repair ${path} — or delete it and re-run \\`loam init\\`.`,\n });\n }\n\n const docsDir = inspected.config?.docsDir ?? null;\n const state = docsDir === null ? null : docsRepoState(docsDir);\n const exists = state !== null && state.kind !== \"missing\";\n const readable = exists && docsDir !== null ? await canAccess(docsDir, constants.R_OK) : false;\n const writable = exists && docsDir !== null ? await canAccess(docsDir, constants.W_OK) : false;\n const servicesDir = state?.kind === \"ok\";\n const landscapeFile = docsDir === null ? null : landscapePath(docsDir);\n const landscape = landscapeFile === null ? false : await canAccess(landscapeFile, constants.F_OK);\n\n // The docs-repo verdict comes from `docsRepoState`, the same function\n // `listServices` refuses on, so doctor cannot call a repo healthy that the\n // enumeration will not read.\n if (state?.kind === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.docs-missing\",\n message: `Configured docsDir is missing or is not a directory: ${docsDir}`,\n fix: `Fix \"docsDir\" in ${path}, clone the docs repo to ${docsDir}, or run \\`loam init --docs <dir> --create\\`.`,\n });\n } else if (state?.kind === \"no-services\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.services-missing\",\n message: `Required services/ directory is missing under ${docsDir} — that path is not a docs repo.`,\n fix: `Point \"docsDir\" in ${path} at the shared docs repo, or run \\`loam init --docs ${docsDir} --create\\`.`,\n });\n }\n\n // An absolute docsDir in a committed config names a directory that exists on\n // exactly one machine. It is a warning and not a blocker because it works\n // perfectly — for the person who ran `loam init`, and for nobody who clones\n // the repo afterwards.\n const asWritten = inspected.config?.docsDirAsWritten;\n const configRoot = inspected.config?.root;\n if (\n asWritten !== undefined\n && configRoot !== undefined\n && isAbsolute(asWritten)\n && resolve(asWritten) !== resolve(configRoot)\n ) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-absolute\",\n message:\n `\"docsDir\" is stored as an absolute path (${asWritten}); ` +\n `${CONFIG_FILENAME} is committed, so it will not resolve on anyone else's machine.`,\n fix: `Rewrite \"docsDir\" in ${path} as a path relative to that file (e.g. \"../docs\").`,\n });\n }\n\n if (exists && !readable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-unreadable\",\n message: `docsDir is not readable by this process: ${docsDir}`,\n fix: `Grant read access to ${docsDir} (check ownership and mode).`,\n });\n }\n if (exists && !writable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-readonly\",\n message: `docsDir is not writable; read-only commands work, archive/init do not: ${docsDir}`,\n fix: `Grant write access to ${docsDir} if you need \\`loam new\\`, \\`loam adopt\\` or \\`loam archive\\`.`,\n });\n }\n if (exists && !landscape) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.landscape-missing\",\n message: `${landscapeFile} is missing — nothing cross-service can be checked without it.`,\n fix: \"Create architecture/landscape.likec4 with one element per service and an edge per call (`loam init --create` writes the empty map).\",\n });\n } else if (exists && landscapeFile !== null && readable) {\n await inspectLandscape(landscapeFile, findings);\n }\n\n // What a killed writer left behind. Read from `scanWritePathResidue`, whose\n // spellings of `.loam-lock` / `.loam-commit` / the temp-file pattern are\n // staging's own — doctor grades, it does not re-spell. This is the surface\n // that used to report `healthy: true` over half-merged docs.\n const writePath = docsDir !== null && exists && readable\n ? await scanWritePathResidue(docsDir)\n : null;\n if (docsDir !== null && writePath !== null) gradeWritePathResidue(docsDir, writePath, findings);\n\n let services: Awaited<ReturnType<typeof listServices>> = [];\n let activeFeatures: Awaited<ReturnType<typeof listFeatures>> = [];\n // Whether the fleet was actually READ, which is a narrower question than\n // whether reading it was attempted — hence set inside the try and not from\n // the guard, so an enumeration that threw leaves this false alongside its own\n // `doctor.inventory-unreadable`.\n let inventoried = false;\n if (docsDir !== null && readable && servicesDir) {\n try {\n [services, activeFeatures] = await Promise.all([\n listServices(docsDir),\n listFeatures(docsDir),\n ]);\n inventoried = true;\n } catch (error) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.inventory-unreadable\",\n message: `Could not inventory the docs repo: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check that ${docsDir}/services and ${docsDir}/features are readable directories.`,\n });\n }\n }\n\n const configuredService = inspected.config?.service ?? null;\n const currentService: DoctorReport[\"currentService\"] = configuredService === null\n ? { configured: null, status: \"unbound\" }\n : services.some((service) => service.id === configuredService)\n ? { configured: configuredService, status: \"matched\" }\n : { configured: configuredService, status: \"unknown\" };\n if (currentService.status === \"unbound\") {\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unbound\",\n message: \"loam.json has no service binding; service-repo checks need `service`.\",\n fix: `Run \\`loam init --docs ${inspected.config?.docsDirAsWritten ?? \"<dir>\"} --service <id>\\` here.`,\n });\n } else if (currentService.status === \"unknown\" && inventoried) {\n // Only when there was an inventory to be absent from. This finding is a\n // claim about a fleet that was read; with no docsDir, an unreadable one, or\n // an enumeration that threw, `services` is empty for a reason that has\n // nothing to do with `service`, and doctor pointed the reader at the one\n // config field that was fine while the blocker it had already filed named\n // the real one. `currentService.status` still reports `unknown` — the\n // envelope describes what loam could and could not match, and an unread\n // fleet matches nothing.\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unknown\",\n message: `Configured service '${configuredService}' is not present under services/.`,\n // `--service <id>`, not a positional: `loam adopt` takes no argument, so\n // the spelling this finding used to print was refused by commander with\n // \"too many arguments\" — as the FIRST instruction a freshly bound service\n // repo ever receives. test/agent-commands-runnable.test.ts parses every\n // `loam …` loam prints against the real program so it cannot recur.\n fix: `Run \\`loam adopt --service ${configuredService}\\` to onboard it, or fix \"service\" in ${path}.`,\n });\n }\n\n // The slash commands (and everything else `init` lays down for a tool) belong\n // to the repo `init` ran in — the directory holding loam.json, which is not\n // the cwd now that config discovery walks upward.\n const agents = await inspectAgentSurface(dirname(path), inspected.config, docsDir, findings);\n\n return {\n healthy: !findings.some((finding) => finding.severity === \"blocker\"),\n runtime: {\n package: \"@ybotok/loam\",\n version: LOAM_VERSION,\n node: process.version,\n platform: process.platform,\n arch: process.arch,\n },\n config: { path, status: inspected.status, error: inspected.error },\n docs: {\n path: docsDir,\n exists,\n readable,\n writable,\n servicesDir,\n landscape,\n },\n counts: { services: services.length, activeFeatures: activeFeatures.length },\n currentService,\n agents,\n writePath,\n findings,\n };\n}\n"]}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/core/doctor.ts"],"names":[],"mappings":"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","sourcesContent":["/** Read-only installation/repository diagnostics for `loam doctor`. */\nimport { constants, existsSync } from \"node:fs\";\nimport { access, readFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { CONFIG_FILENAME, configPath, parseConfig, type LoamConfig } from \"./config.js\";\nimport { LOAM_VERSION } from \"./version.js\";\nimport { agentsPath, docsRepoState, landscapePath, listFeatures, listServices } from \"./repo.js\";\nimport { loadFile } from \"./likec4.js\";\nimport { LIKEC4_PROJECT_CONFIG, LIKEC4_PROJECT_FILENAME } from \"./docs.js\";\nimport { conflictMarkerLines } from \"./fleet-context.js\";\nimport { AGENT_TOOLS, DELIVERIES, plannedCommandFiles, type Delivery } from \"./agent.js\";\nimport {\n agentsStaleFinding,\n agentsStampLine,\n agentsStampVersion,\n versionTrails,\n} from \"./agents-stamp.js\";\nimport { scanWritePathResidue, type WritePathResidue } from \"./staging.js\";\n\nexport type DoctorSeverity = \"blocker\" | \"warning\";\n\nexport interface DoctorFinding {\n severity: DoctorSeverity;\n code: string;\n message: string;\n /**\n * The next command or edit that clears this finding. Mandatory, not optional:\n * a diagnostic that names a problem without naming its fix is a diagnostic\n * the reader has to research, and `doctor` is what someone runs precisely\n * because they do not yet know what loam wants from them.\n */\n fix: string;\n}\n\nexport interface DoctorReport {\n healthy: boolean;\n runtime: {\n package: \"@ybotok/loam\";\n version: string;\n node: string;\n platform: NodeJS.Platform;\n arch: string;\n };\n config: {\n path: string;\n status: \"missing\" | \"invalid\" | \"valid\";\n error: string | null;\n };\n docs: {\n path: string | null;\n exists: boolean;\n readable: boolean;\n writable: boolean;\n servicesDir: boolean;\n landscape: boolean;\n };\n counts: { services: number; activeFeatures: number };\n currentService: {\n configured: string | null;\n status: \"unbound\" | \"matched\" | \"unknown\";\n };\n /** The agent surface of this repo — see `inspectAgentSurface`. */\n agents: AgentSurface;\n /**\n * What a write that did not finish left in the docs repo, or null when\n * `docsDir` never resolved. Reported as state, like `docs` and `counts`: a\n * lock somebody is legitimately holding right now is a fact, not a complaint.\n */\n writePath: WritePathResidue | null;\n findings: DoctorFinding[];\n}\n\nexport interface AgentSurface {\n /** Tool ids this repo has an agent surface for. Empty is legal (`init --no-commands`). */\n tools: string[];\n /** Whether `tools` came from loam.json's own record or from what is on disk. */\n toolsSource: \"config\" | \"disk\";\n /** How many files the running binary would lay down for `tools`. */\n plannedFiles: number;\n /** Repo-relative paths of those files that are not there. */\n missingFiles: string[];\n /**\n * Repo-relative paths of the ones that ARE there carrying no version stamp,\n * or one older than this binary — see `staleAgentFiles`.\n */\n staleFiles: string[];\n stamp: {\n /** Where the docs repo's AGENTS.md is, or null when `docsDir` did not resolve. */\n path: string | null;\n /** Whether it could be read at all — absent and unstamped are different facts. */\n present: boolean;\n /** The version the stamp names, null when there is none (or an unparseable one). */\n version: string | null;\n /** Whether `agents.stale` fired: the stamp is missing or trails this binary. */\n stale: boolean;\n };\n}\n\ninterface ConfigInspection {\n status: DoctorReport[\"config\"][\"status\"];\n config: LoamConfig | null;\n error: string | null;\n}\n\n/**\n * Read and validate the config the way every other command does — through\n * `parseConfig`, never through a second implementation.\n *\n * doctor used to carry its own validator. It agreed with `loadConfig` on the\n * fields both happened to check and disagreed everywhere else: doctor accepted\n * a `gherkinDir` of `\"../shared\"` that `loadConfig` refused outright, so\n * `doctor` reported a healthy repo in which no command could run. Two\n * validators are two opinions about the same file, and the one the user reads\n * is never the one the commands obey.\n */\nasync function inspectConfig(cwd: string): Promise<ConfigInspection> {\n const path = configPath(cwd);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") return { status: \"missing\", config: null, error: null };\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n\n try {\n return { status: \"valid\", error: null, config: parseConfig(raw, dirname(path)) };\n } catch (error) {\n return {\n status: \"invalid\",\n config: null,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nasync function canAccess(path: string, mode: number): Promise<boolean> {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read the landscape, don't just stat it. `doctor` reported `landscape: yes`\n * for a file full of conflict markers — the check answered \"is there a file\",\n * which is not the question anyone runs doctor to ask.\n */\nasync function inspectLandscape(path: string, findings: DoctorFinding[]): Promise<void> {\n let source: string;\n try {\n source = await readFile(path, \"utf8\");\n } catch (error) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-unreadable\",\n message: `${path} exists but could not be read: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check permissions on ${path}.`,\n });\n return;\n }\n\n // The three-way merge left its markers in the file. Checked BEFORE the\n // parser, because a conflicted landscape does parse sometimes — both sides of\n // a conflict can be syntactically valid LikeC4 — and \"your map contains two\n // halves of two different maps\" is a more useful sentence than any parser\n // error. This is the failure mode of onboarding a fleet: ten people adopt ten\n // services into one landscape.likec4 in the same week.\n //\n // The scan itself is fleet-context's, which is where the same rule already\n // grades every other document loam reads. doctor kept a second copy of it,\n // and a second copy of a rule is a second chance to spell it differently;\n // what doctor owns is what a conflicted landscape COSTS, which is the finding\n // below and not the search.\n const conflicted = conflictMarkerLines(source);\n if (conflicted.length > 0) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-merge-conflict\",\n message:\n `architecture/landscape.likec4 still contains merge conflict markers ` +\n `(line${conflicted.length === 1 ? \"\" : \"s\"} ${conflicted.join(\", \")}).`,\n fix: `Resolve the conflict in ${path} — keep BOTH services' elements and edges, then re-run \\`loam doctor\\`.`,\n });\n return;\n }\n\n const doc = await loadFile(path);\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.landscape-invalid\",\n message:\n `architecture/landscape.likec4 does not parse: ${first.message}` +\n (first.line === undefined ? \"\" : ` (line ${first.line})`) +\n (doc.errors.length > 1 ? ` — and ${doc.errors.length - 1} more` : \"\"),\n fix: `Fix ${path}; every fleet-wide check is blind until it parses.`,\n });\n }\n}\n\n/**\n * The tool ids loam.json records `init` as having written files for, or null\n * when it records none we can plan for.\n *\n * `parseConfig` checks `agentTools`' SHAPE and deliberately not its contents —\n * a config written by a newer binary may name a tool this one has never heard\n * of, and refusing to load it would break every command, not just this check.\n * So the registry filter belongs here: `plannedCommandFiles` throws on an id it\n * does not know, and a config from the future must not turn the read-only\n * preflight into a crash. A tool we cannot plan files for is a tool we have\n * nothing to say about.\n */\nfunction recordedTools(config: LoamConfig | null): string[] | null {\n const ids = (config?.agentTools ?? []).filter((t) => t in AGENT_TOOLS);\n return ids.length === 0 ? null : ids;\n}\n\n/**\n * Which tools this repo has agent files for, asked of the layout itself: a tool\n * counts when any file it would be given is already there.\n *\n * The fallback for a loam.json written before `agentTools` existed, and the\n * only answer a hand-written one can have — so its absence must never be the\n * thing that reports a problem. This asks a narrower question than `init`'s\n * own scan, and on purpose: init scans for a tool's dot-directory to decide\n * what a repo would LIKE, while doctor grades what loam has already put there.\n * A repo that runs Claude Code and took `--no-commands --no-skills` has chosen\n * to have no loam files, and is not behind the binary for it.\n */\nfunction detectedTools(repoRoot: string): string[] {\n return Object.keys(AGENT_TOOLS).filter((id) =>\n plannedCommandFiles(repoRoot, [id]).some((f) => existsSync(f.path)));\n}\n\n/**\n * Which deliveries this repo actually HOLDS for one tool — a delivery counts\n * when at least one of its files is on disk.\n *\n * `loam init --no-skills` (or `--no-commands`) records the tool in `agentTools`\n * and writes one delivery, so planning both and subtracting what exists reported\n * every skill file as missing on a brand-new repo — under a message saying it\n * \"was initialized by an older loam\" and a `fix` that re-runs init without the\n * flag that was the whole point. Which deliveries a repo asked for is written\n * down nowhere, and must not be: the files ARE the record (there is no state\n * file loam can disagree with). So the question is asked of them.\n *\n * A tool with NO files at all is the other story — recorded, then deleted — and\n * the caller falls back to the full plan there, which is what the finding's\n * \"or they were deleted\" already says.\n */\nfunction heldDeliveries(repoRoot: string, tool: string): readonly Delivery[] {\n const held = DELIVERIES.filter((d) =>\n plannedCommandFiles(repoRoot, [tool], [d]).some((f) => existsSync(f.path)));\n return held.length > 0 ? held : DELIVERIES;\n}\n\n/**\n * Of the planned files that ARE here, the ones whose stamp is missing or older\n * than this binary.\n *\n * The stamp is the ONLY thing read out of a generated file — never the body.\n * Comparing bodies would yield \"your file differs from the template\", one step\n * from offering to rewrite it, which is the thing loam refuses: a team's edits\n * to a generated file outrank ours. A stamp is a different claim — it says\n * which loam wrote these instructions — and it can be false while every edit\n * around it is legitimate. Same doctrine as AGENTS.md (agents-stamp.ts), same\n * repair: a human looks, and bumps the stamp or deletes the file so a re-run of\n * `loam init` lays down the current one.\n *\n * Absence of a stamp counts, because that is what every file written before\n * stamping existed looks like — precisely the \"initialized by an older loam\"\n * population this check is for.\n */\nasync function staleAgentFiles(\n repoRoot: string,\n planned: Array<{ path: string }>,\n): Promise<string[]> {\n // Read them together rather than one after another. `planned` is bounded by\n // the tool registry — every delivery of every tool loam knows, a couple of\n // hundred small files — so there is nothing here worth a concurrency cap, and\n // `Promise.all` preserves input order, which is the only property the caller\n // asks of the list it gets back.\n const graded = await Promise.all(planned.map(async (file): Promise<string | null> => {\n let text: string;\n try {\n text = await readFile(file.path, \"utf8\");\n } catch {\n // Unreadable is not stale: it is either the missing-file finding above or\n // a permissions problem, and neither is answered by editing a stamp.\n return null;\n }\n const stamp = agentsStampVersion(text);\n if (stamp === null || versionTrails(stamp, LOAM_VERSION)) {\n return relative(repoRoot, file.path).split(/[\\\\/]/).join(\"/\");\n }\n return null;\n }));\n return graded.filter((p) => p !== null);\n}\n\n/**\n * What the running binary would write for this repo's tools that is not here,\n * and what is here under an older loam's name.\n *\n * Presence is `existsSync`, the same probe `init` uses to compute its `skipped`\n * list, so doctor cannot disagree with init about which files init would write.\n */\nasync function inspectAgentSurface(\n repoRoot: string,\n config: LoamConfig | null,\n docsDir: string | null,\n findings: DoctorFinding[],\n): Promise<AgentSurface> {\n const recorded = recordedTools(config);\n const tools = recorded ?? detectedTools(repoRoot);\n const planned = tools.flatMap((id) =>\n plannedCommandFiles(repoRoot, [id], heldDeliveries(repoRoot, id)));\n const present = planned.filter((f) => existsSync(f.path));\n const missingFiles = planned\n .filter((f) => !existsSync(f.path))\n .map((f) => relative(repoRoot, f.path).split(/[\\\\/]/).join(\"/\"));\n const staleFiles = await staleAgentFiles(repoRoot, present);\n\n if (missingFiles.length > 0) {\n // A warning, never a blocker: an out-of-date command set is a repo whose\n // agents have fewer entry points than they could, not one where anything\n // refuses to run. doctor's blockers are for the second kind.\n //\n // The fix spells `--docs` and `--tools` and deliberately NOT `--service`:\n // `init` spreads the committed config forward, and a `--docs`-less re-run\n // keeps the pointer the repo already commits, so the binding survives\n // untouched. Every interpolation here stands for exactly one argument —\n // test/agent-commands-runnable.test.ts parses this string, and a `${flags}`\n // holding three of them is a command it cannot check.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-missing\",\n message:\n `${missingFiles.length} of the ${planned.length} command and skill files loam v${LOAM_VERSION} `\n + `lays down for ${tools.join(\", \")} are not in this repo — it was initialized by an older loam, `\n + `or they were deleted: ${missingFiles.join(\", \")}`,\n fix:\n `Re-run \\`loam init --docs ${config?.docsDirAsWritten ?? \"<dir>\"} --tools ${tools.join(\",\")}\\` `\n + \"here — it writes only the files that are absent and leaves every existing one untouched, and \"\n + \"it keeps this repo's service binding. Nothing is regenerated, so leaving this standing costs \"\n + \"entry points and nothing else.\",\n });\n }\n\n if (staleFiles.length > 0) {\n // Also a warning, and for a stronger reason than the one above: these files\n // are the team's now. Only a person can say whether their edits still mean\n // what the current binary's tables say, so loam reports and stops.\n findings.push({\n severity: \"warning\",\n code: \"doctor.agent-files-stale\",\n message:\n `${staleFiles.length} of the ${planned.length} command and skill files here carry no `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\` stamp, or one older than this loam — the protocol and `\n + `code tables they instruct an agent with may describe a loam that no longer exists: `\n + `${staleFiles.join(\", \")}`,\n fix:\n \"Read each against the body this loam writes, then set its stamp line to \"\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`; or delete the file and re-run \\`loam init\\` to lay `\n + \"down the current one. loam never rewrites a file that exists — your edits outrank the \"\n + \"template, which is why the stamp is a human's claim and not a refresh.\",\n });\n }\n\n // AGENTS.md lives in the DOCS repo, not here: it travels with the thing it\n // describes. The comparison is `agentsStaleFinding`'s, not a second copy of\n // it — doctor only adds the `fix` column its own findings owe the reader.\n const agentsFile = docsDir === null ? null : agentsPath(docsDir);\n let agentsText: string | null = null;\n if (agentsFile !== null) {\n try {\n agentsText = await readFile(agentsFile, \"utf8\");\n } catch {\n // Unreadable reads as absent, exactly as `validate --all` treats it: no\n // file, no contract to have drifted. An unreadable docs repo is already\n // its own finding.\n }\n }\n const stale = agentsStaleFinding(agentsText, LOAM_VERSION);\n if (stale !== null) {\n findings.push({\n severity: \"warning\",\n code: stale.code,\n message: stale.message,\n fix:\n `Review ${agentsFile} against the current \\`loam --help\\`, then set its stamp line to `\n + `\\`${agentsStampLine(LOAM_VERSION)}\\`. loam never rewrites that file — bumping the stamp IS `\n + \"the record that somebody looked.\",\n });\n }\n\n return {\n tools,\n toolsSource: recorded === null ? \"disk\" : \"config\",\n plannedFiles: planned.length,\n missingFiles,\n staleFiles,\n stamp: {\n path: agentsFile,\n present: agentsText !== null,\n version: agentsText === null ? null : agentsStampVersion(agentsText),\n stale: stale !== null,\n },\n };\n}\n\n/**\n * Grade what a write that did not finish left in the docs repo.\n *\n * Three of the four are blockers, which is unusual for doctor and deliberate:\n * they are not \"you have fewer entry points than you could\", they are \"the\n * living docs may be half-written and the next reader cannot tell\". Before this\n * existed, a SIGKILL between two of archive's renames left `doctor: healthy:\n * true` over merged spec.md + openapi.yaml and an unmerged landscape, and the\n * next `loam archive` reported loam's own half-merge as the author's bug.\n *\n * The temp files are the exception: a `.loam-*.tmp` was never linked into place,\n * so nothing reads it and nothing depends on it. It is litter, not damage.\n */\nfunction gradeWritePathResidue(\n docsDir: string,\n residue: WritePathResidue,\n findings: DoctorFinding[],\n): void {\n if (residue.lock !== null) {\n const lockFile = join(docsDir, residue.lock.path);\n findings.push({\n // Held by a live process is a fact about right now — wait and re-run.\n // Held by a process that no longer exists is damage: nothing will ever\n // release it, and every archive and unarchive refuses `docs-busy` until\n // somebody deletes it.\n severity: residue.lock.stale ? \"blocker\" : \"warning\",\n code: \"doctor.docs-locked\",\n message: residue.lock.stale\n ? `${residue.lock.path} is held by ${residue.lock.holder}, a process that no longer exists on this host — every \\`loam archive\\` and \\`loam unarchive\\` will refuse with \\`docs-busy\\` until it is gone.`\n : `${residue.lock.path} is held by ${residue.lock.holder}; another archive or unarchive is running against this docs repo.`,\n fix: residue.lock.stale\n ? `Delete ${lockFile} — its holder is dead, so nothing is going to release it. Check \\`loam doctor\\` again afterwards for an interrupted commit.`\n : \"Wait for it to finish and re-run; nothing is read or written while it is held.\",\n });\n }\n\n if (residue.intentUnreadable) {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.commit-unreadable\",\n message: `${docsDir} holds a .loam-commit that cannot be read — a commit was interrupted and the one record of which files it had already written is unreadable.`,\n fix: \"Hand this to a human: compare the living docs against version control before running any loam command that writes. `loam archive` and `loam unarchive` both refuse with `commit-interrupted` while it is there.\",\n });\n } else if (residue.intent !== null) {\n const i = residue.intent;\n // The command is spelled out per branch rather than interpolated into one\n // sentence: `loam ${i.command} ${i.feature}` reads fine to a person and is\n // opaque to test/agent-commands-runnable.test.ts, which parses the commands\n // loam prints against the real program. Same discipline as spelling `code:`\n // literally — be visible to your own guard.\n const repair = i.command === \"archive\"\n ? `\\`loam archive ${i.feature}\\` — it recovers first, under the lock, putting the half-written files back from the snapshot`\n : `\\`loam unarchive ${i.feature}\\` — it recovers first, under the lock, finishing the restore (an interrupted restore is FINISHED, never undone: the merged text it was replacing is written down nowhere)`;\n findings.push({\n severity: \"blocker\",\n code: \"doctor.commit-interrupted\",\n message:\n `A ${i.command === \"archive\" ? `\\`loam archive ${i.feature}\\`` : `\\`loam unarchive ${i.feature}\\``} `\n + `was killed mid-commit (${i.host}, pid ${i.pid}, ${i.at}) — `\n + `${i.files.length} file(s) may be half-written: ${i.files.map((f) => f.path).join(\", \")}`,\n fix: `Re-run ${repair}, and refuses with \\`commit-interrupted\\` rather than guessing if a file has been edited since.`,\n });\n }\n\n if (residue.temps.length > 0) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.staging-temps\",\n message: `${residue.temps.length} orphaned staging file(s) under ${docsDir}: ${residue.temps.join(\", \")} — a killed writer's scratch, never linked into place.`,\n fix: \"The next `loam archive` or `loam unarchive` removes its own; delete any that remain. Nothing reads them, so they cost disk and nothing else.\",\n });\n }\n}\n\nexport async function diagnose(cwd = process.cwd()): Promise<DoctorReport> {\n const inspected = await inspectConfig(cwd);\n const path = configPath(cwd);\n const findings: DoctorFinding[] = [];\n if (inspected.status === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-missing\",\n message: `No ${CONFIG_FILENAME} found at or above ${cwd}.`,\n fix: \"Run `loam init --docs <path-to-docs-repo>` here (add `--create` to make a new docs repo).\",\n });\n } else if (inspected.status === \"invalid\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.config-invalid\",\n message: `Invalid ${CONFIG_FILENAME}: ${inspected.error ?? \"unknown error\"}`,\n fix: `Repair ${path} — or delete it and re-run \\`loam init\\`.`,\n });\n }\n\n const docsDir = inspected.config?.docsDir ?? null;\n const state = docsDir === null ? null : docsRepoState(docsDir);\n const exists = state !== null && state.kind !== \"missing\";\n const readable = exists && docsDir !== null ? await canAccess(docsDir, constants.R_OK) : false;\n const writable = exists && docsDir !== null ? await canAccess(docsDir, constants.W_OK) : false;\n const servicesDir = state?.kind === \"ok\";\n const landscapeFile = docsDir === null ? null : landscapePath(docsDir);\n const landscape = landscapeFile === null ? false : await canAccess(landscapeFile, constants.F_OK);\n\n // The docs-repo verdict comes from `docsRepoState`, the same function\n // `listServices` refuses on, so doctor cannot call a repo healthy that the\n // enumeration will not read.\n if (state?.kind === \"missing\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.docs-missing\",\n message: `Configured docsDir is missing or is not a directory: ${docsDir}`,\n fix: `Fix \"docsDir\" in ${path}, clone the docs repo to ${docsDir}, or run \\`loam init --docs <dir> --create\\`.`,\n });\n } else if (state?.kind === \"no-services\") {\n findings.push({\n severity: \"blocker\",\n code: \"doctor.services-missing\",\n message: `Required services/ directory is missing under ${docsDir} — that path is not a docs repo.`,\n fix: `Point \"docsDir\" in ${path} at the shared docs repo, or run \\`loam init --docs ${docsDir} --create\\`.`,\n });\n }\n\n // An absolute docsDir in a committed config names a directory that exists on\n // exactly one machine. It is a warning and not a blocker because it works\n // perfectly — for the person who ran `loam init`, and for nobody who clones\n // the repo afterwards.\n const asWritten = inspected.config?.docsDirAsWritten;\n const configRoot = inspected.config?.root;\n if (\n asWritten !== undefined\n && configRoot !== undefined\n && isAbsolute(asWritten)\n && resolve(asWritten) !== resolve(configRoot)\n ) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-absolute\",\n message:\n `\"docsDir\" is stored as an absolute path (${asWritten}); ` +\n `${CONFIG_FILENAME} is committed, so it will not resolve on anyone else's machine.`,\n fix: `Rewrite \"docsDir\" in ${path} as a path relative to that file (e.g. \"../docs\").`,\n });\n }\n\n if (exists && !readable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-unreadable\",\n message: `docsDir is not readable by this process: ${docsDir}`,\n fix: `Grant read access to ${docsDir} (check ownership and mode).`,\n });\n }\n if (exists && !writable) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.docs-readonly\",\n message: `docsDir is not writable; read-only commands work, archive/init do not: ${docsDir}`,\n fix: `Grant write access to ${docsDir} if you need \\`loam new\\`, \\`loam adopt\\` or \\`loam archive\\`.`,\n });\n }\n if (exists && !landscape) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.landscape-missing\",\n message: `${landscapeFile} is missing — nothing cross-service can be checked without it.`,\n fix: \"Create architecture/landscape.likec4 with one element per service and an edge per call (`loam init --create` writes the empty map).\",\n });\n } else if (exists && landscapeFile !== null && readable) {\n await inspectLandscape(landscapeFile, findings);\n }\n\n // The renderer's view of the repo, which is not loam's. loam parses every\n // `.likec4` file ALONE, so each one declares its own `specification` block and\n // re-declares whatever elements it names; LikeC4's own workspace loader merges\n // the whole tree into one model, and those declarations then collide. The\n // result was two tools disagreeing in the most confusing possible direction:\n // `loam validate --all` reported zero errors on a tree where `npx likec4\n // start` — the command loam's own brief recommends — refused to load at all.\n // `loam init --create` writes the project file that scopes the root project to\n // the landscape; a docs repo created before it exists has to be told, because\n // nothing else will ever mention it. A warning, not a blocker: no loam check\n // reads this file, and a repo without it is only unrenderable.\n if (docsDir !== null && exists && readable && !existsSync(join(docsDir, LIKEC4_PROJECT_FILENAME))) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.likec4-config-missing\",\n message:\n `${docsDir}/${LIKEC4_PROJECT_FILENAME} is missing, so this tree is not a loadable LikeC4 workspace — `\n + \"every service model and feature delta declares its own `specification` block, and pointing a \"\n + \"renderer at the repo root merges them into one model and reports every declaration as a duplicate.\",\n fix:\n `Write ${docsDir}/${LIKEC4_PROJECT_FILENAME} with:\\n${LIKEC4_PROJECT_CONFIG.trimEnd()}\\n`\n + \"That scopes the root project to architecture/, so `npx likec4 start` in the docs repo renders \"\n + \"the fleet map. A service model or feature delta is rendered by pointing the renderer at its own \"\n + \"directory (`npx likec4 start services/<id>`) — being readable alone is what those files are for.\",\n });\n }\n\n // What a killed writer left behind. Read from `scanWritePathResidue`, whose\n // spellings of `.loam-lock` / `.loam-commit` / the temp-file pattern are\n // staging's own — doctor grades, it does not re-spell. This is the surface\n // that used to report `healthy: true` over half-merged docs.\n const writePath = docsDir !== null && exists && readable\n ? await scanWritePathResidue(docsDir)\n : null;\n if (docsDir !== null && writePath !== null) gradeWritePathResidue(docsDir, writePath, findings);\n\n let services: Awaited<ReturnType<typeof listServices>> = [];\n let activeFeatures: Awaited<ReturnType<typeof listFeatures>> = [];\n // Whether the fleet was actually READ, which is a narrower question than\n // whether reading it was attempted — hence set inside the try and not from\n // the guard, so an enumeration that threw leaves this false alongside its own\n // `doctor.inventory-unreadable`.\n let inventoried = false;\n if (docsDir !== null && readable && servicesDir) {\n try {\n [services, activeFeatures] = await Promise.all([\n listServices(docsDir),\n listFeatures(docsDir),\n ]);\n inventoried = true;\n } catch (error) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.inventory-unreadable\",\n message: `Could not inventory the docs repo: ${error instanceof Error ? error.message : String(error)}`,\n fix: `Check that ${docsDir}/services and ${docsDir}/features are readable directories.`,\n });\n }\n }\n\n const configuredService = inspected.config?.service ?? null;\n const currentService: DoctorReport[\"currentService\"] = configuredService === null\n ? { configured: null, status: \"unbound\" }\n : services.some((service) => service.id === configuredService)\n ? { configured: configuredService, status: \"matched\" }\n : { configured: configuredService, status: \"unknown\" };\n // Standing INSIDE the docs repo, an absent service binding is the correct\n // state, not a gap: the docs repo is the fleet, it is not any one service, and\n // the fix this finding prints — `loam init --service <id>` — would bind the\n // shared repo to one of the services it holds, which is not a thing loam has\n // a meaning for. doctor already knew where it was standing (it prints the\n // fleet count two lines up) and reported the warning anyway, so the first\n // preflight anyone ran in a freshly created docs repo came back yellow with\n // advice that made it worse. The docs repo's own loam.json is the one that\n // resolves `docsDir` to the directory holding it — that identity, not a\n // filename, is the test.\n const inDocsRepo = configRoot !== undefined && docsDir !== null\n && resolve(configRoot) === resolve(docsDir);\n if (currentService.status === \"unbound\" && !inDocsRepo) {\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unbound\",\n message: \"loam.json has no service binding; service-repo checks need `service`.\",\n fix: `Run \\`loam init --docs ${inspected.config?.docsDirAsWritten ?? \"<dir>\"} --service <id>\\` here.`,\n });\n } else if (currentService.status === \"unknown\" && inventoried) {\n // Only when there was an inventory to be absent from. This finding is a\n // claim about a fleet that was read; with no docsDir, an unreadable one, or\n // an enumeration that threw, `services` is empty for a reason that has\n // nothing to do with `service`, and doctor pointed the reader at the one\n // config field that was fine while the blocker it had already filed named\n // the real one. `currentService.status` still reports `unknown` — the\n // envelope describes what loam could and could not match, and an unread\n // fleet matches nothing.\n findings.push({\n severity: \"warning\",\n code: \"doctor.service-unknown\",\n message: `Configured service '${configuredService}' is not present under services/.`,\n // `--service <id>`, not a positional: `loam adopt` takes no argument, so\n // the spelling this finding used to print was refused by commander with\n // \"too many arguments\" — as the FIRST instruction a freshly bound service\n // repo ever receives. test/agent-commands-runnable.test.ts parses every\n // `loam …` loam prints against the real program so it cannot recur.\n fix: `Run \\`loam adopt --service ${configuredService}\\` to onboard it, or fix \"service\" in ${path}.`,\n });\n }\n\n // The slash commands (and everything else `init` lays down for a tool) belong\n // to the repo `init` ran in — the directory holding loam.json, which is not\n // the cwd now that config discovery walks upward.\n const agents = await inspectAgentSurface(dirname(path), inspected.config, docsDir, findings);\n\n return {\n healthy: !findings.some((finding) => finding.severity === \"blocker\"),\n runtime: {\n package: \"@ybotok/loam\",\n version: LOAM_VERSION,\n node: process.version,\n platform: process.platform,\n arch: process.arch,\n },\n config: { path, status: inspected.status, error: inspected.error },\n docs: {\n path: docsDir,\n exists,\n readable,\n writable,\n servicesDir,\n landscape,\n },\n counts: { services: services.length, activeFeatures: activeFeatures.length },\n currentService,\n agents,\n writePath,\n findings,\n };\n}\n"]}
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { readFile } from "node:fs/promises";
21
21
  import { resolve } from "node:path";
22
+ import { readAsyncapi } from "./asyncapi.js";
22
23
  import { decodeDocument } from "./document-bytes.js";
23
24
  import { loadFile } from "./likec4.js";
24
25
  import { readOpenapi, } from "./openapi.js";
@@ -97,6 +98,7 @@ export class FleetContext {
97
98
  texts = new Map();
98
99
  requirements = new Map();
99
100
  openapis = new Map();
101
+ asyncapis = new Map();
100
102
  likec4 = new Map();
101
103
  counts = {
102
104
  serviceEnumerations: 0,
@@ -105,6 +107,7 @@ export class FleetContext {
105
107
  textReads: 0,
106
108
  requirementParses: 0,
107
109
  openapiParses: 0,
110
+ asyncapiParses: 0,
108
111
  likec4Loads: 0,
109
112
  };
110
113
  /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */
@@ -194,6 +197,16 @@ export class FleetContext {
194
197
  }
195
198
  return pending;
196
199
  }
200
+ readAsyncapi(path) {
201
+ const k = key(path);
202
+ let pending = this.asyncapis.get(k);
203
+ if (pending === undefined) {
204
+ this.counts.asyncapiParses += 1;
205
+ pending = readAsyncapi(path);
206
+ this.asyncapis.set(k, pending);
207
+ }
208
+ return pending;
209
+ }
197
210
  async operations(path) {
198
211
  return (await this.readOpenapi(path)).ops;
199
212
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. The\n * decode itself lives in `document-bytes.ts`, a leaf: the parsers that read a\n * single file without an index owe the same rule, and must not have to import\n * this module — and the fleet it pulls in behind it — to obey it.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { decodeDocument } from \"./document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./likec4.js\";\nimport type { Finding } from \"./report.js\";\nimport {\n readOpenapi,\n type OpenapiDoc,\n type Operation,\n} from \"./openapi.js\";\nimport {\n featureSpecServices,\n listFeatures,\n listServices,\n type FeatureEntry,\n type ServiceEntry,\n} from \"./repo.js\";\nimport { parseRequirements, type Requirement } from \"./spec.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n likec4Loads: number;\n}\n\nfunction key(path: string): string {\n return resolve(path);\n}\n\n/* ------------------------------------------------------------------ */\n/* What a read must never swallow */\n/* ------------------------------------------------------------------ */\n\n/**\n * The three-way merge left its markers in the file — 1-based line numbers.\n *\n * The rule is `loam doctor`'s, verbatim, and it lives here now because doctor\n * checked exactly one file (`architecture/landscape.likec4`) from a command\n * that gates nothing. The same markers in a living `services/<svc>/spec.md`\n * parse as prose: `loam validate --all` reported `valid: true`, and the next\n * `loam archive` rewrote the requirements section and dropped the `=======`\n * line with it, turning a conflict anyone can see into a document nobody can\n * tell is wrong. For a shared docs repo where a fleet lands through PRs this is\n * the DEFAULT failure, not an edge.\n */\nconst CONFLICT_MARKERS = [\"<<<<<<<\", \"=======\", \">>>>>>>\"];\n\nexport function conflictMarkerLines(source: string): number[] {\n const out: number[] = [];\n source.split(/\\r?\\n/).forEach((line, i) => {\n if (CONFLICT_MARKERS.some((m) => line.startsWith(m))) out.push(i + 1);\n });\n return out;\n}\n\n/** One sentence for the breach, wherever it is found — doctor, validate, either axis. */\nfunction conflictMessage(label: string, lines: number[]): string {\n return (\n `${label} still contains git conflict markers (line${lines.length === 1 ? \"\" : \"s\"} ${lines.join(\", \")}) — ` +\n `both sides of somebody's merge are in the file, so nothing it says is anyone's text. ` +\n `Resolve the conflict before anything else: loam rewrites this document on archive, and a rewrite deletes ` +\n `whichever marker lines fall inside the section it owns — a visible conflict becomes a silently malformed file.`\n );\n}\n\n/**\n * The conflict-marker finding for a requirement document (living spec.md /\n * arch.spec.md, or a feature's delta of one). Null when the text is clean.\n *\n * An error because the document is not valid — nobody wrote what it now says —\n * and because the merge that would consume it is unsafe. It does not carry\n * `gates`: that field is coherence's (issue.ts), and the archive gate has to\n * ask this question itself.\n */\nexport function documentConflictFinding(label: string, subject: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"spec.merge-conflict\",\n subject,\n message: conflictMessage(label, lines),\n };\n}\n\n/** The same breach in the fleet map, where it costs every cross-service check at once. */\nexport function landscapeConflictFinding(label: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"landscape.merge-conflict\",\n message: conflictMessage(label, lines),\n };\n}\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<string[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n likec4Loads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n listServices(docsDir: string): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listServices(docsDir);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: string, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: string): Promise<string[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n}\n"]}
1
+ {"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../../src/core/fleet-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Request-scoped read index for commands that walk a whole loam repository.\n *\n * The filesystem remains the source of truth. This class deliberately has no\n * singleton/global instance: a command creates one after loading loam.json and\n * drops it when that invocation finishes. Within that lifetime identical\n * reads share their in-flight Promise, which removes both duplicate I/O and\n * duplicate parsing without making a later CLI invocation observe stale data.\n *\n * Because every markdown artifact is read HERE, this is also where the two\n * failures a read must never swallow are answered: bytes that are not UTF-8\n * (which `readFile(path, \"utf8\")` silently turns into U+FFFD, so a UTF-16\n * spec.md parses as zero requirements and grades green), and git conflict\n * markers left in a living document (which parse as prose, and which the next\n * `loam archive` rewrites away). One place, so no reader can forget. The\n * decode itself lives in `document-bytes.ts`, a leaf: the parsers that read a\n * single file without an index owe the same rule, and must not have to import\n * this module — and the fleet it pulls in behind it — to obey it.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readAsyncapi, type AsyncapiDoc } from \"./asyncapi.js\";\nimport { decodeDocument } from \"./document-bytes.js\";\nimport { loadFile, type LoadedDoc } from \"./likec4.js\";\nimport type { Finding } from \"./report.js\";\nimport {\n readOpenapi,\n type OpenapiDoc,\n type Operation,\n} from \"./openapi.js\";\nimport {\n featureSpecServices,\n listFeatures,\n listServices,\n type FeatureEntry,\n type ServiceEntry,\n} from \"./repo.js\";\nimport { parseRequirements, type Requirement } from \"./spec.js\";\n\nexport interface FleetContextStats {\n serviceEnumerations: number;\n featureEnumerations: number;\n featureServiceEnumerations: number;\n textReads: number;\n requirementParses: number;\n openapiParses: number;\n /**\n * Reads of a service's `asyncapi.yaml`. Counted separately because this axis\n * is the one that makes a per-service run read the whole fleet: \"does anybody\n * publish this message\" has no local answer, so `spine.message-unproduced`\n * walks every service's contract. The memo is what keeps that one walk per\n * command instead of one per consuming edge.\n */\n asyncapiParses: number;\n likec4Loads: number;\n}\n\nfunction key(path: string): string {\n return resolve(path);\n}\n\n/* ------------------------------------------------------------------ */\n/* What a read must never swallow */\n/* ------------------------------------------------------------------ */\n\n/**\n * The three-way merge left its markers in the file — 1-based line numbers.\n *\n * The rule is `loam doctor`'s, verbatim, and it lives here now because doctor\n * checked exactly one file (`architecture/landscape.likec4`) from a command\n * that gates nothing. The same markers in a living `services/<svc>/spec.md`\n * parse as prose: `loam validate --all` reported `valid: true`, and the next\n * `loam archive` rewrote the requirements section and dropped the `=======`\n * line with it, turning a conflict anyone can see into a document nobody can\n * tell is wrong. For a shared docs repo where a fleet lands through PRs this is\n * the DEFAULT failure, not an edge.\n */\nconst CONFLICT_MARKERS = [\"<<<<<<<\", \"=======\", \">>>>>>>\"];\n\nexport function conflictMarkerLines(source: string): number[] {\n const out: number[] = [];\n source.split(/\\r?\\n/).forEach((line, i) => {\n if (CONFLICT_MARKERS.some((m) => line.startsWith(m))) out.push(i + 1);\n });\n return out;\n}\n\n/** One sentence for the breach, wherever it is found — doctor, validate, either axis. */\nfunction conflictMessage(label: string, lines: number[]): string {\n return (\n `${label} still contains git conflict markers (line${lines.length === 1 ? \"\" : \"s\"} ${lines.join(\", \")}) — ` +\n `both sides of somebody's merge are in the file, so nothing it says is anyone's text. ` +\n `Resolve the conflict before anything else: loam rewrites this document on archive, and a rewrite deletes ` +\n `whichever marker lines fall inside the section it owns — a visible conflict becomes a silently malformed file.`\n );\n}\n\n/**\n * The conflict-marker finding for a requirement document (living spec.md /\n * arch.spec.md, or a feature's delta of one). Null when the text is clean.\n *\n * An error because the document is not valid — nobody wrote what it now says —\n * and because the merge that would consume it is unsafe. It does not carry\n * `gates`: that field is coherence's (issue.ts), and the archive gate has to\n * ask this question itself.\n */\nexport function documentConflictFinding(label: string, subject: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"spec.merge-conflict\",\n subject,\n message: conflictMessage(label, lines),\n };\n}\n\n/** The same breach in the fleet map, where it costs every cross-service check at once. */\nexport function landscapeConflictFinding(label: string, text: string): Finding | null {\n const lines = conflictMarkerLines(text);\n if (lines.length === 0) return null;\n return {\n severity: \"error\",\n code: \"landscape.merge-conflict\",\n message: conflictMessage(label, lines),\n };\n}\n\n/** One coherent filesystem snapshot for one command invocation. */\nexport class FleetContext {\n private readonly services = new Map<string, Promise<ServiceEntry[]>>();\n private readonly features = new Map<string, Promise<FeatureEntry[]>>();\n private readonly featureServices = new Map<string, Promise<string[]>>();\n private readonly texts = new Map<string, Promise<string>>();\n private readonly requirements = new Map<string, Promise<Requirement[]>>();\n private readonly openapis = new Map<string, Promise<OpenapiDoc>>();\n private readonly asyncapis = new Map<string, Promise<AsyncapiDoc>>();\n private readonly likec4 = new Map<string, Promise<LoadedDoc>>();\n\n private readonly counts: FleetContextStats = {\n serviceEnumerations: 0,\n featureEnumerations: 0,\n featureServiceEnumerations: 0,\n textReads: 0,\n requirementParses: 0,\n openapiParses: 0,\n asyncapiParses: 0,\n likec4Loads: 0,\n };\n\n /** A copy suitable for diagnostics/tests; callers cannot mutate the counters. */\n stats(): Readonly<FleetContextStats> {\n return { ...this.counts };\n }\n\n listServices(docsDir: string): Promise<ServiceEntry[]> {\n const k = key(docsDir);\n let pending = this.services.get(k);\n if (pending === undefined) {\n this.counts.serviceEnumerations += 1;\n pending = listServices(docsDir);\n this.services.set(k, pending);\n }\n return pending;\n }\n\n listFeatures(docsDir: string, opts: { includeArchived?: boolean } = {}): Promise<FeatureEntry[]> {\n const k = `${key(docsDir)}\\0${opts.includeArchived === true ? \"all\" : \"active\"}`;\n let pending = this.features.get(k);\n if (pending === undefined) {\n this.counts.featureEnumerations += 1;\n pending = listFeatures(docsDir, opts).then((entries) => {\n // listFeatures has already enumerated every feature's specs/ directory;\n // seed the narrower lookup so validation/coherence do not read it again.\n for (const entry of entries) {\n const featureKey = key(entry.dir);\n if (!this.featureServices.has(featureKey)) {\n this.featureServices.set(featureKey, Promise.resolve(entry.services));\n }\n }\n return entries;\n });\n this.features.set(k, pending);\n }\n return pending;\n }\n\n featureSpecServices(featureDir: string): Promise<string[]> {\n const k = key(featureDir);\n let pending = this.featureServices.get(k);\n if (pending === undefined) {\n this.counts.featureServiceEnumerations += 1;\n pending = featureSpecServices(featureDir);\n this.featureServices.set(k, pending);\n }\n return pending;\n }\n\n /**\n * A markdown artifact as text. Bytes in, decoded once, and refused rather\n * than degraded when they are not UTF-8 — see NotUtf8DocumentError.\n */\n readText(path: string): Promise<string> {\n const k = key(path);\n let pending = this.texts.get(k);\n if (pending === undefined) {\n this.counts.textReads += 1;\n pending = readFile(path).then((bytes) => decodeDocument(bytes, path));\n this.texts.set(k, pending);\n }\n return pending;\n }\n\n /**\n * Git conflict markers in an already-read document, by line. Free for any\n * caller that was going to read the file anyway — which is every caller,\n * which is the point: the marker check used to cost a whole extra read of one\n * hand-picked file, so it was done for one hand-picked file.\n */\n async conflictMarkers(path: string): Promise<number[]> {\n return conflictMarkerLines(await this.readText(path));\n }\n\n readRequirements(path: string): Promise<Requirement[]> {\n const k = key(path);\n let pending = this.requirements.get(k);\n if (pending === undefined) {\n this.counts.requirementParses += 1;\n pending = this.readText(path).then(parseRequirements);\n this.requirements.set(k, pending);\n }\n return pending;\n }\n\n readOpenapi(path: string): Promise<OpenapiDoc> {\n const k = key(path);\n let pending = this.openapis.get(k);\n if (pending === undefined) {\n this.counts.openapiParses += 1;\n pending = readOpenapi(path);\n this.openapis.set(k, pending);\n }\n return pending;\n }\n\n readAsyncapi(path: string): Promise<AsyncapiDoc> {\n const k = key(path);\n let pending = this.asyncapis.get(k);\n if (pending === undefined) {\n this.counts.asyncapiParses += 1;\n pending = readAsyncapi(path);\n this.asyncapis.set(k, pending);\n }\n return pending;\n }\n\n async operations(path: string): Promise<Operation[]> {\n return (await this.readOpenapi(path)).ops;\n }\n\n async operationIds(path: string): Promise<string[]> {\n return (await this.readOpenapi(path)).ops.map((op) => op.id);\n }\n\n /*\n * Deliberately no `serviceOperationIds` here. The living-plus-feature union is\n * a RULE — every removal applied before any upsert — not a read, and a second\n * copy of a rule is a second chance to spell it differently. This one did: it\n * interleaved removals with upserts, so a relocated operation answered\n * \"defined\" through `archive` (no context) and \"gone\" through `validate` and\n * `status` (context), or the reverse, depending on the order the feature's\n * YAML happened to spell the two slots in. `core/openapi.ts` owns the rule and\n * threads a context through the reads underneath it, which is the only part\n * this class was ever needed for.\n */\n\n loadLikeC4(path: string): Promise<LoadedDoc> {\n const k = key(path);\n let pending = this.likec4.get(k);\n if (pending === undefined) {\n this.counts.likec4Loads += 1;\n pending = loadFile(path);\n this.likec4.set(k, pending);\n }\n return pending;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAsF3C,MAAM,UAAU,QAAQ,CAAC,OAAgC;IACvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,qBAAqB,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACzG,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,GAAW;IACnD,OAAO,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,aAAa,CAC3B,IAAe,EACf,OAAe,EACf,OAAO,GAA4B,EAAE;IAErC,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,EAAE,eAAe,EAAE,qBAAqB,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAC3F,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,IAAa,EAAE,IAAe,EAAE,OAAe;IAClE,IAAI,IAAI,EAAE,CAAC;QACT,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,mEAAmE;AACnE,MAAM,iBAAiB,GAAG,yDAAyD,CAAC;AAEpF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,yDAAyD,CAAC;AAE5F;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;QAC7B,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,yFAAyF,CAC1F,CAAC;QACF,OAAO;IACT,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * The `--json` envelope — loam's machine contract.\n *\n * Every JSON response carries `ok`. Success spreads the payload alongside it;\n * failure carries a stable `error.code` a caller can branch on without parsing\n * prose. Errors go to stdout too: a consumer reads one stream and always gets\n * valid JSON, whether the command succeeded or not. The exit code still tells\n * the shell what happened.\n *\n * Key casing: camelCase, with one deliberate exception — a key that mirrors a\n * frontmatter field verbatim keeps that field's snake_case spelling\n * (`last_verified`, `sources_digest` in vouch's payload), so the envelope and\n * the document it describes spell the same fact the same way. snake_case only\n * where a key mirrors a frontmatter field verbatim; camelCase everywhere else.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { configPath } from \"./config.js\";\n\n/**\n * Version of the top-level JSON envelope, independent of the CLI/package\n * version. Additive payload fields do not require a bump; incompatible changes\n * to `ok`, `error`, or their semantics do.\n */\nexport const JSON_CONTRACT_VERSION = \"1.0\";\n\n/**\n * Stable failure codes. Prose may change; these may not.\n *\n * The `sources-*` pair mirrors the `sources.*` finding codes on purpose: the\n * same breach is recognisable whether it arrives as a refusal from `vouch` or\n * as a finding from `validate`.\n *\n * The `feature-active` / `snapshot-*` group is `unarchive` refusing to guess:\n * each names a different reason the undo is not one, and a caller has to tell\n * them apart to know whether re-running could ever work. Its commit failures\n * split the same way archive's do: `restore-failed` means nothing was restored\n * or everything was rolled back — the living docs are unchanged and re-running\n * can work; `rollback-incomplete` (shared with archive, same meaning) means\n * some files could not be put back, and the message lists them.\n *\n * The `answers-*` group is `loam verify --record` refusing an answer set that\n * does not answer the current checklist. They are separated for the same reason:\n * an unreadable file is a bug in whatever wrote it, a mismatch means the feature\n * moved and the claims have to be answered again, and an unevidenced\n * confirmation is the one an agent can fix on the spot.\n *\n * The archive group is `loam archive` refusing or failing, and each code is a\n * different answer to \"what do I fix, and can I trust the repo?\": `not-coherent`\n * is the gate — errors in the FEATURE, fix them or override with `--approve`;\n * `living-outside-requirements` is the one refusal `--approve` does not move —\n * the LIVING spec holds requirements outside `## Requirements`, and the merge\n * would duplicate them, so the fix is in the living docs; `archive-exists` is a\n * destination collision under `features/archive/`; `merge-failed` is a merge\n * that could not be computed or was rolled back — either way the living docs\n * are unchanged; `rollback-incomplete` is the one that demands a human: the\n * merge failed AND some files could not be restored.\n *\n * `config-invalid` is distinct from `no-config` because the fixes point in\n * opposite directions: a missing config wants `loam init`, a corrupt one wants\n * repair — and an agent that ran `init` on a corrupt file would silently\n * rewrite it.\n *\n * `internal` is the one code with no stable meaning: an unexpected throw. It\n * exists so a `--json` consumer still receives an envelope instead of a stack\n * trace on stdout's sibling stream.\n */\nexport type ErrorCode =\n | \"no-config\"\n | \"config-invalid\"\n | \"unknown-target\"\n | \"invalid-option\"\n | \"already-exists\"\n | \"sources-absent\"\n | \"sources-path-missing\"\n | \"not-coherent\"\n | \"living-outside-requirements\"\n | \"archive-exists\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n | \"feature-active\"\n | \"snapshot-missing\"\n | \"snapshot-stale\"\n /** A snapshot pre-image whose bytes no longer match the digest archive recorded for them: `unarchive` will not restore text nobody wrote. */\n | \"snapshot-corrupt\"\n /** A `loam archive`/`unarchive` was killed mid-commit: the living docs are half-written, and this run refuses rather than writing over the evidence. */\n | \"commit-interrupted\"\n | \"restore-failed\"\n | \"answers-unreadable\"\n | \"answers-mismatch\"\n | \"answers-unevidenced\"\n | \"service-mismatch\"\n | \"unknown-service\"\n | \"repository-unavailable\"\n /** `loam verify --record` without `--service` over a federated (schema 2) record: it would erase other repositories' attestations. */\n | \"record-federated\"\n /** A `verification.yaml` that exists but cannot be read as a record — never overwritten, never reported as absent. */\n | \"record-unreadable\"\n /** `loam gherkin <FEAT>` refusing to overwrite a `.feature` file owned by another feature still in flight. */\n | \"gherkin-conflict\"\n /** `loam vouch` found the spec changed under it between reading and stamping — another vouch or an edit landed first, and nothing was written. */\n | \"vouch-raced\"\n /** `docsDir` in loam.json points at nothing: the docs repo was never cloned, or the path is wrong. A read command refuses rather than reporting an empty fleet. */\n | \"docs-missing\"\n /** `docsDir` is a directory but has no `services/`: it is some other directory, most often the service repo itself after a typo. */\n | \"services-missing\"\n /** Another `loam archive`/`unarchive` holds the docs repo's advisory lock: nothing was read or written, and re-running once it finishes works. */\n | \"docs-busy\"\n | \"internal\";\n\nexport function emitJson(payload: Record<string, unknown>): void {\n console.log(JSON.stringify({ contractVersion: JSON_CONTRACT_VERSION, ok: true, ...payload }, null, 2));\n}\n\n/** Paths in the contract are repo-relative, with forward slashes: diffable across machines. */\nexport function repoPath(docsDir: string, abs: string): string {\n return relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n}\n\n/** Emit a failure envelope and set the exit code. Returns false, to `return` from a caller. */\nexport function emitJsonError(\n code: ErrorCode,\n message: string,\n details: Record<string, unknown> = {},\n): false {\n console.log(\n JSON.stringify(\n { contractVersion: JSON_CONTRACT_VERSION, ok: false, error: { code, message }, ...details },\n null,\n 2,\n ),\n );\n process.exitCode = 1;\n return false;\n}\n\n/**\n * Report a failure in whichever mode the caller is in. Text mode goes to\n * stderr as it always has; JSON mode goes into the envelope.\n */\nexport function fail(json: boolean, code: ErrorCode, message: string): void {\n if (json) {\n emitJsonError(code, message);\n return;\n }\n console.error(message);\n process.exitCode = 1;\n}\n\n/** The message every command prints when there is no loam.json. */\nconst NO_CONFIG_MESSAGE = \"No loam.json found. Run `loam init --docs <dir>` first.\";\n\n/**\n * The message the service-scoped commands print when neither `--service` nor\n * loam.json names one. Four commands ask the same question and owe the same two\n * answers, and a fifth would have been written by hand. `loam delta` keeps its\n * own longer sentence on purpose — it can say which feature was being read.\n */\nexport const NO_SERVICE_MESSAGE = \"No service. Pass --service <id> or set it in loam.json.\";\n\n/**\n * Report \"no config\" in whichever mode the caller is in — distinguishing a\n * config that is absent from one that exists but would not load, because the\n * two point at opposite fixes: a missing config wants `loam init`, a corrupt\n * one wants repair, and `init` would silently rewrite the corrupt file.\n * Callers reach here only after `loadConfig()` returned null, so the file\n * existing is proof it failed to load.\n */\nexport function reportNoConfig(json: boolean): void {\n if (existsSync(configPath())) {\n fail(\n json,\n \"config-invalid\",\n \"loam.json exists but could not be loaded. Fix it (or delete it and re-run `loam init`).\",\n );\n return;\n }\n fail(json, \"no-config\", NO_CONFIG_MESSAGE);\n}\n"]}
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AA4F3C,MAAM,UAAU,QAAQ,CAAC,OAAgC;IACvD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,eAAe,EAAE,qBAAqB,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACzG,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,GAAW;IACnD,OAAO,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,aAAa,CAC3B,IAAe,EACf,OAAe,EACf,OAAO,GAA4B,EAAE;IAErC,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,EAAE,eAAe,EAAE,qBAAqB,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAC3F,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,IAAa,EAAE,IAAe,EAAE,OAAe;IAClE,IAAI,IAAI,EAAE,CAAC;QACT,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,mEAAmE;AACnE,MAAM,iBAAiB,GAAG,yDAAyD,CAAC;AAEpF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,yDAAyD,CAAC;AAE5F;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC;QAC7B,IAAI,CACF,IAAI,EACJ,gBAAgB,EAChB,yFAAyF,CAC1F,CAAC;QACF,OAAO;IACT,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["/**\n * The `--json` envelope — loam's machine contract.\n *\n * Every JSON response carries `ok`. Success spreads the payload alongside it;\n * failure carries a stable `error.code` a caller can branch on without parsing\n * prose. Errors go to stdout too: a consumer reads one stream and always gets\n * valid JSON, whether the command succeeded or not. The exit code still tells\n * the shell what happened.\n *\n * Key casing: camelCase, with one deliberate exception — a key that mirrors a\n * frontmatter field verbatim keeps that field's snake_case spelling\n * (`last_verified`, `sources_digest` in vouch's payload), so the envelope and\n * the document it describes spell the same fact the same way. snake_case only\n * where a key mirrors a frontmatter field verbatim; camelCase everywhere else.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { configPath } from \"./config.js\";\n\n/**\n * Version of the top-level JSON envelope, independent of the CLI/package\n * version. Additive payload fields do not require a bump; incompatible changes\n * to `ok`, `error`, or their semantics do.\n */\nexport const JSON_CONTRACT_VERSION = \"1.0\";\n\n/**\n * Stable failure codes. Prose may change; these may not.\n *\n * The `sources-*` pair mirrors the `sources.*` finding codes on purpose: the\n * same breach is recognisable whether it arrives as a refusal from `vouch` or\n * as a finding from `validate`.\n *\n * The `feature-active` / `snapshot-*` group is `unarchive` refusing to guess:\n * each names a different reason the undo is not one, and a caller has to tell\n * them apart to know whether re-running could ever work. Its commit failures\n * split the same way archive's do: `restore-failed` means nothing was restored\n * or everything was rolled back — the living docs are unchanged and re-running\n * can work; `rollback-incomplete` (shared with archive, same meaning) means\n * some files could not be put back, and the message lists them.\n *\n * The `answers-*` group is `loam verify --record` refusing an answer set that\n * does not answer the current checklist. They are separated for the same reason:\n * an unreadable file is a bug in whatever wrote it, a mismatch means the feature\n * moved and the claims have to be answered again, and an unevidenced\n * confirmation is the one an agent can fix on the spot.\n *\n * The archive group is `loam archive` refusing or failing, and each code is a\n * different answer to \"what do I fix, and can I trust the repo?\": `not-coherent`\n * is the gate — errors in the FEATURE, fix them or override with `--approve`;\n * `living-outside-requirements` is the one refusal `--approve` does not move —\n * the LIVING spec holds requirements outside `## Requirements`, and the merge\n * would duplicate them, so the fix is in the living docs; `archive-exists` is a\n * destination collision under `features/archive/`; `merge-failed` is a merge\n * that could not be computed or was rolled back — either way the living docs\n * are unchanged; `rollback-incomplete` is the one that demands a human: the\n * merge failed AND some files could not be restored.\n *\n * `config-invalid` is distinct from `no-config` because the fixes point in\n * opposite directions: a missing config wants `loam init`, a corrupt one wants\n * repair — and an agent that ran `init` on a corrupt file would silently\n * rewrite it.\n *\n * `internal` is the one code with no stable meaning: an unexpected throw. It\n * exists so a `--json` consumer still receives an envelope instead of a stack\n * trace on stdout's sibling stream.\n */\nexport type ErrorCode =\n | \"no-config\"\n | \"config-invalid\"\n | \"unknown-target\"\n | \"invalid-option\"\n | \"already-exists\"\n | \"sources-absent\"\n | \"sources-path-missing\"\n | \"not-coherent\"\n | \"living-outside-requirements\"\n | \"archive-exists\"\n | \"merge-failed\"\n | \"rollback-incomplete\"\n | \"feature-active\"\n | \"snapshot-missing\"\n | \"snapshot-stale\"\n /** A snapshot pre-image whose bytes no longer match the digest archive recorded for them: `unarchive` will not restore text nobody wrote. */\n | \"snapshot-corrupt\"\n /** A `loam archive`/`unarchive` was killed mid-commit: the living docs are half-written, and this run refuses rather than writing over the evidence. */\n | \"commit-interrupted\"\n | \"restore-failed\"\n | \"answers-unreadable\"\n | \"answers-mismatch\"\n | \"answers-unevidenced\"\n | \"service-mismatch\"\n | \"unknown-service\"\n | \"repository-unavailable\"\n /** `loam verify --record` without `--service` over a federated (schema 2) record: it would erase other repositories' attestations. */\n | \"record-federated\"\n /** A `verification.yaml` that exists but cannot be read as a record — never overwritten, never reported as absent. */\n | \"record-unreadable\"\n /** `loam gherkin <FEAT>` refusing to overwrite a `.feature` file owned by another feature still in flight. */\n | \"gherkin-conflict\"\n /** `loam vouch` found the spec changed under it between reading and stamping — another vouch or an edit landed first, and nothing was written. */\n | \"vouch-raced\"\n /** `loam vouch` with nothing on the other end of stdin and no `--yes`: the stamp is a person's claim, and nobody was asked. */\n | \"vouch-unattended\"\n /** `loam vouch` could not learn who is vouching — git names no `user.email` here, so the stamp would record a claim with nobody behind it. */\n | \"vouch-unattributable\"\n /** `loam vouch` was asked to confirm and the answer was no. Nothing was stamped; this is a successful refusal, not a failure. */\n | \"vouch-declined\"\n /** `docsDir` in loam.json points at nothing: the docs repo was never cloned, or the path is wrong. A read command refuses rather than reporting an empty fleet. */\n | \"docs-missing\"\n /** `docsDir` is a directory but has no `services/`: it is some other directory, most often the service repo itself after a typo. */\n | \"services-missing\"\n /** Another `loam archive`/`unarchive` holds the docs repo's advisory lock: nothing was read or written, and re-running once it finishes works. */\n | \"docs-busy\"\n | \"internal\";\n\nexport function emitJson(payload: Record<string, unknown>): void {\n console.log(JSON.stringify({ contractVersion: JSON_CONTRACT_VERSION, ok: true, ...payload }, null, 2));\n}\n\n/** Paths in the contract are repo-relative, with forward slashes: diffable across machines. */\nexport function repoPath(docsDir: string, abs: string): string {\n return relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n}\n\n/** Emit a failure envelope and set the exit code. Returns false, to `return` from a caller. */\nexport function emitJsonError(\n code: ErrorCode,\n message: string,\n details: Record<string, unknown> = {},\n): false {\n console.log(\n JSON.stringify(\n { contractVersion: JSON_CONTRACT_VERSION, ok: false, error: { code, message }, ...details },\n null,\n 2,\n ),\n );\n process.exitCode = 1;\n return false;\n}\n\n/**\n * Report a failure in whichever mode the caller is in. Text mode goes to\n * stderr as it always has; JSON mode goes into the envelope.\n */\nexport function fail(json: boolean, code: ErrorCode, message: string): void {\n if (json) {\n emitJsonError(code, message);\n return;\n }\n console.error(message);\n process.exitCode = 1;\n}\n\n/** The message every command prints when there is no loam.json. */\nconst NO_CONFIG_MESSAGE = \"No loam.json found. Run `loam init --docs <dir>` first.\";\n\n/**\n * The message the service-scoped commands print when neither `--service` nor\n * loam.json names one. Four commands ask the same question and owe the same two\n * answers, and a fifth would have been written by hand. `loam delta` keeps its\n * own longer sentence on purpose — it can say which feature was being read.\n */\nexport const NO_SERVICE_MESSAGE = \"No service. Pass --service <id> or set it in loam.json.\";\n\n/**\n * Report \"no config\" in whichever mode the caller is in — distinguishing a\n * config that is absent from one that exists but would not load, because the\n * two point at opposite fixes: a missing config wants `loam init`, a corrupt\n * one wants repair, and `init` would silently rewrite the corrupt file.\n * Callers reach here only after `loadConfig()` returned null, so the file\n * existing is proof it failed to load.\n */\nexport function reportNoConfig(json: boolean): void {\n if (existsSync(configPath())) {\n fail(\n json,\n \"config-invalid\",\n \"loam.json exists but could not be loaded. Fix it (or delete it and re-run `loam init`).\",\n );\n return;\n }\n fail(json, \"no-config\", NO_CONFIG_MESSAGE);\n}\n"]}
@@ -110,6 +110,8 @@ export function flattenModel(model) {
110
110
  // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.
111
111
  title: r.title ?? undefined,
112
112
  op: metaKey(r.metadata, "op"),
113
+ publishes: metaKey(r.metadata, "publishes"),
114
+ consumes: metaKey(r.metadata, "consumes"),
113
115
  tags: [...(r.tags ?? [])],
114
116
  }));
115
117
  return { elements, relationships };
@@ -157,9 +159,13 @@ export async function loadSource(src) {
157
159
  }
158
160
  }
159
161
  /**
160
- * Read one string key out of a LikeC4 `metadata { ... }` block. Two keys carry
161
- * loam's spines: `op` on a relationship (the OpenAPI operationId it calls) and
162
- * `service` on an element (the services/<id> directory it stands for). Elements
162
+ * Read one string key out of a LikeC4 `metadata { ... }` block. Four keys carry
163
+ * loam's spines: `op` on a relationship (the OpenAPI operationId it calls),
164
+ * `publishes`/`consumes` on a relationship (the AsyncAPI message it produces or
165
+ * receives), and `service` on an element (the services/<id> directory it stands
166
+ * for). Every one of them is read by BOTH model readers — the parsed one here
167
+ * and the text scanner `scanModel` uses for archive's splice map — because a key
168
+ * only one of them sees is a key the merge silently drops. Elements
163
169
  * with no metadata come back as `{}`, so a missing key is indistinguishable from
164
170
  * a missing block — both mean "not bound".
165
171
  */
@@ -421,6 +427,8 @@ export function scanModel(text) {
421
427
  indent: indentOf(stmtStart),
422
428
  title,
423
429
  op: keyedLiteral("op", bodyFrom, bodyTo),
430
+ publishes: keyedLiteral("publishes", bodyFrom, bodyTo),
431
+ consumes: keyedLiteral("consumes", bodyFrom, bodyTo),
424
432
  tags,
425
433
  });
426
434
  }
@@ -468,6 +476,8 @@ export function scanModel(text) {
468
476
  target: resolve(r.tgtLit, r.parent),
469
477
  title: r.title,
470
478
  op: r.op,
479
+ publishes: r.publishes,
480
+ consumes: r.consumes,
471
481
  tags: r.tags,
472
482
  start: r.start,
473
483
  end: r.end,