@ybotok/loam 0.2.0-alpha.4 → 0.2.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/MIGRATING-from-OpenSpec.md +4 -1
  3. package/README.md +11 -10
  4. package/ROADMAP.md +70 -16
  5. package/SCHEMA.md +53 -6
  6. package/dist/commands/subsystem/sync.js +110 -19
  7. package/dist/commands/subsystem/sync.js.map +1 -1
  8. package/dist/commands/subsystem/txn/txn.js +19 -0
  9. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  10. package/dist/commands/validate/fleet/landscape.js +6 -0
  11. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  12. package/dist/commands/validate/fleet/views/projects.js +90 -0
  13. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  14. package/dist/core/agent/agents-md/command-map.js +3 -1
  15. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  16. package/dist/core/agent/agents-md/map/subsystem.js +10 -1
  17. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  18. package/dist/core/agent/workflows/adopt.js +13 -3
  19. package/dist/core/agent/workflows/adopt.js.map +1 -1
  20. package/dist/core/agent/workflows/check.js +1 -0
  21. package/dist/core/agent/workflows/check.js.map +1 -1
  22. package/dist/core/brief/checks.js +9 -0
  23. package/dist/core/brief/checks.js.map +1 -1
  24. package/dist/core/brief/targets.js +7 -4
  25. package/dist/core/brief/targets.js.map +1 -1
  26. package/dist/core/brief/unchecked.js +1 -1
  27. package/dist/core/brief/unchecked.js.map +1 -1
  28. package/dist/core/docs.js +11 -7
  29. package/dist/core/docs.js.map +1 -1
  30. package/dist/core/doctor/doctor.js +17 -6
  31. package/dist/core/doctor/doctor.js.map +1 -1
  32. package/dist/core/explain/families.js +1 -1
  33. package/dist/core/explain/families.js.map +1 -1
  34. package/dist/core/repo/paths.js +38 -0
  35. package/dist/core/repo/paths.js.map +1 -1
  36. package/dist/core/repo/tree/render/projects.js +124 -0
  37. package/dist/core/repo/tree/render/projects.js.map +1 -0
  38. package/examples/README.md +6 -1
  39. package/examples/docs/services/checkout-web/likec4.config.json +4 -0
  40. package/examples/docs/services/order-service/likec4.config.json +4 -0
  41. package/examples/docs/services/payment-service/likec4.config.json +4 -0
  42. package/examples/docs/services/payment-service/model.likec4 +4 -2
  43. package/examples/docs/services/platform/identity-service/likec4.config.json +4 -0
  44. package/examples/docs/services/platform/notification-service/likec4.config.json +4 -0
  45. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"families.js","sourceRoot":"","sources":["../../../src/core/explain/families.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The family registry: `loam explain`'s content for the four code families no\n * fix table anywhere grades — `doctor.*`, `next.*`, `diff.*` and `gate.*`.\n *\n * WHY THIS IS NOT THE DUPLICATION fix-tables.ts FORBIDS. That module's header\n * argues against a second hand-maintained copy of the /loam-check prose, and\n * the argument is right: the tables ship in the binary, test/codes-drift.test.ts\n * holds every emitted finding code against them, and a registry restating them\n * would be free to trail the bytes `loam instructions loam-check` prints. None\n * of that applies here. These 65 codes have no fix-table row in any workflow\n * body — /loam-check grades DOCUMENTS, and these four families grade a\n * repository's wiring (`doctor`), a recommendation (`next`), a change between\n * two refs (`diff`) and a deploy question (`gate`). There is nothing to trail:\n * this registry is not a second copy, it is the only copy. The precedent is the\n * sibling module refusals.ts, a hand-written map that is the sole source for\n * the `ErrorCode` meanings for exactly the same reason — their prose lives in\n * doc comments, which are erased, so there is no runtime constant to read.\n *\n * What that costs, stated rather than hidden: nothing mechanical proves an\n * entry below still matches the message its emitter prints. Every meaning and\n * every fix here was written FROM the emitter — `doctor` findings carry a `fix`\n * field of their own (core/doctor/), a `next` step carries a `statement` and a\n * literal `command` (core/status/), the `DiffCode` union carries a doc comment\n * per member (core/diff/semantic.ts) — and the wording is theirs, not a\n * paraphrase invented here. Two guards catch the two failures that CAN be\n * caught: test/explain.test.ts asserts no code below is also answered by a fix\n * table (the two sources must never be able to disagree), and\n * test/agent-commands-runnable.test.ts parses every backticked `loam …` in this\n * file against the real program, so a fix naming a command loam does not have —\n * or a flag it does not declare — fails at gate time. A two-token span is a\n * NAME rather than an invocation there and is only checked for the command\n * existing, so a fix that means an invocation must carry its arguments.\n *\n * `severityNote` is the EMITTER's own word, not a fix table's parenthetical:\n * `doctor` grades blocker/warning, `diff` and `gate` grade ok/warn/error, and a\n * `next` step is graded not at all — it is a recommendation, so its note is\n * `step`. Where a code's severity depends on what was found (`doctor.docs-locked`,\n * `gate.partners-unknown`) the note says so in prose, the way the tables' own\n * long notes do (`warn here; error at archive plan time`).\n *\n * `openspec.*` and `mapping.*` — 49 codes — are deliberately NOT here, and the\n * reason is in test/explain.test.ts beside their backlog entries: they are the\n * only codes in the vocabulary whose subject is not a loam document at all.\n */\n\n/**\n * One code's answer. Structurally a `FindingEntry` (lookup.ts) minus `scope`,\n * which the family supplies — declared here rather than imported so this module\n * imports nothing at all and the package's file-level graph stays a tree.\n *\n * Every entry below is written on ONE line, however long that line runs, and\n * that is a deliberate concession to a counted limit rather than a style: 65\n * codes at four lines each is 260 lines of punctuation before a word of prose,\n * which does not fit the 400-line file limit — and src/core/explain/ is at its\n * five-file cap, so there is no second file to put the overflow in. The fields\n * stay named for the same reason a tuple was refused: `severityNote`, `meaning`\n * and `fix` say which column is which without counting commas.\n */\nexport interface FamilyEntry {\n /** The emitter's own severity word, or the prose a conditional one needs. */\n readonly severityNote: string;\n /** What the code is about, in the words the emitter already prints. */\n readonly meaning: string;\n /** What to do — for `next.*`, that the step's own `command` IS the action. */\n readonly fix: string;\n}\n\n/** One family: the invocation that emits it, and its codes. */\ninterface Family {\n /** The scope label, spelled as the fix tables spell theirs — the invocation that grades it. */\n readonly scope: string;\n readonly codes: Readonly<Record<string, FamilyEntry>>;\n}\n\n/**\n * `loam doctor` — the preflight. Every finding here already carries a `fix`\n * field in doctor's own report (core/doctor/doctor.ts, config.ts, agents.ts,\n * residue.ts); these rows say the same thing without a repository in front of\n * them, which is the whole difference between a report and a lookup.\n */\nconst DOCTOR: Family = {\n scope: \"loam doctor\",\n codes: {\n \"doctor.config-missing\": { severityNote: \"blocker\", meaning: \"No loam.json at or above the working directory, so nothing says which docs repo this checkout belongs to — every other command refuses with `no-config` until there is one.\", fix: \"Run `loam init --docs <dir>` in this repository; add `--create` when the docs repo does not exist yet.\" },\n \"doctor.config-invalid\": { severityNote: \"blocker\", meaning: \"A loam.json is there and `parseConfig` — the same loader every command uses — refuses it; the finding carries the parser's own words. doctor deliberately has no second validator: the one it used to carry accepted a config the commands rejected, and reported a healthy repo in which nothing could run.\", fix: \"Repair the file the finding names, or delete it and re-run `loam init`.\" },\n \"doctor.docs-missing\": { severityNote: \"blocker\", meaning: \"`docsDir` resolves to something that is missing or is not a directory — the docs repo was never cloned, or the path is wrong.\", fix: \"Fix `docsDir` in the loam.json the finding names, clone the docs repo to that path, or run `loam init --docs <dir> --create`.\" },\n \"doctor.services-missing\": { severityNote: \"blocker\", meaning: \"`docsDir` is a directory and holds no `services/`, so it is some other directory — most often the service repo itself, after a typo in the pointer.\", fix: \"Point `docsDir` at the shared docs repo, or run `loam init --docs <dir> --create` to scaffold one. Do not scaffold a second source of truth beside the real one.\" },\n \"doctor.docs-absolute\": { severityNote: \"warning\", meaning: \"`docsDir` is stored as an absolute path and loam.json is committed, so the pointer names a directory that exists on exactly one machine: it works for whoever ran `loam init` and for nobody who clones the repo afterwards.\", fix: \"Rewrite `docsDir` as a path relative to the loam.json holding it, such as `../docs`.\" },\n \"doctor.docs-unreadable\": { severityNote: \"warning\", meaning: \"The docs repo exists and this process cannot read it, so every command that reads the fleet will fail or report an empty one.\", fix: \"Grant read access to the directory the finding names — check ownership and mode.\" },\n \"doctor.docs-readonly\": { severityNote: \"warning\", meaning: \"The docs repo is readable and not writable: read-only commands work and the writing ones do not.\", fix: \"Grant write access if you need `loam new`, `loam adopt` or `loam archive`. A read-only checkout is a legitimate state for a reviewer, which is why this is a warning.\" },\n \"doctor.landscape-missing\": { severityNote: \"warning\", meaning: \"architecture/landscape.likec4 is missing, and it is the one artifact every cross-service check reads — nothing about who calls whom can be checked without it.\", fix: \"Create it with one element per service and an edge per call: `loam init --create` writes the empty map, and `loam seed --from fleet.yaml` templates one from stated facts.\" },\n \"doctor.landscape-unreadable\": { severityNote: \"blocker\", meaning: \"The landscape file exists and the read itself failed — permissions or an I/O error, carried verbatim in the message.\", fix: \"Check permissions on the path the finding names.\" },\n \"doctor.landscape-merge-conflict\": { severityNote: \"blocker\", meaning: \"The landscape still contains merge conflict markers, named by line. Checked BEFORE the parser on purpose: both sides of a conflict can be syntactically valid LikeC4, so the file sometimes parses and is still two halves of two different maps.\", fix: \"Resolve the conflict keeping BOTH services' elements and edges — the usual cause is several services adopted into one map in the same week — then re-run `loam doctor`.\" },\n \"doctor.landscape-invalid\": { severityNote: \"blocker\", meaning: \"The landscape exists, carries no conflict markers, and does not parse; the message quotes the first parser error with its line and counts the rest.\", fix: \"Fix the file — every fleet-wide check is blind until it parses.\" },\n \"doctor.likec4-config-missing\": { severityNote: \"warning\", meaning: \"The docs repo has no likec4.config.json, so the tree is not a loadable LikeC4 workspace: every service model and feature delta declares its own `specification` block, and a renderer pointed at the repo root merges them and reports every declaration as a duplicate. No loam check reads this file — the repo is only unrenderable, which is why it is a warning.\", fix: \"Write the project file the finding prints; it scopes the root project to architecture/, so `npx likec4 start` in the docs repo renders the fleet map. A service model or a feature delta is rendered by pointing the renderer at its own directory instead.\" },\n \"doctor.docs-locked\": { severityNote: \"warning while a live process holds it; blocker when its holder is dead or it names none\", meaning: \"A `.loam-lock` is present. Held by a running process it is a fact about right now. Held by a process that no longer exists on this host — or unreadable, so no holder can be named at all — nothing is ever going to release it, and every writing command refuses with `docs-busy` forever.\", fix: \"Wait and re-run while the holder is alive; nothing is read or written while it is held. Delete the lock file the finding names when its holder is dead or unnameable, then run `loam doctor` again for an interrupted commit underneath it.\" },\n \"doctor.commit-unreadable\": { severityNote: \"blocker\", meaning: \"A `.loam-commit` journal is present and cannot be read: a commit was interrupted and the one record of which files it had already written is unreadable.\", fix: \"Hand this to a human — compare the root's files against version control before running any loam command that writes there. Every journaled writer refuses with `commit-interrupted` while it is present.\" },\n \"doctor.commit-interrupted\": { severityNote: \"blocker\", meaning: \"A journaled writer — archive, unarchive, rebase, vouch, new, gherkin, or a recording verify — was killed mid-commit. The message names the host, pid and time, and every file or directory move that may be half-done. This is the surface that used to report a healthy repo over half-merged living docs.\", fix: \"Re-run the command the finding names: it recovers first, under the lock, rolling the staged bytes forward, and refuses rather than guessing if a file has been edited since. An interrupted `loam unarchive` is FINISHED, never undone — the merged text it was replacing is written down nowhere.\" },\n \"doctor.staging-temps\": { severityNote: \"warning\", meaning: \"Orphaned `.loam-*.tmp` scratch files from a killed writer, never linked into place — so nothing reads them and nothing depends on them. A journal's own recorded temps are excluded, because those are the durable copy its roll-forward recovery renames in. Litter, not damage.\", fix: \"The next journaled writer through that root removes its own; delete any that remain. They cost disk and nothing else.\" },\n \"doctor.inventory-unreadable\": { severityNote: \"warning\", meaning: \"The config and the docs repo are both fine and enumerating `services/` or `features/` threw, so doctor's fleet counts — and its verdict on the service binding — are about a fleet nobody could read.\", fix: \"Check that services/ and features/ under the docs repo are readable directories.\" },\n \"doctor.service-unbound\": { severityNote: \"warning\", meaning: \"loam.json declares no `service`, so nothing binds this checkout to one governed boundary and the service-repo checks have nothing to run against — `sources.stale`, a recording `loam verify`, `loam gherkin`. Deliberately not emitted inside the docs repo itself, where having no binding is the correct state rather than a gap.\", fix: \"Run `loam init --docs <dir> --service <id>` here.\" },\n \"doctor.service-unknown\": { severityNote: \"warning\", meaning: \"loam.json names a `service` the fleet has no services/<id>/ for — and the fleet WAS read, so this is a claim about a real inventory rather than one nobody could take.\", fix: \"Run `loam adopt --service <id>` to onboard it, or fix `service` in loam.json.\" },\n \"doctor.agent-files-missing\": { severityNote: \"warning\", meaning: \"Some of the command and skill files this loam lays down for the repo's recorded tools and profile are not here: the repo was initialized by an older loam, or they were deleted. An out-of-date command set costs entry points, never a refusal, which is why it is a warning.\", fix: \"Re-run `loam init --docs <dir> --tools <ids>` here. It creates absent files, refreshes only pointers whose current bytes still match their recorded digest, preserves every customized file, and keeps this repo's service binding.\" },\n \"doctor.agent-files-stale\": { severityNote: \"warning\", meaning: \"Command or skill files that ARE here carry no loam stamp, or one older than this binary, so the protocol and code tables they instruct an agent with may describe a loam that no longer exists. Staleness reads only the stamp; safe refresh is a separate digest check performed by init.\", fix: \"Re-run `loam init` to refresh a pointer only when its recorded digest still matches. Review a customized file against the current body yourself and update its stamp, or delete it so init can recreate it; your edits always outrank the template.\" },\n },\n};\n\n/**\n * `loam status` — both forms print `next[]`, and each entry carries a `code` to\n * branch on, one sentence, and the literal `command` to run. So the \"what to\n * do\" column is genuinely empty for this whole family: the step IS the action,\n * and each `fix` below names the step's own command rather than inventing a\n * second instruction beside it.\n */\nconst NEXT_STEPS: Family = {\n scope: \"loam status\",\n codes: {\n \"next.adopt-bound\": { severityNote: \"step\", meaning: \"The fleet form's first entry: this repository's loam.json says it is a service the fleet has no services/<id>/ for, so nothing about the boundary you are standing in is written down yet. Ahead of every other service's adoption work, because it is the only step that is about HERE.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <id> --json`.\" },\n \"next.author-landscape\": { severityNote: \"step\", meaning: \"The first rung of the first-hour ladder, over a fleet with no services and no features: architecture/landscape.likec4 does not exist, or is still the scaffold's untouched map with no service drawn on it.\", fix: \"The step IS the action: run its `command` — `loam init --create` when the file is absent, `loam seed --from fleet.yaml` when it is the untouched scaffold. loam never guesses who calls whom.\" },\n \"next.bind-service\": { severityNote: \"step\", meaning: \"First-hour ladder: no service repository is bound to this fleet yet, so no command can ever run from a service's own checkout.\", fix: \"The step IS the action: run its `command`, `loam init --docs <path-to-this-docs-repo> --service <service-id>`, from a service's own repo, and commit the loam.json it writes.\" },\n \"next.adopt-first\": { severityNote: \"step\", meaning: \"First-hour ladder: services/ is empty, so nothing about any service is written down and nothing can be graded against anything.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <service-id> --json`; the brief it prints walks an agent through writing the baseline docs as draft.\" },\n \"next.adopt\": { severityNote: \"step\", meaning: \"One service in the fleet has no spec.md — nothing about it is written down, so no feature can be graded against it. One entry per undocumented service.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <id> --json`.\" },\n \"next.complete-service\": { severityNote: \"step\", meaning: \"Adoption does not end at spec.md: a service has one and no model.likec4, the state archive warns about and the fleet gate reports incomplete until something models it.\", fix: \"The step IS the action: run its `command`, `loam validate --service <id> --json`.\" },\n \"next.feature\": { severityNote: \"step\", meaning: \"One feature in flight that is not ready to ship, named with the stage it sits at and what it is missing — or, when it is blocked by another feature, the features it is waiting on.\", fix: \"The step IS the action: run its `command`, `loam status <FEAT-id> --json`, for that feature's own next list.\" },\n \"next.archive\": { severityNote: \"step\", meaning: \"In the fleet form, a feature that is authored and verified: ship it, and everything queued behind it is released. In a feature's own list it is always the last entry, saying the feature ships once everything above it is done.\", fix: \"The step IS the action: run its `command`, `loam archive <FEAT-id> --dry-run --json`; the dry run writes nothing and lists every file the merge would touch.\" },\n \"next.elided\": { severityNote: \"step\", meaning: \"The fleet list is capped, and this says how many steps of the same kinds it left out. The list is ordered most-unblocking first, so what was elided is the least urgent — a list nobody reads is not navigation.\", fix: \"The step IS the action: run its `command`, `loam list --json`, or work down the printed list and run status again.\" },\n \"next.fleet-clean\": { severityNote: \"step\", meaning: \"Services exist, nothing is in flight, and nothing is owed. Only that truthful case: over an EMPTY fleet the same sentence was vacuously true, and the first-hour ladder owns that repository now.\", fix: \"The step IS the action: run its `command`, `loam validate --all --json`, then start the next feature.\" },\n \"next.fleet-gate\": { severityNote: \"step\", meaning: \"The last entry of every fleet list, and always present: `status` grades nothing, so the least it can do is say where grading happens — `loam validate --all` is the gate this repository has to pass and the one CI runs.\", fix: \"The step IS the action: run its `command`, `loam validate --all --json`.\" },\n \"next.recover-commit\": { severityNote: \"step\", meaning: \"Always first, and never elided by the fleet form's cap: a journaled writer was killed mid-commit, so everything below this step is derived from files that may be half-written. Where the journal itself cannot be read, nothing below can be trusted until a human compares the living docs against version control.\", fix: \"The step IS the action: run its `command` — the interrupted command re-run, which recovers first under the lock, or `loam doctor --json` when the journal is unreadable and no loam command can repair it.\" },\n \"next.archived\": { severityNote: \"step\", meaning: \"The whole `next[]` for a feature that already shipped: it is archived, and its delta is folded into the living docs.\", fix: \"The step IS the action: run its `command`, `loam show <FEAT-id> --json`.\" },\n \"next.author-intent\": { severityNote: \"step\", meaning: \"The feature has no intent document — what it is for, in prose, before anything is derived from it.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam validate <FEAT-id> --json`. The command never writes the artifact: loam does not author.\" },\n \"next.touch-service\": { severityNote: \"step\", meaning: \"The feature carries no per-service delta at all, so nothing says which governed boundaries it changes.\", fix: \"The step IS the action: run its `command`, `loam list services --json`, then add specs/<service>/spec.md for every service the feature changes.\" },\n \"next.author-spec\": { severityNote: \"step\", meaning: \"One service the feature touches has no requirement delta — the ADDED / MODIFIED / REMOVED requirements the feature makes to that service, each with a scenario.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam delta <FEAT-id> --service <id> --json`, which projects the delta onto the living document.\" },\n \"next.author-openapi\": { severityNote: \"step\", meaning: \"Nothing gives one of the feature's services a living openapi.yaml, so this feature is the only thing that can write down its API surface. Deliberately not phrased as the service being absent from the living docs: a service adopted without a contract is the commoner case, and that wording sent readers looking for a directory that was right there.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam delta <FEAT-id> --service <id> --json`.\" },\n \"next.author-scenarios\": { severityNote: \"step\", meaning: \"Requirements in one service's delta carry no `#### Scenario:`, so they generate no test and no verify claim — a promise that reaches neither the suite nor the done-check. One entry per service and never per requirement, because a delta with forty bare requirements would otherwise BE the payload.\", fix: \"The step IS the action: write a scenario under each bare requirement, then run its `command`, `loam delta <FEAT-id> --service <id> --json`.\" },\n \"next.rebase\": { severityNote: \"step\", meaning: \"The feature carries requirements or operations with no baseline pin, on any of the three axes; until they have one the merge cannot tell what it EDITS from what it merely quotes.\", fix: \"The step IS the action: run its `command`, `loam rebase <FEAT-id>`.\" },\n \"next.archive-first\": { severityNote: \"step\", meaning: \"The feature builds on other features still in flight, which have to archive before it can.\", fix: \"The step IS the action: run its `command`, `loam dependencies <FEAT-id> --json`.\" },\n \"next.fix-coherence\": { severityNote: \"step\", meaning: \"The feature has findings that stop it, named starting with the first. Everything EITHER gate refuses on is counted, so a warning marked as gating archive is in the tally even though `validate` grades the feature valid — which is why its exit code alone will not show you why archive refuses.\", fix: \"The step IS the action: run its `command`, `loam validate <FEAT-id> --json`.\" },\n \"next.generate-tests\": { severityNote: \"step\", meaning: \"The feature's delta for a service carries scenarios no test run has answered yet. `loam gherkin` writes the .feature files whose green run is the only thing that may answer a `scenario.tested` claim, and it writes into the service's own repository — so the step names which repo to run it in when that is not this one.\", fix: \"The step IS the action: run its `command`, `loam gherkin <FEAT-id> --service <id>`, from that service's own checkout.\" },\n \"next.verify\": { severityNote: \"step\", meaning: \"The done-check has not been started, or its record cannot be used. Five situations share this code because they share the work — nothing recorded, a verification.yaml that does not read as a record, a record the feature has moved out from under, a record that answers no claims at all, and a feature with nothing to derive a checklist from yet — and the step's own statement says which.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --json`, which derives the checklist and prints what it asks. A record nothing can read is repaired or deleted by hand: nothing overwrites it while it is unreadable.\" },\n \"next.verify-unconfirmed\": { severityNote: \"step\", meaning: \"Claims on the feature are open — unconfirmed or unanswered — and this repository is not one of the services that owes an answer, so the recording form is not a step anyone can take from here.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --json`. The open claims are closed from each affected service's own repository.\" },\n \"next.attest-service\": { severityNote: \"step\", meaning: \"Claims are open AND this repository is one of the services that owes an answer, so the recording form is nameable here: `--record --service` binds the answers to this repo's HEAD and refuses anywhere else.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --record answers.json --service <id>`, answering the claims filed under this service; the rest belong to their own repositories.\" },\n \"next.verify-attested\": { severityNote: \"step\", meaning: \"Every claim is answered and some scenario claims are confirmed on an agent's word rather than on a test run (`verify.scenario-attested`) — complete work resting on an assertion, which is what `ready` has always meant here.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --results <cucumber-report.json>`, and the record stops resting on an assertion.\" },\n },\n};\n\n/**\n * `loam diff` — what CHANGED between a base git ref of the docs repo and the\n * working tree, not what is WRONG with either. That is why most rows below have\n * no repair: the meanings are the `DiffCode` union's own doc comments\n * (core/diff/semantic.ts), and the two error rows are the only ones naming work.\n */\nconst DIFF: Family = {\n scope: \"loam diff --base <ref>\",\n codes: {\n \"diff.service-added\": { severityNote: \"ok\", meaning: \"A services/<id>/ directory exists now that did not exist at the base ref.\", fix: \"Nothing to fix: `loam diff` says what changed between two points of the living docs, never what is wrong with them. This is the class of the change, for a reviewer to read.\" },\n \"diff.service-removed\": { severityNote: \"warn\", meaning: \"A service present at the base ref is gone from the working tree; the finding names the directory it occupied.\", fix: \"A review question rather than a defect: nothing in the working tree describes that boundary any more. `loam validate --all` is what grades the fleet that is left.\" },\n \"diff.requirement-added\": { severityNote: \"ok\", meaning: \"A living requirement not present at base — by `Requirement-ID:` where one is authored, by heading otherwise, the same join `loam rebase` pins against.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.requirement-removed\": { severityNote: \"warn\", meaning: \"A requirement present at the base ref is no longer in the living spec under the same identity.\", fix: \"A review question. A requirement that CHANGED identity — a new `Requirement-ID:`, a renamed heading — reads here as a removal plus an addition, which is the join being honest about what it can see rather than guessing a rename.\" },\n \"diff.requirement-modified\": { severityNote: \"ok\", meaning: \"The same requirement identity is on both sides and its digest moved. A rebase pin never moves that digest, so this is a real edit to the requirement's text.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.op-added\": { severityNote: \"ok\", meaning: \"An operationId in the living contract that was absent at the base ref; the message names its method and path.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.op-removed\": { severityNote: \"warn\", meaning: \"A base operationId is gone from the living contract and no current landscape edge or living requirement names it — or, in the second wording this code also carries, whether one does could NOT be fully answered because some consumer documents were not scannable, and the details name them.\", fix: \"Read which of the two sentences it is. Nobody could be scanned is never nobody names it: the unreadable documents carry the run to exit 1 on their own, and the removal stays unreviewed until they can be read.\" },\n \"diff.op-removed-consumed\": { severityNote: \"error\", meaning: \"An operation was removed since the base ref and the current fleet still names it — a landscape edge, or a foreign service's living requirement. The details list the victims, and the finding sets `breaking`.\", fix: \"These joins break. Either restore the operation, or land the consumers' own deltas in the same change so nothing in the fleet is left calling something that is gone.\" },\n \"diff.op-deprecated\": { severityNote: \"warn\", meaning: \"`deprecated: true` was introduced on an operation since the base ref. The details name the current consumers, under one of three deliberately honest phrasings: some still name it, none does, or whether any does could not be fully answered.\", fix: \"Deprecation is a promise to the consumers the details name, and nothing is broken yet. Read which phrasing it is before treating a silent list as an empty one.\" },\n \"diff.message-added\": { severityNote: \"ok\", meaning: \"An AsyncAPI message name absent at the base ref.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.message-removed\": { severityNote: \"warn\", meaning: \"A base message is gone. The finding says which of three cases it is: another current service still declares the name, nothing currently consumes it, or whether anything does could not be fully answered because some consumer documents were not scannable.\", fix: \"Read which case it is. A name another service still declares is not orphaned, and an unanswerable scan is not an empty one — only positive evidence vouches here.\" },\n \"diff.message-removed-consumed\": { severityNote: \"error\", meaning: \"A message was removed since the base ref and the current fleet still consumes it — a `Consumes:` line, or a consumes edge. The details list the victims, and the finding sets `breaking`.\", fix: \"These joins break. Either restore the message, or land the consumers' own deltas in the same change.\" },\n \"diff.consumer-added\": { severityNote: \"ok\", meaning: \"A cross-service join appeared since base: another service's living requirements now name this service's operation or message. Filed on the PROVIDER — the service whose surface the join lands on.\", fix: \"Nothing to fix, and the change class most worth reading: a new consumer is a new obligation on this service's contract, and removing that operation later is what `diff.op-removed-consumed` will be about.\" },\n \"diff.consumer-removed\": { severityNote: \"ok\", meaning: \"A cross-service join present at the base ref is gone: a consumer's living requirements no longer name this service's operation or message.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n },\n};\n\n/**\n * `loam gate` — the can-i-deploy question, a pure query over evidence other\n * commands already recorded. Nothing here re-derives a coherence plan, so every\n * repair below is somebody else's command.\n */\nconst GATE: Family = {\n scope: \"loam gate\",\n codes: {\n \"gate.partners-unknown\": { severityNote: \"warn when the map is absent; error when it exists and cannot be used\", meaning: \"The partner set for the gated service could not be derived from architecture/landscape.likec4 — it is absent, or it exists and does not parse. That means nobody could look, never that nothing joins this service. Absent is a warning because a repo before its first adopt legitimately has no map yet; a broken map is an error because `loam validate --all` fails that repo for the same file, and a deploy gate passing where the fleet gate fails would be the quieter of two contradictory verdicts.\", fix: \"Fix the landscape and re-run. `loam validate --all` diagnoses the file itself; this check only refuses the false silence.\" },\n \"gate.service-undocumented\": { severityNote: \"error\", meaning: \"The service being deployed sits below `documented` on the adoption ladder, so the docs cannot say what its joins even are and no recorded evidence can answer the deploy question. The details list what the next rung wants.\", fix: \"Author the baseline — `loam adopt --service <id>` briefs exactly what is missing — and this check passes once the required artifact set exists.\" },\n \"gate.partner-undocumented\": { severityNote: \"warn\", meaning: \"A service that joins the gated one has no services/<id>/ directory at all, or has one sitting below `documented`: its side of the join is not recorded, so the gate cannot see what that partner promises. An `#external` partner is exempt on purpose — somebody else's system is unrecorded by design.\", fix: \"Adopt the partner (`loam adopt --service <id>` briefs it), bind its landscape element to the right service with `metadata { service '<id>' }`, or tag it `#external` if it is not ours.\" },\n \"gate.feature-unverified\": { severityNote: \"warn\", meaning: \"An active feature touching the gated service is not verified: no record at all, a verification.yaml that does not read as a record, a record the feature moved out from under, a record answering no claims, or open claims counted in the message. A record that is merely `attested` is NOT reported under this code — it carries `verify.scenario-attested` instead, because complete work resting on an assertion is a different fact from work nobody checked.\", fix: \"Derive the checklist and answer every claim with evidence: `loam verify <FEAT-id> --json`. A record nothing can read is repaired or deleted by hand; nothing overwrites it while it is unreadable.\" },\n },\n};\n\n/** In listing order, though every consumer sorts by code — the order is documentation, not contract. */\nconst FAMILIES: readonly Family[] = [DOCTOR, NEXT_STEPS, DIFF, GATE];\n\n/** One family row, in the shape `FindingEntry` (lookup.ts) takes. */\nexport interface FamilyFinding extends FamilyEntry {\n readonly scope: string;\n}\n\n/**\n * The registry's answer for one subject, or null.\n *\n * Built per call over constants already in memory, never cached at module\n * level, for the reason lookup.ts's header gives: tests fork and chdir, and the\n * MCP server outlives many calls.\n */\nexport function familyFinding(subject: string): FamilyFinding | null {\n for (const family of FAMILIES) {\n const entry = family.codes[subject];\n if (entry !== undefined) return { scope: family.scope, ...entry };\n }\n return null;\n}\n\n/** Every code the registry answers, sorted — the same plain sort the code inventory uses. */\nexport function familyCodes(): string[] {\n return FAMILIES.flatMap((family) => Object.keys(family.codes)).sort();\n}\n"]}
1
+ {"version":3,"file":"families.js","sourceRoot":"","sources":["../../../src/core/explain/families.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The family registry: `loam explain`'s content for the four code families no\n * fix table anywhere grades — `doctor.*`, `next.*`, `diff.*` and `gate.*`.\n *\n * WHY THIS IS NOT THE DUPLICATION fix-tables.ts FORBIDS. That module's header\n * argues against a second hand-maintained copy of the /loam-check prose, and\n * the argument is right: the tables ship in the binary, test/codes-drift.test.ts\n * holds every emitted finding code against them, and a registry restating them\n * would be free to trail the bytes `loam instructions loam-check` prints. None\n * of that applies here. These 65 codes have no fix-table row in any workflow\n * body — /loam-check grades DOCUMENTS, and these four families grade a\n * repository's wiring (`doctor`), a recommendation (`next`), a change between\n * two refs (`diff`) and a deploy question (`gate`). There is nothing to trail:\n * this registry is not a second copy, it is the only copy. The precedent is the\n * sibling module refusals.ts, a hand-written map that is the sole source for\n * the `ErrorCode` meanings for exactly the same reason — their prose lives in\n * doc comments, which are erased, so there is no runtime constant to read.\n *\n * What that costs, stated rather than hidden: nothing mechanical proves an\n * entry below still matches the message its emitter prints. Every meaning and\n * every fix here was written FROM the emitter — `doctor` findings carry a `fix`\n * field of their own (core/doctor/), a `next` step carries a `statement` and a\n * literal `command` (core/status/), the `DiffCode` union carries a doc comment\n * per member (core/diff/semantic.ts) — and the wording is theirs, not a\n * paraphrase invented here. Two guards catch the two failures that CAN be\n * caught: test/explain.test.ts asserts no code below is also answered by a fix\n * table (the two sources must never be able to disagree), and\n * test/agent-commands-runnable.test.ts parses every backticked `loam …` in this\n * file against the real program, so a fix naming a command loam does not have —\n * or a flag it does not declare — fails at gate time. A two-token span is a\n * NAME rather than an invocation there and is only checked for the command\n * existing, so a fix that means an invocation must carry its arguments.\n *\n * `severityNote` is the EMITTER's own word, not a fix table's parenthetical:\n * `doctor` grades blocker/warning, `diff` and `gate` grade ok/warn/error, and a\n * `next` step is graded not at all — it is a recommendation, so its note is\n * `step`. Where a code's severity depends on what was found (`doctor.docs-locked`,\n * `gate.partners-unknown`) the note says so in prose, the way the tables' own\n * long notes do (`warn here; error at archive plan time`).\n *\n * `openspec.*` and `mapping.*` — 49 codes — are deliberately NOT here, and the\n * reason is in test/explain.test.ts beside their backlog entries: they are the\n * only codes in the vocabulary whose subject is not a loam document at all.\n */\n\n/**\n * One code's answer. Structurally a `FindingEntry` (lookup.ts) minus `scope`,\n * which the family supplies — declared here rather than imported so this module\n * imports nothing at all and the package's file-level graph stays a tree.\n *\n * Every entry below is written on ONE line, however long that line runs, and\n * that is a deliberate concession to a counted limit rather than a style: 65\n * codes at four lines each is 260 lines of punctuation before a word of prose,\n * which does not fit the 400-line file limit — and src/core/explain/ is at its\n * five-file cap, so there is no second file to put the overflow in. The fields\n * stay named for the same reason a tuple was refused: `severityNote`, `meaning`\n * and `fix` say which column is which without counting commas.\n */\nexport interface FamilyEntry {\n /** The emitter's own severity word, or the prose a conditional one needs. */\n readonly severityNote: string;\n /** What the code is about, in the words the emitter already prints. */\n readonly meaning: string;\n /** What to do — for `next.*`, that the step's own `command` IS the action. */\n readonly fix: string;\n}\n\n/** One family: the invocation that emits it, and its codes. */\ninterface Family {\n /** The scope label, spelled as the fix tables spell theirs — the invocation that grades it. */\n readonly scope: string;\n readonly codes: Readonly<Record<string, FamilyEntry>>;\n}\n\n/**\n * `loam doctor` — the preflight. Every finding here already carries a `fix`\n * field in doctor's own report (core/doctor/doctor.ts, config.ts, agents.ts,\n * residue.ts); these rows say the same thing without a repository in front of\n * them, which is the whole difference between a report and a lookup.\n */\nconst DOCTOR: Family = {\n scope: \"loam doctor\",\n codes: {\n \"doctor.config-missing\": { severityNote: \"blocker\", meaning: \"No loam.json at or above the working directory, so nothing says which docs repo this checkout belongs to — every other command refuses with `no-config` until there is one.\", fix: \"Run `loam init --docs <dir>` in this repository; add `--create` when the docs repo does not exist yet.\" },\n \"doctor.config-invalid\": { severityNote: \"blocker\", meaning: \"A loam.json is there and `parseConfig` — the same loader every command uses — refuses it; the finding carries the parser's own words. doctor deliberately has no second validator: the one it used to carry accepted a config the commands rejected, and reported a healthy repo in which nothing could run.\", fix: \"Repair the file the finding names, or delete it and re-run `loam init`.\" },\n \"doctor.docs-missing\": { severityNote: \"blocker\", meaning: \"`docsDir` resolves to something that is missing or is not a directory — the docs repo was never cloned, or the path is wrong.\", fix: \"Fix `docsDir` in the loam.json the finding names, clone the docs repo to that path, or run `loam init --docs <dir> --create`.\" },\n \"doctor.services-missing\": { severityNote: \"blocker\", meaning: \"`docsDir` is a directory and holds no `services/`, so it is some other directory — most often the service repo itself, after a typo in the pointer.\", fix: \"Point `docsDir` at the shared docs repo, or run `loam init --docs <dir> --create` to scaffold one. Do not scaffold a second source of truth beside the real one.\" },\n \"doctor.docs-absolute\": { severityNote: \"warning\", meaning: \"`docsDir` is stored as an absolute path and loam.json is committed, so the pointer names a directory that exists on exactly one machine: it works for whoever ran `loam init` and for nobody who clones the repo afterwards.\", fix: \"Rewrite `docsDir` as a path relative to the loam.json holding it, such as `../docs`.\" },\n \"doctor.docs-unreadable\": { severityNote: \"warning\", meaning: \"The docs repo exists and this process cannot read it, so every command that reads the fleet will fail or report an empty one.\", fix: \"Grant read access to the directory the finding names — check ownership and mode.\" },\n \"doctor.docs-readonly\": { severityNote: \"warning\", meaning: \"The docs repo is readable and not writable: read-only commands work and the writing ones do not.\", fix: \"Grant write access if you need `loam new`, `loam adopt` or `loam archive`. A read-only checkout is a legitimate state for a reviewer, which is why this is a warning.\" },\n \"doctor.landscape-missing\": { severityNote: \"warning\", meaning: \"architecture/landscape.likec4 is missing, and it is the one artifact every cross-service check reads — nothing about who calls whom can be checked without it.\", fix: \"Create it with one element per service and an edge per call: `loam init --create` writes the empty map, and `loam seed --from fleet.yaml` templates one from stated facts.\" },\n \"doctor.landscape-unreadable\": { severityNote: \"blocker\", meaning: \"The landscape file exists and the read itself failed — permissions or an I/O error, carried verbatim in the message.\", fix: \"Check permissions on the path the finding names.\" },\n \"doctor.landscape-merge-conflict\": { severityNote: \"blocker\", meaning: \"The landscape still contains merge conflict markers, named by line. Checked BEFORE the parser on purpose: both sides of a conflict can be syntactically valid LikeC4, so the file sometimes parses and is still two halves of two different maps.\", fix: \"Resolve the conflict keeping BOTH services' elements and edges — the usual cause is several services adopted into one map in the same week — then re-run `loam doctor`.\" },\n \"doctor.landscape-invalid\": { severityNote: \"blocker\", meaning: \"The landscape exists, carries no conflict markers, and does not parse; the message quotes the first parser error with its line and counts the rest.\", fix: \"Fix the file — every fleet-wide check is blind until it parses.\" },\n \"doctor.likec4-config-missing\": { severityNote: \"warning\", meaning: \"The docs repo has no likec4.config.json, so the tree is not a loadable LikeC4 workspace: every service model and feature delta declares its own `specification` block, and a renderer pointed at the repo root merges them and reports every declaration as a duplicate. No loam check reads this file — the repo is only unrenderable, which is why it is a warning.\", fix: \"Write the project file the finding prints; it scopes the root project to architecture/, so `npx likec4 start` in the docs repo renders the fleet map. Then run `loam subsystem sync`: it writes one create-only likec4.config.json beside every service model, so each model renders from that same root as a project of its own — only a feature delta still renders from its own directory.\" },\n \"doctor.docs-locked\": { severityNote: \"warning while a live process holds it; blocker when its holder is dead or it names none\", meaning: \"A `.loam-lock` is present. Held by a running process it is a fact about right now. Held by a process that no longer exists on this host — or unreadable, so no holder can be named at all — nothing is ever going to release it, and every writing command refuses with `docs-busy` forever.\", fix: \"Wait and re-run while the holder is alive; nothing is read or written while it is held. Delete the lock file the finding names when its holder is dead or unnameable, then run `loam doctor` again for an interrupted commit underneath it.\" },\n \"doctor.commit-unreadable\": { severityNote: \"blocker\", meaning: \"A `.loam-commit` journal is present and cannot be read: a commit was interrupted and the one record of which files it had already written is unreadable.\", fix: \"Hand this to a human — compare the root's files against version control before running any loam command that writes there. Every journaled writer refuses with `commit-interrupted` while it is present.\" },\n \"doctor.commit-interrupted\": { severityNote: \"blocker\", meaning: \"A journaled writer — archive, unarchive, rebase, vouch, new, gherkin, or a recording verify — was killed mid-commit. The message names the host, pid and time, and every file or directory move that may be half-done. This is the surface that used to report a healthy repo over half-merged living docs.\", fix: \"Re-run the command the finding names: it recovers first, under the lock, rolling the staged bytes forward, and refuses rather than guessing if a file has been edited since. An interrupted `loam unarchive` is FINISHED, never undone — the merged text it was replacing is written down nowhere.\" },\n \"doctor.staging-temps\": { severityNote: \"warning\", meaning: \"Orphaned `.loam-*.tmp` scratch files from a killed writer, never linked into place — so nothing reads them and nothing depends on them. A journal's own recorded temps are excluded, because those are the durable copy its roll-forward recovery renames in. Litter, not damage.\", fix: \"The next journaled writer through that root removes its own; delete any that remain. They cost disk and nothing else.\" },\n \"doctor.inventory-unreadable\": { severityNote: \"warning\", meaning: \"The config and the docs repo are both fine and enumerating `services/` or `features/` threw, so doctor's fleet counts — and its verdict on the service binding — are about a fleet nobody could read.\", fix: \"Check that services/ and features/ under the docs repo are readable directories.\" },\n \"doctor.service-unbound\": { severityNote: \"warning\", meaning: \"loam.json declares no `service`, so nothing binds this checkout to one governed boundary and the service-repo checks have nothing to run against — `sources.stale`, a recording `loam verify`, `loam gherkin`. Deliberately not emitted inside the docs repo itself, where having no binding is the correct state rather than a gap.\", fix: \"Run `loam init --docs <dir> --service <id>` here.\" },\n \"doctor.service-unknown\": { severityNote: \"warning\", meaning: \"loam.json names a `service` the fleet has no services/<id>/ for — and the fleet WAS read, so this is a claim about a real inventory rather than one nobody could take.\", fix: \"Run `loam adopt --service <id>` to onboard it, or fix `service` in loam.json.\" },\n \"doctor.agent-files-missing\": { severityNote: \"warning\", meaning: \"Some of the command and skill files this loam lays down for the repo's recorded tools and profile are not here: the repo was initialized by an older loam, or they were deleted. An out-of-date command set costs entry points, never a refusal, which is why it is a warning.\", fix: \"Re-run `loam init --docs <dir> --tools <ids>` here. It creates absent files, refreshes only pointers whose current bytes still match their recorded digest, preserves every customized file, and keeps this repo's service binding.\" },\n \"doctor.agent-files-stale\": { severityNote: \"warning\", meaning: \"Command or skill files that ARE here carry no loam stamp, or one older than this binary, so the protocol and code tables they instruct an agent with may describe a loam that no longer exists. Staleness reads only the stamp; safe refresh is a separate digest check performed by init.\", fix: \"Re-run `loam init` to refresh a pointer only when its recorded digest still matches. Review a customized file against the current body yourself and update its stamp, or delete it so init can recreate it; your edits always outrank the template.\" },\n },\n};\n\n/**\n * `loam status` — both forms print `next[]`, and each entry carries a `code` to\n * branch on, one sentence, and the literal `command` to run. So the \"what to\n * do\" column is genuinely empty for this whole family: the step IS the action,\n * and each `fix` below names the step's own command rather than inventing a\n * second instruction beside it.\n */\nconst NEXT_STEPS: Family = {\n scope: \"loam status\",\n codes: {\n \"next.adopt-bound\": { severityNote: \"step\", meaning: \"The fleet form's first entry: this repository's loam.json says it is a service the fleet has no services/<id>/ for, so nothing about the boundary you are standing in is written down yet. Ahead of every other service's adoption work, because it is the only step that is about HERE.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <id> --json`.\" },\n \"next.author-landscape\": { severityNote: \"step\", meaning: \"The first rung of the first-hour ladder, over a fleet with no services and no features: architecture/landscape.likec4 does not exist, or is still the scaffold's untouched map with no service drawn on it.\", fix: \"The step IS the action: run its `command` — `loam init --create` when the file is absent, `loam seed --from fleet.yaml` when it is the untouched scaffold. loam never guesses who calls whom.\" },\n \"next.bind-service\": { severityNote: \"step\", meaning: \"First-hour ladder: no service repository is bound to this fleet yet, so no command can ever run from a service's own checkout.\", fix: \"The step IS the action: run its `command`, `loam init --docs <path-to-this-docs-repo> --service <service-id>`, from a service's own repo, and commit the loam.json it writes.\" },\n \"next.adopt-first\": { severityNote: \"step\", meaning: \"First-hour ladder: services/ is empty, so nothing about any service is written down and nothing can be graded against anything.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <service-id> --json`; the brief it prints walks an agent through writing the baseline docs as draft.\" },\n \"next.adopt\": { severityNote: \"step\", meaning: \"One service in the fleet has no spec.md — nothing about it is written down, so no feature can be graded against it. One entry per undocumented service.\", fix: \"The step IS the action: run its `command`, `loam adopt --service <id> --json`.\" },\n \"next.complete-service\": { severityNote: \"step\", meaning: \"Adoption does not end at spec.md: a service has one and no model.likec4, the state archive warns about and the fleet gate reports incomplete until something models it.\", fix: \"The step IS the action: run its `command`, `loam validate --service <id> --json`.\" },\n \"next.feature\": { severityNote: \"step\", meaning: \"One feature in flight that is not ready to ship, named with the stage it sits at and what it is missing — or, when it is blocked by another feature, the features it is waiting on.\", fix: \"The step IS the action: run its `command`, `loam status <FEAT-id> --json`, for that feature's own next list.\" },\n \"next.archive\": { severityNote: \"step\", meaning: \"In the fleet form, a feature that is authored and verified: ship it, and everything queued behind it is released. In a feature's own list it is always the last entry, saying the feature ships once everything above it is done.\", fix: \"The step IS the action: run its `command`, `loam archive <FEAT-id> --dry-run --json`; the dry run writes nothing and lists every file the merge would touch.\" },\n \"next.elided\": { severityNote: \"step\", meaning: \"The fleet list is capped, and this says how many steps of the same kinds it left out. The list is ordered most-unblocking first, so what was elided is the least urgent — a list nobody reads is not navigation.\", fix: \"The step IS the action: run its `command`, `loam list --json`, or work down the printed list and run status again.\" },\n \"next.fleet-clean\": { severityNote: \"step\", meaning: \"Services exist, nothing is in flight, and nothing is owed. Only that truthful case: over an EMPTY fleet the same sentence was vacuously true, and the first-hour ladder owns that repository now.\", fix: \"The step IS the action: run its `command`, `loam validate --all --json`, then start the next feature.\" },\n \"next.fleet-gate\": { severityNote: \"step\", meaning: \"The last entry of every fleet list, and always present: `status` grades nothing, so the least it can do is say where grading happens — `loam validate --all` is the gate this repository has to pass and the one CI runs.\", fix: \"The step IS the action: run its `command`, `loam validate --all --json`.\" },\n \"next.recover-commit\": { severityNote: \"step\", meaning: \"Always first, and never elided by the fleet form's cap: a journaled writer was killed mid-commit, so everything below this step is derived from files that may be half-written. Where the journal itself cannot be read, nothing below can be trusted until a human compares the living docs against version control.\", fix: \"The step IS the action: run its `command` — the interrupted command re-run, which recovers first under the lock, or `loam doctor --json` when the journal is unreadable and no loam command can repair it.\" },\n \"next.archived\": { severityNote: \"step\", meaning: \"The whole `next[]` for a feature that already shipped: it is archived, and its delta is folded into the living docs.\", fix: \"The step IS the action: run its `command`, `loam show <FEAT-id> --json`.\" },\n \"next.author-intent\": { severityNote: \"step\", meaning: \"The feature has no intent document — what it is for, in prose, before anything is derived from it.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam validate <FEAT-id> --json`. The command never writes the artifact: loam does not author.\" },\n \"next.touch-service\": { severityNote: \"step\", meaning: \"The feature carries no per-service delta at all, so nothing says which governed boundaries it changes.\", fix: \"The step IS the action: run its `command`, `loam list services --json`, then add specs/<service>/spec.md for every service the feature changes.\" },\n \"next.author-spec\": { severityNote: \"step\", meaning: \"One service the feature touches has no requirement delta — the ADDED / MODIFIED / REMOVED requirements the feature makes to that service, each with a scenario.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam delta <FEAT-id> --service <id> --json`, which projects the delta onto the living document.\" },\n \"next.author-openapi\": { severityNote: \"step\", meaning: \"Nothing gives one of the feature's services a living openapi.yaml, so this feature is the only thing that can write down its API surface. Deliberately not phrased as the service being absent from the living docs: a service adopted without a contract is the commoner case, and that wording sent readers looking for a directory that was right there.\", fix: \"The step IS the action: write the file the step names, then run its `command`, `loam delta <FEAT-id> --service <id> --json`.\" },\n \"next.author-scenarios\": { severityNote: \"step\", meaning: \"Requirements in one service's delta carry no `#### Scenario:`, so they generate no test and no verify claim — a promise that reaches neither the suite nor the done-check. One entry per service and never per requirement, because a delta with forty bare requirements would otherwise BE the payload.\", fix: \"The step IS the action: write a scenario under each bare requirement, then run its `command`, `loam delta <FEAT-id> --service <id> --json`.\" },\n \"next.rebase\": { severityNote: \"step\", meaning: \"The feature carries requirements or operations with no baseline pin, on any of the three axes; until they have one the merge cannot tell what it EDITS from what it merely quotes.\", fix: \"The step IS the action: run its `command`, `loam rebase <FEAT-id>`.\" },\n \"next.archive-first\": { severityNote: \"step\", meaning: \"The feature builds on other features still in flight, which have to archive before it can.\", fix: \"The step IS the action: run its `command`, `loam dependencies <FEAT-id> --json`.\" },\n \"next.fix-coherence\": { severityNote: \"step\", meaning: \"The feature has findings that stop it, named starting with the first. Everything EITHER gate refuses on is counted, so a warning marked as gating archive is in the tally even though `validate` grades the feature valid — which is why its exit code alone will not show you why archive refuses.\", fix: \"The step IS the action: run its `command`, `loam validate <FEAT-id> --json`.\" },\n \"next.generate-tests\": { severityNote: \"step\", meaning: \"The feature's delta for a service carries scenarios no test run has answered yet. `loam gherkin` writes the .feature files whose green run is the only thing that may answer a `scenario.tested` claim, and it writes into the service's own repository — so the step names which repo to run it in when that is not this one.\", fix: \"The step IS the action: run its `command`, `loam gherkin <FEAT-id> --service <id>`, from that service's own checkout.\" },\n \"next.verify\": { severityNote: \"step\", meaning: \"The done-check has not been started, or its record cannot be used. Five situations share this code because they share the work — nothing recorded, a verification.yaml that does not read as a record, a record the feature has moved out from under, a record that answers no claims at all, and a feature with nothing to derive a checklist from yet — and the step's own statement says which.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --json`, which derives the checklist and prints what it asks. A record nothing can read is repaired or deleted by hand: nothing overwrites it while it is unreadable.\" },\n \"next.verify-unconfirmed\": { severityNote: \"step\", meaning: \"Claims on the feature are open — unconfirmed or unanswered — and this repository is not one of the services that owes an answer, so the recording form is not a step anyone can take from here.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --json`. The open claims are closed from each affected service's own repository.\" },\n \"next.attest-service\": { severityNote: \"step\", meaning: \"Claims are open AND this repository is one of the services that owes an answer, so the recording form is nameable here: `--record --service` binds the answers to this repo's HEAD and refuses anywhere else.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --record answers.json --service <id>`, answering the claims filed under this service; the rest belong to their own repositories.\" },\n \"next.verify-attested\": { severityNote: \"step\", meaning: \"Every claim is answered and some scenario claims are confirmed on an agent's word rather than on a test run (`verify.scenario-attested`) — complete work resting on an assertion, which is what `ready` has always meant here.\", fix: \"The step IS the action: run its `command`, `loam verify <FEAT-id> --results <cucumber-report.json>`, and the record stops resting on an assertion.\" },\n },\n};\n\n/**\n * `loam diff` — what CHANGED between a base git ref of the docs repo and the\n * working tree, not what is WRONG with either. That is why most rows below have\n * no repair: the meanings are the `DiffCode` union's own doc comments\n * (core/diff/semantic.ts), and the two error rows are the only ones naming work.\n */\nconst DIFF: Family = {\n scope: \"loam diff --base <ref>\",\n codes: {\n \"diff.service-added\": { severityNote: \"ok\", meaning: \"A services/<id>/ directory exists now that did not exist at the base ref.\", fix: \"Nothing to fix: `loam diff` says what changed between two points of the living docs, never what is wrong with them. This is the class of the change, for a reviewer to read.\" },\n \"diff.service-removed\": { severityNote: \"warn\", meaning: \"A service present at the base ref is gone from the working tree; the finding names the directory it occupied.\", fix: \"A review question rather than a defect: nothing in the working tree describes that boundary any more. `loam validate --all` is what grades the fleet that is left.\" },\n \"diff.requirement-added\": { severityNote: \"ok\", meaning: \"A living requirement not present at base — by `Requirement-ID:` where one is authored, by heading otherwise, the same join `loam rebase` pins against.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.requirement-removed\": { severityNote: \"warn\", meaning: \"A requirement present at the base ref is no longer in the living spec under the same identity.\", fix: \"A review question. A requirement that CHANGED identity — a new `Requirement-ID:`, a renamed heading — reads here as a removal plus an addition, which is the join being honest about what it can see rather than guessing a rename.\" },\n \"diff.requirement-modified\": { severityNote: \"ok\", meaning: \"The same requirement identity is on both sides and its digest moved. A rebase pin never moves that digest, so this is a real edit to the requirement's text.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.op-added\": { severityNote: \"ok\", meaning: \"An operationId in the living contract that was absent at the base ref; the message names its method and path.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.op-removed\": { severityNote: \"warn\", meaning: \"A base operationId is gone from the living contract and no current landscape edge or living requirement names it — or, in the second wording this code also carries, whether one does could NOT be fully answered because some consumer documents were not scannable, and the details name them.\", fix: \"Read which of the two sentences it is. Nobody could be scanned is never nobody names it: the unreadable documents carry the run to exit 1 on their own, and the removal stays unreviewed until they can be read.\" },\n \"diff.op-removed-consumed\": { severityNote: \"error\", meaning: \"An operation was removed since the base ref and the current fleet still names it — a landscape edge, or a foreign service's living requirement. The details list the victims, and the finding sets `breaking`.\", fix: \"These joins break. Either restore the operation, or land the consumers' own deltas in the same change so nothing in the fleet is left calling something that is gone.\" },\n \"diff.op-deprecated\": { severityNote: \"warn\", meaning: \"`deprecated: true` was introduced on an operation since the base ref. The details name the current consumers, under one of three deliberately honest phrasings: some still name it, none does, or whether any does could not be fully answered.\", fix: \"Deprecation is a promise to the consumers the details name, and nothing is broken yet. Read which phrasing it is before treating a silent list as an empty one.\" },\n \"diff.message-added\": { severityNote: \"ok\", meaning: \"An AsyncAPI message name absent at the base ref.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n \"diff.message-removed\": { severityNote: \"warn\", meaning: \"A base message is gone. The finding says which of three cases it is: another current service still declares the name, nothing currently consumes it, or whether anything does could not be fully answered because some consumer documents were not scannable.\", fix: \"Read which case it is. A name another service still declares is not orphaned, and an unanswerable scan is not an empty one — only positive evidence vouches here.\" },\n \"diff.message-removed-consumed\": { severityNote: \"error\", meaning: \"A message was removed since the base ref and the current fleet still consumes it — a `Consumes:` line, or a consumes edge. The details list the victims, and the finding sets `breaking`.\", fix: \"These joins break. Either restore the message, or land the consumers' own deltas in the same change.\" },\n \"diff.consumer-added\": { severityNote: \"ok\", meaning: \"A cross-service join appeared since base: another service's living requirements now name this service's operation or message. Filed on the PROVIDER — the service whose surface the join lands on.\", fix: \"Nothing to fix, and the change class most worth reading: a new consumer is a new obligation on this service's contract, and removing that operation later is what `diff.op-removed-consumed` will be about.\" },\n \"diff.consumer-removed\": { severityNote: \"ok\", meaning: \"A cross-service join present at the base ref is gone: a consumer's living requirements no longer name this service's operation or message.\", fix: \"Nothing to fix: this is the class of the change, for a reviewer to read.\" },\n },\n};\n\n/**\n * `loam gate` — the can-i-deploy question, a pure query over evidence other\n * commands already recorded. Nothing here re-derives a coherence plan, so every\n * repair below is somebody else's command.\n */\nconst GATE: Family = {\n scope: \"loam gate\",\n codes: {\n \"gate.partners-unknown\": { severityNote: \"warn when the map is absent; error when it exists and cannot be used\", meaning: \"The partner set for the gated service could not be derived from architecture/landscape.likec4 — it is absent, or it exists and does not parse. That means nobody could look, never that nothing joins this service. Absent is a warning because a repo before its first adopt legitimately has no map yet; a broken map is an error because `loam validate --all` fails that repo for the same file, and a deploy gate passing where the fleet gate fails would be the quieter of two contradictory verdicts.\", fix: \"Fix the landscape and re-run. `loam validate --all` diagnoses the file itself; this check only refuses the false silence.\" },\n \"gate.service-undocumented\": { severityNote: \"error\", meaning: \"The service being deployed sits below `documented` on the adoption ladder, so the docs cannot say what its joins even are and no recorded evidence can answer the deploy question. The details list what the next rung wants.\", fix: \"Author the baseline — `loam adopt --service <id>` briefs exactly what is missing — and this check passes once the required artifact set exists.\" },\n \"gate.partner-undocumented\": { severityNote: \"warn\", meaning: \"A service that joins the gated one has no services/<id>/ directory at all, or has one sitting below `documented`: its side of the join is not recorded, so the gate cannot see what that partner promises. An `#external` partner is exempt on purpose — somebody else's system is unrecorded by design.\", fix: \"Adopt the partner (`loam adopt --service <id>` briefs it), bind its landscape element to the right service with `metadata { service '<id>' }`, or tag it `#external` if it is not ours.\" },\n \"gate.feature-unverified\": { severityNote: \"warn\", meaning: \"An active feature touching the gated service is not verified: no record at all, a verification.yaml that does not read as a record, a record the feature moved out from under, a record answering no claims, or open claims counted in the message. A record that is merely `attested` is NOT reported under this code — it carries `verify.scenario-attested` instead, because complete work resting on an assertion is a different fact from work nobody checked.\", fix: \"Derive the checklist and answer every claim with evidence: `loam verify <FEAT-id> --json`. A record nothing can read is repaired or deleted by hand; nothing overwrites it while it is unreadable.\" },\n },\n};\n\n/** In listing order, though every consumer sorts by code — the order is documentation, not contract. */\nconst FAMILIES: readonly Family[] = [DOCTOR, NEXT_STEPS, DIFF, GATE];\n\n/** One family row, in the shape `FindingEntry` (lookup.ts) takes. */\nexport interface FamilyFinding extends FamilyEntry {\n readonly scope: string;\n}\n\n/**\n * The registry's answer for one subject, or null.\n *\n * Built per call over constants already in memory, never cached at module\n * level, for the reason lookup.ts's header gives: tests fork and chdir, and the\n * MCP server outlives many calls.\n */\nexport function familyFinding(subject: string): FamilyFinding | null {\n for (const family of FAMILIES) {\n const entry = family.codes[subject];\n if (entry !== undefined) return { scope: family.scope, ...entry };\n }\n return null;\n}\n\n/** Every code the registry answers, sorted — the same plain sort the code inventory uses. */\nexport function familyCodes(): string[] {\n return FAMILIES.flatMap((family) => Object.keys(family.codes)).sort();\n}\n"]}
@@ -274,4 +274,42 @@ export function agentsPath(docsDir) {
274
274
  export function archiveDir(docsDir) {
275
275
  return join(featuresDir(docsDir), ARCHIVE_DIR);
276
276
  }
277
+ /**
278
+ * The LikeC4 project file — the one thing in a docs repo loam writes for a
279
+ * tool other than itself — and it lives at two altitudes.
280
+ *
281
+ * At the ROOT, `loam init --create` writes the project `fleet`, scoped to
282
+ * `architecture/` (`core/docs.ts` composes those bytes). Beside each
283
+ * `services/<…>/<id>/model.likec4`, `loam subsystem sync` writes a project of
284
+ * the service's own: loam parses every `.likec4` file ALONE, so each model
285
+ * declares its own `specification` block and the root project must exclude
286
+ * `services/**` — which left every adopted service a box on the fleet map with
287
+ * nothing renderable inside it, in the renderer opened at the docs root.
288
+ * `core/repo/tree/render/projects.ts` composes the per-service bytes and
289
+ * records what was measured.
290
+ *
291
+ * Spelled here and NOT in `ARTIFACT_FILES`, and the distinction is
292
+ * load-bearing: a filename in that table classifies a directory as a service,
293
+ * and this file is the renderer's — a stray one must mint nothing. The root's
294
+ * NAME sits beside the filename because the per-service rule needs it too (a
295
+ * service id equal to the root project's name would be silently renamed by
296
+ * the renderer), and two spellings of the word are how the scaffold and the
297
+ * rule drift apart.
298
+ */
299
+ export const LIKEC4_PROJECT_FILENAME = "likec4.config.json";
300
+ /** The root project's name, exactly as `loam init --create` scaffolds it. */
301
+ export const LIKEC4_ROOT_PROJECT = "fleet";
302
+ export function rootProjectPath(docsDir) {
303
+ return join(docsDir, LIKEC4_PROJECT_FILENAME);
304
+ }
305
+ /**
306
+ * The two files the per-service project question is asked of — the model that
307
+ * needs rendering and the project file that renders it — in one spelling for
308
+ * the writer (`subsystem sync`) and the grader (`validate --all`) alike. Handed
309
+ * INTO `render/projects.ts` rather than imported there, because that package
310
+ * cannot import this module: `render/stale.ts` records the cycle.
311
+ */
312
+ export function serviceRenderPaths(dir) {
313
+ return { model: join(dir, ARTIFACT_FILES.model), project: join(dir, LIKEC4_PROJECT_FILENAME) };
314
+ }
277
315
  //# sourceMappingURL=paths.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/repo/paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where every artifact lives. Artifact filenames are spelled here and nowhere\n * else, so a rename is one edit rather than a grep.\n *\n * This is also the module that carries loam's one path guarantee.\n * `unfiledServicePaths(docsDir, service)` spells `<docsDir>/services/<service>/`,\n * so both parts are caller-controlled path input, and `node:path` cannot help:\n * `join(...paths: string[])` accepts every string there is. The guarantee IS\n * the parameter brands and nothing else — `PathableService` for the id (a name\n * whose provenance is the repository, unconstructible from document text),\n * `DocsDir`/`FeatureDir` for the roots (a resolved config or validated\n * `--docs`; a directory an enumeration read), and `ServiceDir` for\n * `servicePathsAt` (the enumeration's own resolved directory, narrower still)\n * — the `kernel/ids/` package holds the only casts, so an unchecked value at\n * these call sites does not compile.\n * Code that spells `services/<id>/` or a feature layout with a bare join is\n * outside the guarantee — `commands/new.ts` and the openspec migrator both do,\n * held instead by `resolveInside` at the write.\n */\nimport { join } from \"node:path\";\nimport { serviceDirOf, type DocsDir, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport type { SubsystemName } from \"../kernel/ids/subsystem.js\";\n\n/** Directory under features/ holding shipped features. Never a feature itself. */\nexport const ARCHIVE_DIR = \"archive\";\n\n/* ------------------------------------------------------------------ */\n/* Paths — artifact filenames are spelled here and nowhere else */\n/* ------------------------------------------------------------------ */\n\n/**\n * The living artifact filenames, as one table, because three questions read\n * them: `servicePathsAt` below spells the paths, the tree walk classifies\n * a directory as a service by whether any of these names sit in it, and\n * `core/diff/base-state.ts` classifies the SAME way over a base git ref's\n * tree listing (exported for that reader — a filename respelled there would\n * be the drift this table exists to prevent). The walk cannot import this\n * module — `repo → repo/tree` is the package direction, and an edge back\n * would be the cycle `scripts/package-graph.mjs` refuses — so `repo.ts`\n * injects `isServiceArtifactName` into the walk request instead.\n */\nexport const ARTIFACT_FILES = {\n model: \"model.likec4\",\n spec: \"spec.md\",\n archSpec: \"arch.spec.md\",\n openapi: \"openapi.yaml\",\n asyncapi: \"asyncapi.yaml\",\n runbook: \"runbook.md\",\n health: \"health.yaml\",\n steps: \"steps.yaml\",\n} as const;\nconst ADRS_DIR = \"adrs\";\n\n/** Does a directory entry name a living service artifact? The classification half of the table above. */\nexport function isServiceArtifactName(name: string, kind: \"file\" | \"dir\"): boolean {\n if (kind === \"dir\") return name === ADRS_DIR;\n return Object.values(ARTIFACT_FILES).some((file) => file === name);\n}\n\nexport interface ServicePaths {\n dir: ServiceDir;\n model: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract — the event axis's sibling of `openapi`. */\n asyncapi: string;\n runbook: string;\n health: string;\n /**\n * The step catalogue — which phrases this service's suite has DECIDED to\n * define. Authored and opt-in; its CONTENTS are read by `loam steps` alone and\n * nothing in `validate` consults them\n * (`core/gherkin/steps/catalogue.ts` records why).\n *\n * Its EXISTENCE is read more widely, and that is not the same statement: the\n * filename is in `ARTIFACT_FILES`, so `isServiceArtifactName` classifies it\n * and the tree walk treats a directory holding one as a service. That is\n * correct — it is a service artifact — and it is worth writing down, because\n * it means a `steps.yaml` written at the wrong path mints a service.\n */\n steps: string;\n adrsDir: string;\n}\n\n/**\n * The artifact paths of a service whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the tree walk found it. This is the\n * spelling every reader of an existing service must use: joining\n * `services/<id>/` at the root is only true for a fleet nobody has filed, and\n * a moved service would silently grade as absent through it.\n */\nexport function servicePathsAt(dir: ServiceDir): ServicePaths {\n return {\n dir,\n model: join(dir, ARTIFACT_FILES.model),\n spec: join(dir, ARTIFACT_FILES.spec),\n archSpec: join(dir, ARTIFACT_FILES.archSpec),\n openapi: join(dir, ARTIFACT_FILES.openapi),\n asyncapi: join(dir, ARTIFACT_FILES.asyncapi),\n runbook: join(dir, ARTIFACT_FILES.runbook),\n health: join(dir, ARTIFACT_FILES.health),\n steps: join(dir, ARTIFACT_FILES.steps),\n adrsDir: join(dir, ADRS_DIR),\n };\n}\n\n/**\n * The UNFILED spelling — `<docsDir>/services/<id>/`, the tree's root level.\n * This is where creation lands (`adopt`'s brief, a new service materialised by\n * `archive`, the OpenSpec migration) and the honest fallback for a service the\n * enumeration does not answer; for a service that EXISTS, resolve through the\n * enumeration instead (`servicePathsAt(entry.dir)`, or\n * `locateServicePaths` in `service-target.ts`, which chooses between the two).\n * Renamed from `servicePaths` when the tree landed, precisely so the compiler\n * would put every remaining caller in front of a reviewer: a root join that\n * survives unreviewed is a moved service silently grading as absent.\n */\nexport function unfiledServicePaths(docsDir: DocsDir, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(docsDir, \"services\", service)));\n}\n\n/**\n * The creation spelling INSIDE a subsystem — `<subsystemDir>/<id>/`, the\n * target `adopt --subsystem` briefs so an adoption need not land unfiled and\n * cost a second command. The same two provenances the unfiled spelling\n * combines, one level down: the subsystem directory came off the tree walk's\n * readdir, and the id passed the service grammar before it reached here.\n */\nexport function servicePathsUnder(subsystemDir: string, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(subsystemDir, service)));\n}\n\n/**\n * The directory a SUBSYSTEM occupies (or will occupy) under a parent — the\n * spelling `subsystem new` creates and `subsystem rename` renames into. The\n * same guarantee-by-brand as the service spellings above: the parent came off\n * the tree walk's readdir (or is `servicesDir` itself), and demanding\n * `SubsystemName` here is what makes the validator unskippable — a raw argv\n * string at this call site does not compile, so no future verb can hoist the\n * join above the check that earns it.\n */\nexport function subsystemPathUnder(parentDir: string, name: SubsystemName): string {\n return join(parentDir, name);\n}\n\n/**\n * The pair of requirement-carrying spec files, living and delta alike: the\n * business spec and the architecture spec. Same grammar, same delta algebra,\n * same merge — everything that walks \"the spec files\" of a service walks this\n * list, so the two axes cannot drift apart in what handles them. `key` indexes\n * ServicePaths/FeatureSpecPaths; `label` is how the axis names itself in prose.\n */\nexport const SPEC_AXES = [\n { key: \"spec\", file: \"spec.md\", label: \"requirements\" },\n { key: \"archSpec\", file: \"arch.spec.md\", label: \"arch requirements\" },\n] as const;\nexport type SpecAxis = (typeof SPEC_AXES)[number];\n\nexport interface FeaturePaths {\n dir: string;\n intent: string;\n delta: string;\n specsDir: string;\n adrsDir: string;\n}\n\nexport function featurePaths(featureDir: FeatureDir): FeaturePaths {\n return {\n dir: featureDir,\n intent: join(featureDir, \"intent.md\"),\n delta: join(featureDir, \"delta.likec4\"),\n specsDir: join(featureDir, \"specs\"),\n adrsDir: join(featureDir, \"adrs\"),\n };\n}\n\nexport interface FeatureSpecPaths {\n dir: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract delta — the event axis's sibling of `openapi`. */\n asyncapi: string;\n}\n\nexport function featureSpecPaths(featureDir: FeatureDir, service: PathableService): FeatureSpecPaths {\n const dir = join(featureDir, \"specs\", service);\n return {\n dir,\n spec: join(dir, \"spec.md\"),\n archSpec: join(dir, \"arch.spec.md\"),\n openapi: join(dir, \"openapi.yaml\"),\n asyncapi: join(dir, \"asyncapi.yaml\"),\n };\n}\n\nexport function landscapePath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", LANDSCAPE_FILE);\n}\n\n/** The fleet's authorization vocabulary — beside the fleet map, for the same reason. */\nexport function permissionsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"permissions.yaml\");\n}\n\n/** The fleet's capability vocabulary — beside the other two fleet documents, for the same reason. */\nexport function capabilitiesPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"capabilities.yaml\");\n}\n\n/**\n * The fleet's ARCHITECTURAL OBLIGATION vocabulary — beside the landscape it tags\n * and the two vocabularies it sits with, for their reason: it is a fact about\n * the fleet that no single service owns.\n */\nexport function obligationsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"obligations.yaml\");\n}\n\n/**\n * The FLEET's decision records — `architecture/adrs/`, beside the landscape and\n * the two vocabularies.\n *\n * ADRs used to exist at two altitudes only, `services/<id>/adrs/` and\n * `features/<FEAT>/adrs/`, so a decision about the fleet itself — \"event\n * publishers use a transactional outbox\", \"cross-service calls carry a circuit\n * breaker\" — had no home but one arbitrary service's directory, where the next\n * reader of the other fifty services never finds it. This is not a new axis: it\n * is the same `ADRS_DIR` name one level up, spelled through the same constant so\n * the two altitudes cannot drift into `adrs/` and `decisions/`.\n *\n * PRESENCE-TRACKED AND NOTHING MORE, exactly like the service directory it\n * mirrors: `loam list` counts the files (`repo.ts`'s `fleetAdrCount`) and no\n * check anywhere reads one. A fleet with no fleet-level ADRs owes nothing and\n * must produce no finding — which is also why `loam init` does NOT scaffold the\n * directory. git does not carry an empty one, so it would vanish on the first\n * clone and come back as a diff on the next `init`; and an empty `adrs/` in a\n * fresh repo reads as an obligation nobody has met, when there is no obligation\n * at all.\n */\nexport function fleetAdrsDir(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", ADRS_DIR);\n}\n\n/**\n * The GENERATED subsystem views — beside the fleet map because the LikeC4\n * renderer merges the whole `architecture/` project, and a view can only\n * `include` elements the landscape beside it defines. `loam subsystem sync`\n * owns the bytes (`core/repo/tree/render/views.ts` renders them); nothing in loam\n * ever parses the file, and staleness against the tree is a byte compare.\n */\nexport function subsystemViewsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", SUBSYSTEM_VIEWS_FILE);\n}\n\n/** The generated views' file name, spelled once because the reservation below reads it too. */\nconst SUBSYSTEM_VIEWS_FILE = \"subsystems.likec4\";\n\n/**\n * Names inside `architecture/` that LOAM owns, and what goes wrong if a\n * feature's own document claims one.\n *\n * `features/<FEAT>/deployment/<name>.likec4` merges as a whole-file copy\n * straight into `architecture/` — unlike a flow, which lands under\n * `usecases/` — so its `<name>` is free to be any of these, and two of them\n * fail in ways nothing downstream would report:\n *\n * - `subsystems.likec4` is GENERATED and is EXCLUDED from every project load\n * (docs/DESIGN.md rule 26 keeps it a byte compare), so topology landing\n * there is invisible to loam from the moment it archives — and the next\n * `loam subsystem sync` overwrites the file wholesale. Silent both ways,\n * which is the worst shape a failure can have;\n * - `landscape.likec4` is the living map the archive SPLICES; a whole-file\n * copy over it would discard the fleet;\n * - `usecases/` is the flow tree, walked by the use-case scan. A deployment\n * document filed there parses (one project) and is then looked for by a\n * reader asking a different question.\n *\n * Returns the reason, or null when the name is the author's to choose. Takes\n * the `/`-separated rel a directory walk produced, never a string from argv.\n */\nexport function reservedArchitectureName(rel: string): string | null {\n const parts = rel.split(\"/\");\n if (parts[0] === USECASE_DIR) {\n return `\\`${USECASE_DIR}/\\` is the use-case tree — a flow belongs in \\`features/<FEAT>/usecases/\\`, which merges there`;\n }\n if (rel === SUBSYSTEM_VIEWS_FILE) {\n return \"`subsystems.likec4` is generated by `loam subsystem sync`, which overwrites it wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives\";\n }\n if (rel === LANDSCAPE_FILE) {\n return \"`landscape.likec4` is the living fleet map, which the archive SPLICES into rather than replaces\";\n }\n return null;\n}\n\n/** The living map's file name, and the flow tree's directory — spelled once, read by the reservation. */\nconst LANDSCAPE_FILE = \"landscape.likec4\";\nconst USECASE_DIR = \"usecases\";\n\nexport function featuresDir(docsDir: DocsDir): string {\n return join(docsDir, \"features\");\n}\n\n/**\n * The root of the tree that IS the fleet — where an unfiled service and a\n * root-level subsystem both sit. The `subsystem` verbs join validated names\n * under it; the enumerating walk spells the same join for itself\n * (`repo/tree/walk.ts`), because it cannot import this module.\n */\nexport function servicesDir(docsDir: DocsDir): string {\n return join(docsDir, \"services\");\n}\n\n/**\n * The agent contract's filename, and where it sits in a docs repo.\n *\n * Both spellings are exported because the two questions are different: `init`,\n * `doctor` and `validate` want a path under a docsDir they already hold, while\n * `docs.ts` builds the scaffold as `[relative path, bytes]` pairs and needs the\n * bare name. Four modules spelled the literal out for themselves, and one of\n * them — `init` — uses it to decide whether a directory IS a docs repo at all,\n * so a rename that reached three of the four would leave `init` recognising a\n * file nothing else writes any more.\n *\n * `openspec-inventory.ts`'s \"AGENTS.md\" is deliberately NOT this constant: it\n * names a file in a foreign OpenSpec tree that happens to share the name.\n */\nexport const AGENTS_FILENAME = \"AGENTS.md\";\n\nexport function agentsPath(docsDir: DocsDir): string {\n return join(docsDir, AGENTS_FILENAME);\n}\n\n/**\n * Where shipped features live. `features/archive/` is spelled here and nowhere\n * else for the same reason every other artifact path is: `archive` and\n * `unarchive` move directories in and out of it, and a layout string that two\n * commands re-derive is a layout two commands can disagree about. The\n * `features/archive/…` strings in refusal PROSE are not this path — they are\n * what a reader types into `ls`, and they stay spelled out.\n */\nexport function archiveDir(docsDir: DocsDir): string {\n return join(featuresDir(docsDir), ARCHIVE_DIR);\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/repo/paths.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where every artifact lives. Artifact filenames are spelled here and nowhere\n * else, so a rename is one edit rather than a grep.\n *\n * This is also the module that carries loam's one path guarantee.\n * `unfiledServicePaths(docsDir, service)` spells `<docsDir>/services/<service>/`,\n * so both parts are caller-controlled path input, and `node:path` cannot help:\n * `join(...paths: string[])` accepts every string there is. The guarantee IS\n * the parameter brands and nothing else — `PathableService` for the id (a name\n * whose provenance is the repository, unconstructible from document text),\n * `DocsDir`/`FeatureDir` for the roots (a resolved config or validated\n * `--docs`; a directory an enumeration read), and `ServiceDir` for\n * `servicePathsAt` (the enumeration's own resolved directory, narrower still)\n * — the `kernel/ids/` package holds the only casts, so an unchecked value at\n * these call sites does not compile.\n * Code that spells `services/<id>/` or a feature layout with a bare join is\n * outside the guarantee — `commands/new.ts` and the openspec migrator both do,\n * held instead by `resolveInside` at the write.\n */\nimport { join } from \"node:path\";\nimport { serviceDirOf, type DocsDir, type FeatureDir, type ServiceDir } from \"../kernel/ids/dirs.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport type { SubsystemName } from \"../kernel/ids/subsystem.js\";\n\n/** Directory under features/ holding shipped features. Never a feature itself. */\nexport const ARCHIVE_DIR = \"archive\";\n\n/* ------------------------------------------------------------------ */\n/* Paths — artifact filenames are spelled here and nowhere else */\n/* ------------------------------------------------------------------ */\n\n/**\n * The living artifact filenames, as one table, because three questions read\n * them: `servicePathsAt` below spells the paths, the tree walk classifies\n * a directory as a service by whether any of these names sit in it, and\n * `core/diff/base-state.ts` classifies the SAME way over a base git ref's\n * tree listing (exported for that reader — a filename respelled there would\n * be the drift this table exists to prevent). The walk cannot import this\n * module — `repo → repo/tree` is the package direction, and an edge back\n * would be the cycle `scripts/package-graph.mjs` refuses — so `repo.ts`\n * injects `isServiceArtifactName` into the walk request instead.\n */\nexport const ARTIFACT_FILES = {\n model: \"model.likec4\",\n spec: \"spec.md\",\n archSpec: \"arch.spec.md\",\n openapi: \"openapi.yaml\",\n asyncapi: \"asyncapi.yaml\",\n runbook: \"runbook.md\",\n health: \"health.yaml\",\n steps: \"steps.yaml\",\n} as const;\nconst ADRS_DIR = \"adrs\";\n\n/** Does a directory entry name a living service artifact? The classification half of the table above. */\nexport function isServiceArtifactName(name: string, kind: \"file\" | \"dir\"): boolean {\n if (kind === \"dir\") return name === ADRS_DIR;\n return Object.values(ARTIFACT_FILES).some((file) => file === name);\n}\n\nexport interface ServicePaths {\n dir: ServiceDir;\n model: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract — the event axis's sibling of `openapi`. */\n asyncapi: string;\n runbook: string;\n health: string;\n /**\n * The step catalogue — which phrases this service's suite has DECIDED to\n * define. Authored and opt-in; its CONTENTS are read by `loam steps` alone and\n * nothing in `validate` consults them\n * (`core/gherkin/steps/catalogue.ts` records why).\n *\n * Its EXISTENCE is read more widely, and that is not the same statement: the\n * filename is in `ARTIFACT_FILES`, so `isServiceArtifactName` classifies it\n * and the tree walk treats a directory holding one as a service. That is\n * correct — it is a service artifact — and it is worth writing down, because\n * it means a `steps.yaml` written at the wrong path mints a service.\n */\n steps: string;\n adrsDir: string;\n}\n\n/**\n * The artifact paths of a service whose directory is already KNOWN — the\n * enumeration's `dir`, at whatever depth the tree walk found it. This is the\n * spelling every reader of an existing service must use: joining\n * `services/<id>/` at the root is only true for a fleet nobody has filed, and\n * a moved service would silently grade as absent through it.\n */\nexport function servicePathsAt(dir: ServiceDir): ServicePaths {\n return {\n dir,\n model: join(dir, ARTIFACT_FILES.model),\n spec: join(dir, ARTIFACT_FILES.spec),\n archSpec: join(dir, ARTIFACT_FILES.archSpec),\n openapi: join(dir, ARTIFACT_FILES.openapi),\n asyncapi: join(dir, ARTIFACT_FILES.asyncapi),\n runbook: join(dir, ARTIFACT_FILES.runbook),\n health: join(dir, ARTIFACT_FILES.health),\n steps: join(dir, ARTIFACT_FILES.steps),\n adrsDir: join(dir, ADRS_DIR),\n };\n}\n\n/**\n * The UNFILED spelling — `<docsDir>/services/<id>/`, the tree's root level.\n * This is where creation lands (`adopt`'s brief, a new service materialised by\n * `archive`, the OpenSpec migration) and the honest fallback for a service the\n * enumeration does not answer; for a service that EXISTS, resolve through the\n * enumeration instead (`servicePathsAt(entry.dir)`, or\n * `locateServicePaths` in `service-target.ts`, which chooses between the two).\n * Renamed from `servicePaths` when the tree landed, precisely so the compiler\n * would put every remaining caller in front of a reviewer: a root join that\n * survives unreviewed is a moved service silently grading as absent.\n */\nexport function unfiledServicePaths(docsDir: DocsDir, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(docsDir, \"services\", service)));\n}\n\n/**\n * The creation spelling INSIDE a subsystem — `<subsystemDir>/<id>/`, the\n * target `adopt --subsystem` briefs so an adoption need not land unfiled and\n * cost a second command. The same two provenances the unfiled spelling\n * combines, one level down: the subsystem directory came off the tree walk's\n * readdir, and the id passed the service grammar before it reached here.\n */\nexport function servicePathsUnder(subsystemDir: string, service: PathableService): ServicePaths {\n return servicePathsAt(serviceDirOf(join(subsystemDir, service)));\n}\n\n/**\n * The directory a SUBSYSTEM occupies (or will occupy) under a parent — the\n * spelling `subsystem new` creates and `subsystem rename` renames into. The\n * same guarantee-by-brand as the service spellings above: the parent came off\n * the tree walk's readdir (or is `servicesDir` itself), and demanding\n * `SubsystemName` here is what makes the validator unskippable — a raw argv\n * string at this call site does not compile, so no future verb can hoist the\n * join above the check that earns it.\n */\nexport function subsystemPathUnder(parentDir: string, name: SubsystemName): string {\n return join(parentDir, name);\n}\n\n/**\n * The pair of requirement-carrying spec files, living and delta alike: the\n * business spec and the architecture spec. Same grammar, same delta algebra,\n * same merge — everything that walks \"the spec files\" of a service walks this\n * list, so the two axes cannot drift apart in what handles them. `key` indexes\n * ServicePaths/FeatureSpecPaths; `label` is how the axis names itself in prose.\n */\nexport const SPEC_AXES = [\n { key: \"spec\", file: \"spec.md\", label: \"requirements\" },\n { key: \"archSpec\", file: \"arch.spec.md\", label: \"arch requirements\" },\n] as const;\nexport type SpecAxis = (typeof SPEC_AXES)[number];\n\nexport interface FeaturePaths {\n dir: string;\n intent: string;\n delta: string;\n specsDir: string;\n adrsDir: string;\n}\n\nexport function featurePaths(featureDir: FeatureDir): FeaturePaths {\n return {\n dir: featureDir,\n intent: join(featureDir, \"intent.md\"),\n delta: join(featureDir, \"delta.likec4\"),\n specsDir: join(featureDir, \"specs\"),\n adrsDir: join(featureDir, \"adrs\"),\n };\n}\n\nexport interface FeatureSpecPaths {\n dir: string;\n spec: string;\n archSpec: string;\n openapi: string;\n /** The async contract delta — the event axis's sibling of `openapi`. */\n asyncapi: string;\n}\n\nexport function featureSpecPaths(featureDir: FeatureDir, service: PathableService): FeatureSpecPaths {\n const dir = join(featureDir, \"specs\", service);\n return {\n dir,\n spec: join(dir, \"spec.md\"),\n archSpec: join(dir, \"arch.spec.md\"),\n openapi: join(dir, \"openapi.yaml\"),\n asyncapi: join(dir, \"asyncapi.yaml\"),\n };\n}\n\nexport function landscapePath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", LANDSCAPE_FILE);\n}\n\n/** The fleet's authorization vocabulary — beside the fleet map, for the same reason. */\nexport function permissionsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"permissions.yaml\");\n}\n\n/** The fleet's capability vocabulary — beside the other two fleet documents, for the same reason. */\nexport function capabilitiesPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"capabilities.yaml\");\n}\n\n/**\n * The fleet's ARCHITECTURAL OBLIGATION vocabulary — beside the landscape it tags\n * and the two vocabularies it sits with, for their reason: it is a fact about\n * the fleet that no single service owns.\n */\nexport function obligationsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", \"obligations.yaml\");\n}\n\n/**\n * The FLEET's decision records — `architecture/adrs/`, beside the landscape and\n * the two vocabularies.\n *\n * ADRs used to exist at two altitudes only, `services/<id>/adrs/` and\n * `features/<FEAT>/adrs/`, so a decision about the fleet itself — \"event\n * publishers use a transactional outbox\", \"cross-service calls carry a circuit\n * breaker\" — had no home but one arbitrary service's directory, where the next\n * reader of the other fifty services never finds it. This is not a new axis: it\n * is the same `ADRS_DIR` name one level up, spelled through the same constant so\n * the two altitudes cannot drift into `adrs/` and `decisions/`.\n *\n * PRESENCE-TRACKED AND NOTHING MORE, exactly like the service directory it\n * mirrors: `loam list` counts the files (`repo.ts`'s `fleetAdrCount`) and no\n * check anywhere reads one. A fleet with no fleet-level ADRs owes nothing and\n * must produce no finding — which is also why `loam init` does NOT scaffold the\n * directory. git does not carry an empty one, so it would vanish on the first\n * clone and come back as a diff on the next `init`; and an empty `adrs/` in a\n * fresh repo reads as an obligation nobody has met, when there is no obligation\n * at all.\n */\nexport function fleetAdrsDir(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", ADRS_DIR);\n}\n\n/**\n * The GENERATED subsystem views — beside the fleet map because the LikeC4\n * renderer merges the whole `architecture/` project, and a view can only\n * `include` elements the landscape beside it defines. `loam subsystem sync`\n * owns the bytes (`core/repo/tree/render/views.ts` renders them); nothing in loam\n * ever parses the file, and staleness against the tree is a byte compare.\n */\nexport function subsystemViewsPath(docsDir: DocsDir): string {\n return join(docsDir, \"architecture\", SUBSYSTEM_VIEWS_FILE);\n}\n\n/** The generated views' file name, spelled once because the reservation below reads it too. */\nconst SUBSYSTEM_VIEWS_FILE = \"subsystems.likec4\";\n\n/**\n * Names inside `architecture/` that LOAM owns, and what goes wrong if a\n * feature's own document claims one.\n *\n * `features/<FEAT>/deployment/<name>.likec4` merges as a whole-file copy\n * straight into `architecture/` — unlike a flow, which lands under\n * `usecases/` — so its `<name>` is free to be any of these, and two of them\n * fail in ways nothing downstream would report:\n *\n * - `subsystems.likec4` is GENERATED and is EXCLUDED from every project load\n * (docs/DESIGN.md rule 26 keeps it a byte compare), so topology landing\n * there is invisible to loam from the moment it archives — and the next\n * `loam subsystem sync` overwrites the file wholesale. Silent both ways,\n * which is the worst shape a failure can have;\n * - `landscape.likec4` is the living map the archive SPLICES; a whole-file\n * copy over it would discard the fleet;\n * - `usecases/` is the flow tree, walked by the use-case scan. A deployment\n * document filed there parses (one project) and is then looked for by a\n * reader asking a different question.\n *\n * Returns the reason, or null when the name is the author's to choose. Takes\n * the `/`-separated rel a directory walk produced, never a string from argv.\n */\nexport function reservedArchitectureName(rel: string): string | null {\n const parts = rel.split(\"/\");\n if (parts[0] === USECASE_DIR) {\n return `\\`${USECASE_DIR}/\\` is the use-case tree — a flow belongs in \\`features/<FEAT>/usecases/\\`, which merges there`;\n }\n if (rel === SUBSYSTEM_VIEWS_FILE) {\n return \"`subsystems.likec4` is generated by `loam subsystem sync`, which overwrites it wholesale — and nothing in loam ever parses it, so anything landing there is invisible from the moment it archives\";\n }\n if (rel === LANDSCAPE_FILE) {\n return \"`landscape.likec4` is the living fleet map, which the archive SPLICES into rather than replaces\";\n }\n return null;\n}\n\n/** The living map's file name, and the flow tree's directory — spelled once, read by the reservation. */\nconst LANDSCAPE_FILE = \"landscape.likec4\";\nconst USECASE_DIR = \"usecases\";\n\nexport function featuresDir(docsDir: DocsDir): string {\n return join(docsDir, \"features\");\n}\n\n/**\n * The root of the tree that IS the fleet — where an unfiled service and a\n * root-level subsystem both sit. The `subsystem` verbs join validated names\n * under it; the enumerating walk spells the same join for itself\n * (`repo/tree/walk.ts`), because it cannot import this module.\n */\nexport function servicesDir(docsDir: DocsDir): string {\n return join(docsDir, \"services\");\n}\n\n/**\n * The agent contract's filename, and where it sits in a docs repo.\n *\n * Both spellings are exported because the two questions are different: `init`,\n * `doctor` and `validate` want a path under a docsDir they already hold, while\n * `docs.ts` builds the scaffold as `[relative path, bytes]` pairs and needs the\n * bare name. Four modules spelled the literal out for themselves, and one of\n * them — `init` — uses it to decide whether a directory IS a docs repo at all,\n * so a rename that reached three of the four would leave `init` recognising a\n * file nothing else writes any more.\n *\n * `openspec-inventory.ts`'s \"AGENTS.md\" is deliberately NOT this constant: it\n * names a file in a foreign OpenSpec tree that happens to share the name.\n */\nexport const AGENTS_FILENAME = \"AGENTS.md\";\n\nexport function agentsPath(docsDir: DocsDir): string {\n return join(docsDir, AGENTS_FILENAME);\n}\n\n/**\n * Where shipped features live. `features/archive/` is spelled here and nowhere\n * else for the same reason every other artifact path is: `archive` and\n * `unarchive` move directories in and out of it, and a layout string that two\n * commands re-derive is a layout two commands can disagree about. The\n * `features/archive/…` strings in refusal PROSE are not this path — they are\n * what a reader types into `ls`, and they stay spelled out.\n */\nexport function archiveDir(docsDir: DocsDir): string {\n return join(featuresDir(docsDir), ARCHIVE_DIR);\n}\n\n/**\n * The LikeC4 project file — the one thing in a docs repo loam writes for a\n * tool other than itself — and it lives at two altitudes.\n *\n * At the ROOT, `loam init --create` writes the project `fleet`, scoped to\n * `architecture/` (`core/docs.ts` composes those bytes). Beside each\n * `services/<…>/<id>/model.likec4`, `loam subsystem sync` writes a project of\n * the service's own: loam parses every `.likec4` file ALONE, so each model\n * declares its own `specification` block and the root project must exclude\n * `services/**` — which left every adopted service a box on the fleet map with\n * nothing renderable inside it, in the renderer opened at the docs root.\n * `core/repo/tree/render/projects.ts` composes the per-service bytes and\n * records what was measured.\n *\n * Spelled here and NOT in `ARTIFACT_FILES`, and the distinction is\n * load-bearing: a filename in that table classifies a directory as a service,\n * and this file is the renderer's — a stray one must mint nothing. The root's\n * NAME sits beside the filename because the per-service rule needs it too (a\n * service id equal to the root project's name would be silently renamed by\n * the renderer), and two spellings of the word are how the scaffold and the\n * rule drift apart.\n */\nexport const LIKEC4_PROJECT_FILENAME = \"likec4.config.json\";\n\n/** The root project's name, exactly as `loam init --create` scaffolds it. */\nexport const LIKEC4_ROOT_PROJECT = \"fleet\";\n\nexport function rootProjectPath(docsDir: DocsDir): string {\n return join(docsDir, LIKEC4_PROJECT_FILENAME);\n}\n\n/**\n * The two files the per-service project question is asked of — the model that\n * needs rendering and the project file that renders it — in one spelling for\n * the writer (`subsystem sync`) and the grader (`validate --all`) alike. Handed\n * INTO `render/projects.ts` rather than imported there, because that package\n * cannot import this module: `render/stale.ts` records the cycle.\n */\nexport function serviceRenderPaths(dir: ServiceDir): { model: string; project: string } {\n return { model: join(dir, ARTIFACT_FILES.model), project: join(dir, LIKEC4_PROJECT_FILENAME) };\n}\n"]}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The renderer's project file for ONE service — `services/<…>/<id>/likec4.config.json`
3
+ * — the second thing under `services/` that exists only to be rendered.
4
+ *
5
+ * Why it exists. The root `likec4.config.json` scopes the fleet project to
6
+ * `architecture/` and excludes `services/**`, and must: loam parses every
7
+ * `.likec4` file ALONE, so each `model.likec4` declares its own `specification`
8
+ * block, and a renderer merging the tree reports every declaration as a
9
+ * duplicate. The cost of that exclusion went unstated for a release: an
10
+ * adopted service's model — the artifact `loam adopt` spends the most effort
11
+ * on — was a box on the fleet map with nothing renderable inside it from the
12
+ * docs root, and the documented way to see it was one renderer invocation per
13
+ * service directory. Measured at the 1.59.2 pin: a `likec4.config.json` INSIDE
14
+ * a service directory is registered as a project of its own even though the
15
+ * root excludes the directory (the root's `exclude` does not hide a nested
16
+ * project file), the workspace then loads `fleet` plus one project per service
17
+ * with zero errors, and each service project carries LikeC4's own `index` view
18
+ * of the containers beside whatever view the model authors. One two-key file
19
+ * per service directory is therefore what makes the model renderable from the
20
+ * same root, in the same renderer, beside the map.
21
+ *
22
+ * CREATE-ONLY, never compared, never rewritten, never removed — the root
23
+ * config's ownership rule (`core/docs.ts`: written once, never re-read, the
24
+ * team owns it), NOT the generated views file's. The bytes are a function of
25
+ * the service id alone, which SCHEMA.md defines as the identity no verb ever
26
+ * changes, so nothing can make the file stale; a compare would only fight the
27
+ * per-project keys LikeC4 lets a team add (`title`, styles, a contact), and
28
+ * would restale every service on any change to this rule. Presence is the one
29
+ * question loam owes the file, and presence needs no parse: rule 26 stands,
30
+ * and loam still reads none of it. A leftover file after a model is deleted is
31
+ * the team's, and harmless — measured, a nested project holding no `.likec4`
32
+ * file is silently absent from the renderer's project list.
33
+ *
34
+ * THE NAME. LikeC4's project-name grammar (measured at the pin) refuses an
35
+ * empty name, the word `default`, and the bytes `.`, `@` and `#`; loam's
36
+ * service-id grammar admits `.`. So every `.` becomes `-`, and a name that
37
+ * would then equal the ROOT project's (`fleet` — a nested project of that name
38
+ * is silently registered as `fleet-1`) or LikeC4's reserved `default` is
39
+ * prefixed `service-`. Readable and typeable after `--project`, and NOT
40
+ * injective: an id spelled a.b beside a service literally named a-b, or
41
+ * `service-fleet` beside `fleet`, share a name and the renderer suffixes the
42
+ * later one `-1`. That is disclosed rather than escaped away, because the
43
+ * picker shows the `title` (measured: `title ?? id`), which is ALWAYS the id
44
+ * verbatim, and a collision costs one suffixed label rather than the blanked
45
+ * workspace that made `subsystemViewId` injective. The root's name arrives as
46
+ * a parameter, like every spelling this package needs from `repo/`: `render/`
47
+ * cannot import `repo/paths.ts` (`stale.ts` records the cycle), and a second
48
+ * copy of the word `fleet` here would be the drift the injection prevents.
49
+ *
50
+ * A DIRECTORY THAT IS NOT A LEGAL SERVICE ID IS OWED NO FILE. The tree walk
51
+ * enumerates every directory on purpose — the fleet is never reported smaller
52
+ * than it is — and `service.id-invalid` names the illegal ones. Folding only
53
+ * `.` is right for every LEGAL id, whose remaining bytes LikeC4 accepts; a
54
+ * name like `pay@1` would produce a project file the renderer silently drops
55
+ * (measured: zero errors, the project simply absent from the list), and
56
+ * because the file is create-only the grade below would then be silent
57
+ * forever while the model stayed unrendered — the exact state this file
58
+ * exists to remove, with a label saying it was fixed. So the survey skips
59
+ * such a service: the writer creates nothing, the grader says nothing, and
60
+ * `service.id-invalid`'s own repair — the rename — is what earns the file on
61
+ * the first sync after it.
62
+ *
63
+ * NO MODEL, NO FILE: a service directory without `model.likec4` — one an
64
+ * archive materialised, a spec-only baseline — is owed nothing, and gets its
65
+ * file on the first `loam subsystem sync` after the model lands. A feature's
66
+ * `delta.likec4` is deliberately out of scope: the directory is transient and
67
+ * `loam archive` moves it where a project file would still be registered, so
68
+ * every shipped feature would keep a dead project in the list forever.
69
+ */
70
+ import { existsSync } from "node:fs";
71
+ import { dirNameHazard } from "../../../kernel/ids/service.js";
72
+ /** LikeC4's own reserved project name; a file naming it is refused outright. */
73
+ const LIKEC4_RESERVED_PROJECT = "default";
74
+ /**
75
+ * The project name for a service id — the id itself for every id made of
76
+ * letters, digits, `_` and `-`, which is nearly all of them.
77
+ */
78
+ export function serviceProjectName(id, rootProject) {
79
+ const folded = id.replace(/\./g, "-");
80
+ return folded === rootProject || folded === LIKEC4_RESERVED_PROJECT ? `service-${folded}` : folded;
81
+ }
82
+ /**
83
+ * The exact bytes of a service's project file: two keys, two-space JSON, LF,
84
+ * one trailing newline — the root file's own serialisation. `title` is the id
85
+ * verbatim, always, so whatever the renderer labels a project with is the
86
+ * service's real identity even where `name` had to bend.
87
+ */
88
+ export function renderServiceProject(id, rootProject) {
89
+ return `${JSON.stringify({ name: serviceProjectName(id, rootProject), title: id }, null, 2)}\n`;
90
+ }
91
+ /**
92
+ * ONE predicate for the writer (`subsystem sync`) and the grader
93
+ * (`validate --all`), on `viewsState`'s precedent — two private spellings of
94
+ * "is this model a project" would be two chances to answer differently about
95
+ * one file, and a grader naming a gap the writer would not fill is a loop no
96
+ * command can clear. Both the gaps AND the count live here for the same
97
+ * reason: a caller re-deriving "has a model" beside this function is the
98
+ * second spelling in another coat.
99
+ *
100
+ * Structural over `{ id, dir }` so a walked service and a full entry both fit,
101
+ * and the two file spellings arrive injected (`repo/paths.ts`'s
102
+ * `serviceRenderPaths`) for the package reason the banner gives. Sorted by id,
103
+ * plain lexicographic like every list this package emits, so the writer's
104
+ * report and the grader's findings come out in one stable order.
105
+ */
106
+ export function surveyProjects(services, pathsOf) {
107
+ const gaps = [];
108
+ let modelled = 0;
109
+ for (const service of services) {
110
+ // The banner's rule: a name the renderer would refuse earns no file, and
111
+ // `service.id-invalid` — not this survey — is what names the repair.
112
+ if (dirNameHazard(service.id) !== null)
113
+ continue;
114
+ const paths = pathsOf(service.dir);
115
+ if (!existsSync(paths.model))
116
+ continue;
117
+ modelled += 1;
118
+ if (!existsSync(paths.project))
119
+ gaps.push({ service, path: paths.project });
120
+ }
121
+ gaps.sort((a, b) => (a.service.id < b.service.id ? -1 : a.service.id > b.service.id ? 1 : 0));
122
+ return { gaps, modelled };
123
+ }
124
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../../../src/core/repo/tree/render/projects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,gFAAgF;AAChF,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAU,EAAE,WAAmB;IAChE,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtC,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,uBAAuB,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACrG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAU,EAAE,WAAmB;IAClE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAClG,CAAC;AA6BD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAsB,EACtB,OAAgE;IAEhE,MAAM,IAAI,GAAoB,EAAE,CAAC;IACjC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,yEAAyE;QACzE,qEAAqE;QACrE,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI;YAAE,SAAS;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,SAAS;QACvC,QAAQ,IAAI,CAAC,CAAC;QACd,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * The renderer's project file for ONE service — `services/<…>/<id>/likec4.config.json`\n * — the second thing under `services/` that exists only to be rendered.\n *\n * Why it exists. The root `likec4.config.json` scopes the fleet project to\n * `architecture/` and excludes `services/**`, and must: loam parses every\n * `.likec4` file ALONE, so each `model.likec4` declares its own `specification`\n * block, and a renderer merging the tree reports every declaration as a\n * duplicate. The cost of that exclusion went unstated for a release: an\n * adopted service's model — the artifact `loam adopt` spends the most effort\n * on — was a box on the fleet map with nothing renderable inside it from the\n * docs root, and the documented way to see it was one renderer invocation per\n * service directory. Measured at the 1.59.2 pin: a `likec4.config.json` INSIDE\n * a service directory is registered as a project of its own even though the\n * root excludes the directory (the root's `exclude` does not hide a nested\n * project file), the workspace then loads `fleet` plus one project per service\n * with zero errors, and each service project carries LikeC4's own `index` view\n * of the containers beside whatever view the model authors. One two-key file\n * per service directory is therefore what makes the model renderable from the\n * same root, in the same renderer, beside the map.\n *\n * CREATE-ONLY, never compared, never rewritten, never removed — the root\n * config's ownership rule (`core/docs.ts`: written once, never re-read, the\n * team owns it), NOT the generated views file's. The bytes are a function of\n * the service id alone, which SCHEMA.md defines as the identity no verb ever\n * changes, so nothing can make the file stale; a compare would only fight the\n * per-project keys LikeC4 lets a team add (`title`, styles, a contact), and\n * would restale every service on any change to this rule. Presence is the one\n * question loam owes the file, and presence needs no parse: rule 26 stands,\n * and loam still reads none of it. A leftover file after a model is deleted is\n * the team's, and harmless — measured, a nested project holding no `.likec4`\n * file is silently absent from the renderer's project list.\n *\n * THE NAME. LikeC4's project-name grammar (measured at the pin) refuses an\n * empty name, the word `default`, and the bytes `.`, `@` and `#`; loam's\n * service-id grammar admits `.`. So every `.` becomes `-`, and a name that\n * would then equal the ROOT project's (`fleet` — a nested project of that name\n * is silently registered as `fleet-1`) or LikeC4's reserved `default` is\n * prefixed `service-`. Readable and typeable after `--project`, and NOT\n * injective: an id spelled a.b beside a service literally named a-b, or\n * `service-fleet` beside `fleet`, share a name and the renderer suffixes the\n * later one `-1`. That is disclosed rather than escaped away, because the\n * picker shows the `title` (measured: `title ?? id`), which is ALWAYS the id\n * verbatim, and a collision costs one suffixed label rather than the blanked\n * workspace that made `subsystemViewId` injective. The root's name arrives as\n * a parameter, like every spelling this package needs from `repo/`: `render/`\n * cannot import `repo/paths.ts` (`stale.ts` records the cycle), and a second\n * copy of the word `fleet` here would be the drift the injection prevents.\n *\n * A DIRECTORY THAT IS NOT A LEGAL SERVICE ID IS OWED NO FILE. The tree walk\n * enumerates every directory on purpose — the fleet is never reported smaller\n * than it is — and `service.id-invalid` names the illegal ones. Folding only\n * `.` is right for every LEGAL id, whose remaining bytes LikeC4 accepts; a\n * name like `pay@1` would produce a project file the renderer silently drops\n * (measured: zero errors, the project simply absent from the list), and\n * because the file is create-only the grade below would then be silent\n * forever while the model stayed unrendered — the exact state this file\n * exists to remove, with a label saying it was fixed. So the survey skips\n * such a service: the writer creates nothing, the grader says nothing, and\n * `service.id-invalid`'s own repair — the rename — is what earns the file on\n * the first sync after it.\n *\n * NO MODEL, NO FILE: a service directory without `model.likec4` — one an\n * archive materialised, a spec-only baseline — is owed nothing, and gets its\n * file on the first `loam subsystem sync` after the model lands. A feature's\n * `delta.likec4` is deliberately out of scope: the directory is transient and\n * `loam archive` moves it where a project file would still be registered, so\n * every shipped feature would keep a dead project in the list forever.\n */\nimport { existsSync } from \"node:fs\";\nimport type { ServiceDir } from \"../../../kernel/ids/dirs.js\";\nimport { dirNameHazard } from \"../../../kernel/ids/service.js\";\n\n/** LikeC4's own reserved project name; a file naming it is refused outright. */\nconst LIKEC4_RESERVED_PROJECT = \"default\";\n\n/**\n * The project name for a service id — the id itself for every id made of\n * letters, digits, `_` and `-`, which is nearly all of them.\n */\nexport function serviceProjectName(id: string, rootProject: string): string {\n const folded = id.replace(/\\./g, \"-\");\n return folded === rootProject || folded === LIKEC4_RESERVED_PROJECT ? `service-${folded}` : folded;\n}\n\n/**\n * The exact bytes of a service's project file: two keys, two-space JSON, LF,\n * one trailing newline — the root file's own serialisation. `title` is the id\n * verbatim, always, so whatever the renderer labels a project with is the\n * service's real identity even where `name` had to bend.\n */\nexport function renderServiceProject(id: string, rootProject: string): string {\n return `${JSON.stringify({ name: serviceProjectName(id, rootProject), title: id }, null, 2)}\\n`;\n}\n\n/**\n * A service whose model has no project file beside it, and where the file\n * goes. The SERVICE travels whole — a walked service or a full entry,\n * whichever the caller surveyed — rather than its id: the grader spells the\n * finding's path from the entry's own placement, and a join back by id would\n * name the wrong directory the moment two services share a leaf name under\n * different subsystems (`subsystem.name-collision` — a broken tree the walk\n * still enumerates in full, and one this grade is emitted on by design).\n */\nexport interface ProjectGap<T> {\n service: T;\n /** Absolute path of the file `loam subsystem sync` will create. */\n path: string;\n}\n\n/** What one survey of the tree found: the gaps, and the domain they were counted against. */\nexport interface ProjectSurvey<T> {\n /** Every service owed a file and lacking one, sorted by id. */\n gaps: ProjectGap<T>[];\n /**\n * Every service the survey considered at all — a model on disk, a legal id\n * — file or no file. `modelled - gaps.length` is therefore \"already had\n * one\", derived from the same walk rather than counted a second way.\n */\n modelled: number;\n}\n\n/**\n * ONE predicate for the writer (`subsystem sync`) and the grader\n * (`validate --all`), on `viewsState`'s precedent — two private spellings of\n * \"is this model a project\" would be two chances to answer differently about\n * one file, and a grader naming a gap the writer would not fill is a loop no\n * command can clear. Both the gaps AND the count live here for the same\n * reason: a caller re-deriving \"has a model\" beside this function is the\n * second spelling in another coat.\n *\n * Structural over `{ id, dir }` so a walked service and a full entry both fit,\n * and the two file spellings arrive injected (`repo/paths.ts`'s\n * `serviceRenderPaths`) for the package reason the banner gives. Sorted by id,\n * plain lexicographic like every list this package emits, so the writer's\n * report and the grader's findings come out in one stable order.\n */\nexport function surveyProjects<T extends { id: string; dir: ServiceDir }>(\n services: readonly T[],\n pathsOf: (dir: ServiceDir) => { model: string; project: string },\n): ProjectSurvey<T> {\n const gaps: ProjectGap<T>[] = [];\n let modelled = 0;\n for (const service of services) {\n // The banner's rule: a name the renderer would refuse earns no file, and\n // `service.id-invalid` — not this survey — is what names the repair.\n if (dirNameHazard(service.id) !== null) continue;\n const paths = pathsOf(service.dir);\n if (!existsSync(paths.model)) continue;\n modelled += 1;\n if (!existsSync(paths.project)) gaps.push({ service, path: paths.project });\n }\n gaps.sort((a, b) => (a.service.id < b.service.id ? -1 : a.service.id > b.service.id ? 1 : 0));\n return { gaps, modelled };\n}\n"]}
@@ -145,7 +145,12 @@ contracts and digests are byte-identical to their unfiled days, every command ad
145
145
  id, and `architecture/subsystems.likec4` — GENERATED by `loam subsystem sync`, one view per
146
146
  subsystem, one `include` per line — is the only file the grouping added. Edit the tree with
147
147
  `loam subsystem move|rename|rm` and the views file travels in the same transaction; edit it by hand
148
- and `validate --all` answers `subsystem.views-stale`. (`order-service` and `payment-service` stay
148
+ and `validate --all` answers `subsystem.views-stale`. Each of the five services also carries a
149
+ `likec4.config.json` — written by `loam subsystem sync`, create-only, never graded for content —
150
+ because the root project excludes `services/**` and that file is what registers a service's
151
+ `model.likec4` as a LikeC4 project of its own: `npx likec4 validate examples/docs` names six
152
+ projects, `fleet` and the five services, and with more than one project it needs `--project fleet`
153
+ (or a service's name) to grade one. (`order-service` and `payment-service` stay
149
154
  unfiled deliberately: FEAT-088's committed version-2 snapshot restores them by literal path, and the
150
155
  `loam unarchive FEAT-088` walkthrough must keep working byte-for-byte.)
151
156
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "checkout-web",
3
+ "title": "checkout-web"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "order-service",
3
+ "title": "order-service"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "payment-service",
3
+ "title": "payment-service"
4
+ }
@@ -2,8 +2,10 @@
2
2
  //
3
3
  // loam parses every `.likec4` file ALONE, so this document declares its own
4
4
  // `specification` block; a renderer handed the whole tree at once would merge
5
- // them and call every declaration a duplicate, which is what
6
- // `likec4.config.json` scopes the root project to `architecture/` for.
5
+ // them and call every declaration a duplicate, which is why the root
6
+ // `likec4.config.json` scopes the fleet project to `architecture/` and stays
7
+ // that way. What renders THIS model beside the map from the docs root is this
8
+ // directory's own `likec4.config.json`, written by `loam subsystem sync`.
7
9
  specification {
8
10
  element softwareSystem
9
11
  element container
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "identity-service",
3
+ "title": "identity-service"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "notification-service",
3
+ "title": "notification-service"
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ybotok/loam",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.5",
4
4
  "description": "Architecture-first semantic integrity and change governance for evolving software systems: connect C4, requirements, contracts, and verification evidence.",
5
5
  "author": "spentsov",
6
6
  "repository": {