@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":"txn.js","sourceRoot":"","sources":["../../../../src/commands/subsystem/txn/txn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared commit window every subsystem WRITE verb goes through — `new`,\n * `rm`, `move` and `rename` all land here: lock, recover a predecessor's\n * journal, plan against the walked tree, regenerate the views file from the\n * PLANNED post-state, commit through the journaled transaction (directory\n * renames included), report. One window, because the views file must land in\n * the same commit as whatever changed the tree — a marker without its view,\n * or renames without theirs, is exactly the `subsystem.views-stale` state\n * the generated file's contract refuses to leave between two commits.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { fail, type ErrorCode } from \"../../../core/envelope/json.js\";\nimport { subsystemViewsPath } from \"../../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../../core/repo/repo.js\";\nimport type { FleetTree } from \"../../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../../core/staging/interrupted.js\";\nimport { acquireDocsLock, DocsBusyError } from \"../../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { viewsAgree } from \"../../../core/repo/tree/render/views.js\";\nimport { expectedViews } from \"./views.js\";\n\nexport type ViewsAction = \"created\" | \"updated\" | \"removed\" | \"current\";\n\n/** The text tail a verb's success line carries about the generated file. */\nexport function reportViews(views: ViewsAction): string {\n return views === \"current\" ? \"\" : ` — architecture/subsystems.likec4 ${views}`;\n}\n\n/** What one subsystem verb asks the shared commit window to land. */\nexport interface SubsystemTxn {\n /** The journal's `target` — what this commit is FOR. */\n target: string;\n /** Marker writes (create/delete); the views write is derived and appended. */\n writes: PlannedWrite[];\n /** The tree AS THE COMMIT LEAVES IT — the views file is rendered from this. */\n tree: FleetTree;\n /** Directory renames, performed after the swaps, in this order. */\n moves?: { from: string; to: string }[];\n /** Remap of a RACED commit failure (new: a lost marker create is `already-exists`). */\n racedCode?: ErrorCode;\n /** Remap of a rolled-back commit failure (move: `move-failed` — no merge was computed). */\n failedCode?: ErrorCode;\n /** The rollback sentence's verb for half-state files. */\n what: string;\n report: (views: ViewsAction, recovered: CommitRecovery | null) => void | Promise<void>;\n}\n\n/**\n * Lock → recover → plan → stage (views included) → journaled commit → report.\n * `plan` returning null means it already reported a refusal; nothing was\n * staged and nothing is written.\n */\nexport async function commitWindow(\n docsDir: DocsDir,\n json: boolean,\n plan: (tree: FleetTree) => Promise<SubsystemTxn | null>,\n): Promise<void> {\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLock(docsDir);\n } catch (err) {\n if (!(err instanceof DocsBusyError)) throw err;\n fail(json, \"docs-busy\", err.message);\n return;\n }\n try {\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n fail(json, \"commit-interrupted\", err.message);\n return;\n }\n const txn = await plan(await listFleetTree(docsDir));\n if (txn === null) return;\n\n const path = subsystemViewsPath(docsDir);\n const expected = await expectedViews(docsDir, txn.tree);\n const bytes = expected === null ? null : Buffer.from(expected, \"utf8\");\n // Content, not bytes — `viewsAgree` carries the reason. Without it every\n // subsystem move on a Windows clone folded a rewrite of this file into its\n // transaction that changed not one fact, and journalled it.\n const current = existsSync(path) ? await readFile(path, \"utf8\") : null;\n let views: ViewsAction = \"current\";\n const writes = [...txn.writes];\n if (!viewsAgree(current, expected)) {\n views = current === null ? \"created\" : bytes === null ? \"removed\" : \"updated\";\n writes.push({ path, content: bytes });\n }\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n {\n root: docsDir,\n command: \"subsystem\",\n rerun: \"loam subsystem sync\",\n target: txn.target,\n ...(txn.moves === undefined ? {} : { moves: txn.moves }),\n },\n staged,\n txn.what,\n );\n if (!committed.ok) {\n const code =\n committed.code === \"rollback-incomplete\"\n ? committed.code\n : committed.raced\n ? (txn.racedCode ?? txn.failedCode ?? committed.code)\n : (txn.failedCode ?? committed.code);\n fail(json, code, committed.message);\n return;\n }\n await txn.report(views, recovered);\n } finally {\n await releaseLock();\n }\n}\n"]}
1
+ {"version":3,"file":"txn.js","sourceRoot":"","sources":["../../../../src/commands/subsystem/txn/txn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared commit window every subsystem WRITE verb goes through — `new`,\n * `rm`, `move` and `rename` all land here: lock, recover a predecessor's\n * journal, plan against the walked tree, regenerate the views file from the\n * PLANNED post-state, commit through the journaled transaction (directory\n * renames included), report. One window, because the views file must land in\n * the same commit as whatever changed the tree — a marker without its view,\n * or renames without theirs, is exactly the `subsystem.views-stale` state\n * the generated file's contract refuses to leave between two commits.\n *\n * This window does NOT write the per-service project files\n * (`services/<…>/<id>/likec4.config.json`); only `../sync.ts` does, and the\n * asymmetry is deliberate rather than an omission. The views file must ride\n * in the verb's commit because its bytes depend on the PLACEMENT the verb is\n * changing. A project file's bytes depend on the service id alone, which no\n * verb here changes — and the file lives INSIDE the service directory, so a\n * `move` carries it to the new location as part of the rename it already\n * journals (in a git-backed repo that move REFUSES first, `move-uncommitted`,\n * until the files `sync` created are committed — the refusal names them, and\n * `sync`'s own output says to commit them), and `rm` never reaches one,\n * because it refuses any subsystem that still holds a member\n * (`subsystem-not-empty`, `../edit.ts`). No verb can\n * therefore leave a project file stale or orphaned, and a verb that filled\n * gaps in passing would silently write files a `--json` reader of `new` or\n * `rename` was never told to expect. The one state a verb can leave is a\n * model with no file beside it, and that is the state `validate --all` names\n * as `service.likec4-config-missing`, with `loam subsystem sync` as the\n * repair — one writer, so the create-only rule has one place to hold.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { fail, type ErrorCode } from \"../../../core/envelope/json.js\";\nimport { subsystemViewsPath } from \"../../../core/repo/paths.js\";\nimport { listFleetTree } from \"../../../core/repo/repo.js\";\nimport type { FleetTree } from \"../../../core/repo/tree/walk.js\";\nimport { stageWrites } from \"../../../core/staging/commit.js\";\nimport { type CommitRecovery, InterruptedCommitError } from \"../../../core/staging/interrupted.js\";\nimport { acquireDocsLock, DocsBusyError } from \"../../../core/staging/lock.js\";\nimport { recoverInterruptedCommit } from \"../../../core/staging/recovery/recover.js\";\nimport { commitStaged } from \"../../../core/staging/txn/transaction.js\";\nimport { type PlannedWrite } from \"../../../core/staging/writes.js\";\nimport { viewsAgree } from \"../../../core/repo/tree/render/views.js\";\nimport { expectedViews } from \"./views.js\";\n\nexport type ViewsAction = \"created\" | \"updated\" | \"removed\" | \"current\";\n\n/** The text tail a verb's success line carries about the generated file. */\nexport function reportViews(views: ViewsAction): string {\n return views === \"current\" ? \"\" : ` — architecture/subsystems.likec4 ${views}`;\n}\n\n/** What one subsystem verb asks the shared commit window to land. */\nexport interface SubsystemTxn {\n /** The journal's `target` — what this commit is FOR. */\n target: string;\n /** Marker writes (create/delete); the views write is derived and appended. */\n writes: PlannedWrite[];\n /** The tree AS THE COMMIT LEAVES IT — the views file is rendered from this. */\n tree: FleetTree;\n /** Directory renames, performed after the swaps, in this order. */\n moves?: { from: string; to: string }[];\n /** Remap of a RACED commit failure (new: a lost marker create is `already-exists`). */\n racedCode?: ErrorCode;\n /** Remap of a rolled-back commit failure (move: `move-failed` — no merge was computed). */\n failedCode?: ErrorCode;\n /** The rollback sentence's verb for half-state files. */\n what: string;\n report: (views: ViewsAction, recovered: CommitRecovery | null) => void | Promise<void>;\n}\n\n/**\n * Lock → recover → plan → stage (views included) → journaled commit → report.\n * `plan` returning null means it already reported a refusal; nothing was\n * staged and nothing is written.\n */\nexport async function commitWindow(\n docsDir: DocsDir,\n json: boolean,\n plan: (tree: FleetTree) => Promise<SubsystemTxn | null>,\n): Promise<void> {\n let releaseLock: () => Promise<void>;\n try {\n releaseLock = await acquireDocsLock(docsDir);\n } catch (err) {\n if (!(err instanceof DocsBusyError)) throw err;\n fail(json, \"docs-busy\", err.message);\n return;\n }\n try {\n let recovered: CommitRecovery | null;\n try {\n recovered = await recoverInterruptedCommit(docsDir);\n } catch (err) {\n if (!(err instanceof InterruptedCommitError)) throw err;\n fail(json, \"commit-interrupted\", err.message);\n return;\n }\n const txn = await plan(await listFleetTree(docsDir));\n if (txn === null) return;\n\n const path = subsystemViewsPath(docsDir);\n const expected = await expectedViews(docsDir, txn.tree);\n const bytes = expected === null ? null : Buffer.from(expected, \"utf8\");\n // Content, not bytes — `viewsAgree` carries the reason. Without it every\n // subsystem move on a Windows clone folded a rewrite of this file into its\n // transaction that changed not one fact, and journalled it.\n const current = existsSync(path) ? await readFile(path, \"utf8\") : null;\n let views: ViewsAction = \"current\";\n const writes = [...txn.writes];\n if (!viewsAgree(current, expected)) {\n views = current === null ? \"created\" : bytes === null ? \"removed\" : \"updated\";\n writes.push({ path, content: bytes });\n }\n const staged = await stageWrites(writes);\n const committed = await commitStaged(\n {\n root: docsDir,\n command: \"subsystem\",\n rerun: \"loam subsystem sync\",\n target: txn.target,\n ...(txn.moves === undefined ? {} : { moves: txn.moves }),\n },\n staged,\n txn.what,\n );\n if (!committed.ok) {\n const code =\n committed.code === \"rollback-incomplete\"\n ? committed.code\n : committed.raced\n ? (txn.racedCode ?? txn.failedCode ?? committed.code)\n : (txn.failedCode ?? committed.code);\n fail(json, code, committed.message);\n return;\n }\n await txn.report(views, recovered);\n } finally {\n await releaseLock();\n }\n}\n"]}
@@ -34,6 +34,7 @@ import { kindTagFindings } from "./kind-tags.js";
34
34
  import { useCaseFindings } from "./usecases/usecases.js";
35
35
  import { viewIdFindings } from "./views/ids.js";
36
36
  import { viewsStaleFindings } from "./views/stale.js";
37
+ import { projectFindings } from "./views/projects.js";
37
38
  import { readCapabilityVocabulary } from "../../../core/capabilities/capabilities.js";
38
39
  import { capabilityRequirementIndex, gradableCapabilityIds } from "../../../core/capabilities/findings.js";
39
40
  import { parseRequirements, readRequirementsDocument } from "../../../core/document/parse.js";
@@ -111,6 +112,11 @@ export async function validateLandscape(docsDir, preloaded, fleet) {
111
112
  // the fleet gate refuse while still naming every service it found.
112
113
  const tree = await listFleetTree(docsDir, fleet);
113
114
  findings.push(...tree.findings);
115
+ // A model with no renderer project beside it is a fact about `services/`
116
+ // too, graded here for `service.id-invalid`'s reason: it holds whether or
117
+ // not the map parses. One warning per service — views/projects.ts says why
118
+ // a warning, why this run and not `doctor`, and why the root-file gate.
119
+ findings.push(...projectFindings(docsDir, entries));
114
120
  // Before the landscape's own early returns: the authorization and capability
115
121
  // vocabularies are fleet facts that do not depend on the map existing or parsing.
116
122
  findings.push(...(await permissionFindings(docsDir, entries, fleet)));
@@ -1 +1 @@
1
- {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadFile, type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { errorText } from \"../checks/vocabulary.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { useCaseFindings } from \"./usecases/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { readCapabilityVocabulary } from \"../../../core/capabilities/capabilities.js\";\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../core/capabilities/findings.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../../core/document/parse.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no preload (a single-target\n * run) and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null | undefined): readonly ParsedView[] | null {\n if (preloaded === null || preloaded === undefined || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded?: LoadedDoc | null,\n fleet?: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (fleet === undefined ? await loadFile(path) : await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p): p is string => p !== undefined))];\n const spell = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.map(spell).sort().join(\", \");\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n message: `landscape: ${named} has ${land.errors.length} error(s) — cross-check with services/ impossible`,\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then.\n details: land.errors.map((e) =>\n e.sourceFsPath === undefined || broken.length < 2 ? errorText(e) : `${spell(e.sourceFsPath)} ${errorText(e)}`,\n ),\n });\n return report;\n }\n\n // The generated subsystem views, graded only now that the landscape has\n // parsed: the expected bytes are a function of (tree, landscape elements) —\n // `views/stale.ts` says why a byte compare and why exactly one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land.elements)));\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole architecture/ project down in\n // the renderer while every check here stays green — see views/ids.ts.\n findings.push(...viewIdFindings(land.viewIds, tree));\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n\n // Two boxes standing for one service directory. Every join in loam is\n // `element -> service`, computed by picking the FIRST element that resolves —\n // so with two of them, which one wins is readdir order, and the edges of the\n // loser are attributed to a service they do not belong to. Silent until now,\n // and unfixable by staring at either element on its own.\n const perService = new Map<string, Elem[]>();\n for (const e of drawn) {\n if (e.tags.includes(EXTERNAL_TAG)) continue;\n const id = elementService(e);\n perService.set(id, [...(perService.get(id) ?? []), e]);\n }\n for (const [id, elems] of perService) {\n if (elems.length < 2) continue;\n // A collision only matters where it decides something: a real directory to\n // attribute to, or a binding somebody wrote down on purpose.\n if (!services.has(id) && !elems.some((e) => e.service !== undefined)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.binding-duplicate\",\n subject: id,\n message: `landscape: ${elems.length} elements resolve to service '${id}' (${elems.map((e) => e.id).join(\", \")}) — every element→service join picks one of them arbitrarily, so the others' edges are filed under a service that does not own them; keep one element per services/<id>/`,\n });\n }\n\n for (const id of services) {\n if (modelled.has(id)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.service-unmodelled\",\n subject: id,\n message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,\n });\n }\n\n // A binding is a claim about this repo wherever it is written — including\n // inside another element, which the old top-level filter never looked at, so\n // a typo one level down bound an edge to a service that does not exist and\n // nothing said so. Every element with a binding answers for it, at any depth.\n for (const e of land.elements) {\n if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined) continue;\n if (services.has(e.service)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.binding-unknown\",\n subject: e.service,\n message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,\n });\n }\n\n // Walked over the SYSTEM census — the same derivation the scorecard counts,\n // so the map's exemptions and the fleet rollup cannot drift — with the two\n // residual skips that are not exemptions: a bound element is the binding\n // pass's subject above, and a title naming a real directory is documented.\n for (const e of drawnSystems(land.elements, services)) {\n if (e.service !== undefined) continue; // graded by the binding pass above\n if (services.has(e.title)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-undocumented\",\n subject: e.title,\n message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, or tag it #${EXTERNAL_TAG} if it is not ours`,\n });\n }\n\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(\n ...fleetShapeFindings({ drawn, relationships: land.relationships, services, resolve: landSvcOf, pathOf }),\n );\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (usecases/usecases.ts owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n //\n // The views come from `preloaded` rather than from `land`, and that is the\n // file-naming rule made mechanical instead of remembered: only the PROJECT\n // load gives a view the `sourcePath` a finding has to name, while the\n // single-file fallback a few lines above calls every document `source.c4` —\n // so a message built from that load would send its reader to\n // `architecture/source.c4`, a file that has never existed. No preload, no\n // grading, and nothing to get wrong.\n //\n // Read second, and the ladder APPLIED BY ITS OWN FUNCTION rather than\n // re-spelled here: an absent or unreadable capabilities.yaml means silence for\n // the whole family, not a fleet full of unresolved tags, so `null` travels\n // rather than an empty list — which the join would read as \"the fleet declares\n // no capabilities\" and grade every tag against. `gradableCapabilityIds` is the\n // one statement of that rule (`core/capabilities/findings.ts`), and this is a\n // caller of it precisely so a fourth un-gradable vocabulary state cannot be\n // fixed in core while the command layer keeps handing the join a whole key set.\n // `capabilityFleetFindings` above has already paid for this read through the\n // fleet context's memo.\n const vocabulary =\n fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n // The requirement index behind the `#req-` tag, read here rather than inside\n // the use-case package so that package never learns what a capability\n // document is. Under `--all` every document was already parsed by\n // `capabilityFleetFindings` above and the fleet memo answers from cache.\n const capabilityReqs = await capabilityRequirementIndex(\n vocabulary,\n fleet === undefined ? async (p) => parseRequirements(await readRequirementsDocument(p)) : (p) => fleet.readRequirements(p),\n );\n findings.push(\n ...useCaseFindings({\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n }),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
1
+ {"version":3,"file":"landscape.js","sourceRoot":"","sources":["../../../../src/commands/validate/fleet/landscape.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The fleet target: `services/` and architecture/landscape.likec4 graded\n * against each other, plus the two exemptions that decide which drawn element\n * answers for a service directory at all.\n *\n * The landscape is the one artifact no service owns — parsed once for the whole\n * run and graded on a target of its own, outside the dispatcher's `guarded`.\n * That is why its IO containment lives in this package instead: see\n * `unreadableLandscape` in `./load.ts`.\n */\nimport { existsSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadFile, type Elem, type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { elementService, serviceResolver } from \"../../../core/c4/resolve/service.js\";\nimport { landscapePath as landscapeFile } from \"../../../core/repo/paths.js\";\nimport { serviceIdFindings } from \"../../../core/repo/entries.js\";\nimport { listFleetTree, listServices } from \"../../../core/repo/repo.js\";\nimport { type Finding, type TargetReport } from \"../../../core/vocabulary/report.js\";\nimport { landscapeConflictFinding } from \"../../../core/conflict-markers.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { capabilityFleetFindings, fleetShapeFindings, permissionFindings } from \"../checks/fleet-shape.js\";\nimport { fleetLinkFindings } from \"../links/corpus.js\";\nimport { glossaryFindings } from \"../links/glossary.js\";\nimport { obligationFindings, obligationVocabularyFindings } from \"./obligations.js\";\nimport { readObligations } from \"../../../core/obligations/obligations.js\";\nimport { obligationsPath } from \"../../../core/repo/paths.js\";\nimport { EXTERNAL_TAG } from \"../../../core/vocabulary/maturity.js\";\nimport { errorText } from \"../checks/vocabulary.js\";\nimport { serviceTreePath, type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\nimport { drawnSystems, serviceLevelElements } from \"./census.js\";\nimport { unreadableLandscape } from \"./load.js\";\nimport { kindTagFindings } from \"./kind-tags.js\";\nimport { useCaseFindings } from \"./usecases/usecases.js\";\nimport { viewIdFindings } from \"./views/ids.js\";\nimport { viewsStaleFindings } from \"./views/stale.js\";\nimport { projectFindings } from \"./views/projects.js\";\nimport { readCapabilityVocabulary } from \"../../../core/capabilities/capabilities.js\";\nimport { capabilityRequirementIndex, gradableCapabilityIds } from \"../../../core/capabilities/findings.js\";\nimport { parseRequirements, readRequirementsDocument } from \"../../../core/document/parse.js\";\nimport type { ParsedView } from \"../../../core/c4/parsed/dynamic-views.js\";\nimport { isUseCase } from \"../../../core/usecases/fleet.js\";\n\n/**\n * The fleet's flows as a grade may read them, or `null` when loam cannot see\n * them at all — the same `null`-suspends idiom `gradableCapabilityIds` uses,\n * and here it guards one specific wrong answer.\n *\n * `capability.requirement-unrealized` says a business promise is kept by\n * nobody. A use case is one of the two things that can keep it, so a run that\n * could not read the flows must not make that claim: no preload (a single-target\n * run) and a project that did not parse are both \"loam did not look\", never\n * \"there is nothing there\". A preload that DID parse and declares no views is a\n * real, empty answer and grades normally.\n *\n * Filtered through `isUseCase` — the same opt-in `readUseCases` applies before\n * `loam list capabilities` asks this question — because the two surfaces must\n * never disagree about `keptBy`. Handing the unfiltered preload over was\n * harmless only by luck: an untagged view yields no claims, so the answers\n * matched for want of an input that could tell them apart. A `#req-`-only view\n * is exactly that input.\n */\nfunction gradableFlows(preloaded: LoadedDoc | null | undefined): readonly ParsedView[] | null {\n if (preloaded === null || preloaded === undefined || preloaded.errors.length > 0) return null;\n return (preloaded.views ?? []).filter(isUseCase);\n}\n\n/**\n * The fleet cross-check: `services/` and the landscape both claim to name the\n * fleet, and nothing used to compare them. A directory nobody drew and an element\n * with nothing behind it were equally invisible.\n *\n * The two directions are graded differently because the evidence differs. A\n * directory that exists is a fact, so a landscape missing it is an error — every\n * view derived from that landscape is then incomplete. An element with no\n * directory may legitimately be someone else's system, so it warns, and\n * `#external` says \"deliberately not ours\" and silences it. An explicit\n * `metadata { service '<id>' }` naming nothing is an error either way: a binding\n * is a claim about this repo, not a guess at one.\n *\n * An ABSENT landscape is a finding, not a skipped check. It used to return null\n * — no target, no findings, and a fleet gate that went green over a docs repo\n * with no fleet map at all, which is the single artifact every derived view and\n * every spine check is computed from. It is graded by what its absence proves:\n * with services in `services/` it is an ERROR (a fleet that exists is undrawn),\n * with none it is a WARNING (a docs repo before its first adopt legitimately\n * has nothing to draw, but the file still belongs there — `loam init` scaffolds\n * it, and a repo missing it will silently accept never getting one).\n *\n * `preloaded` is the already-parsed landscape under --all — the same doc every\n * service check gets.\n */\nexport async function validateLandscape(\n docsDir: DocsDir,\n preloaded?: LoadedDoc | null,\n fleet?: FleetContext,\n): Promise<TargetReport> {\n const path = landscapeFile(docsDir);\n const findings: Finding[] = [];\n const report: TargetReport = {\n kind: \"landscape\",\n id: \"landscape\",\n path: \"architecture/landscape.likec4\",\n findings,\n };\n\n // The SET of service directories is this target's other subject, so it is\n // graded before the map is even opened: `service.id-invalid` is a fact about\n // `services/` that holds whether or not a landscape exists or parses, and\n // both of those return early below. Emitted here and nowhere else — the\n // enumeration is what makes the id a question, and one finding per fleet is\n // what the rename fixes.\n const entries = await listServices(docsDir, fleet);\n findings.push(...serviceIdFindings(entries));\n // The tree walk's own findings ride the same fleet target, before the map's\n // early returns for the same reason `service.id-invalid` does: a stranded,\n // colliding or mismarked directory is a fact about `services/` that holds\n // whether or not a landscape exists or parses — and a broken tree must make\n // the fleet gate refuse while still naming every service it found.\n const tree = await listFleetTree(docsDir, fleet);\n findings.push(...tree.findings);\n // A model with no renderer project beside it is a fact about `services/`\n // too, graded here for `service.id-invalid`'s reason: it holds whether or\n // not the map parses. One warning per service — views/projects.ts says why\n // a warning, why this run and not `doctor`, and why the root-file gate.\n findings.push(...projectFindings(docsDir, entries));\n // Before the landscape's own early returns: the authorization and capability\n // vocabularies are fleet facts that do not depend on the map existing or parsing.\n findings.push(...(await permissionFindings(docsDir, entries, fleet)));\n findings.push(...(await capabilityFleetFindings(docsDir, entries, fleet, gradableFlows(preloaded))));\n // The documents that belong to no service and no feature — the fleet's ADRs\n // and the living capability tree — are graded for their links here, once,\n // for the same reason the two vocabularies above are: a fleet fact repeated\n // on every service target is the report.\n findings.push(...(await fleetLinkFindings({ docsDir, fleet })));\n // The glossary is graded on who cites it, which is a question only the whole\n // repository answers — so it belongs here, beside the two vocabularies, and\n // nowhere else.\n findings.push(...(await glossaryFindings(docsDir, fleet)));\n // The architectural obligation vocabulary, read ONCE here and handed to the\n // map half below: its own two verdicts hold whether or not the landscape\n // exists or parses, and a run where somebody is fixing the map must still be\n // told that this file does not read.\n const obligations = await readObligations(obligationsPath(docsDir));\n findings.push(...obligationVocabularyFindings(docsDir, obligations));\n\n if (!existsSync(path)) {\n const count = entries.length;\n findings.push({\n severity: count > 0 ? \"error\" : \"warn\",\n code: \"landscape.missing\",\n message:\n `landscape: architecture/landscape.likec4 does not exist — ` +\n (count > 0\n ? `${count} service(s) are adopted and nothing draws the fleet. `\n : \"nothing draws the fleet. \") +\n \"It is the one map every derived view and the C4↔API spine are computed from: \" +\n \"write a `specification { element softwareSystem }` + `model { … }` document there \" +\n \"with one element per services/<id>/, bound with `metadata { service '<id>' }`.\",\n });\n return report;\n }\n\n // Conflict markers before the parse — `loam doctor`'s order, and for its\n // reason: the markers are the cause and the parse errors are the cascade.\n // Nothing may be concluded from a file that is two halves of two different\n // maps, least of all that a service nobody drew is unmodelled, so this\n // returns the way `landscape.invalid` does. An error, gating by the default\n // rule and deliberately not carrying `gates` — that field is coherence's,\n // and the archive gate has to ask this question itself (it does not yet).\n // Read plainly rather than through FleetContext: this target runs outside\n // `guarded`, and a document refused for its encoding would surface here as\n // the whole run's `repository-unavailable` instead of one finding.\n //\n // Both reads are contained for that same reason — see `unreadableLandscape`.\n // The conflict-marker read is the FIRST touch of the file, so it is the one\n // that fails when the file cannot be opened at all, and it runs before\n // `preloaded ??` can spare it.\n let land: LoadedDoc;\n try {\n const conflict = landscapeConflictFinding(\n \"architecture/landscape.likec4\",\n await readFile(path, \"utf8\"),\n );\n if (conflict !== null) {\n findings.push(conflict);\n return report;\n }\n land = preloaded ?? (fleet === undefined ? await loadFile(path) : await fleet.loadLikeC4(path));\n } catch (err) {\n land = unreadableLandscape(err);\n }\n if (land.errors.length > 0) {\n // Nothing may be concluded from a document that did not parse — in particular\n // not that every service is unmodelled.\n //\n // The fleet map is a PROJECT now, not a file: `architecture/landscape.likec4`\n // plus every `architecture/usecases/*.likec4`, merged the way the renderer\n // merges them. So the message names the FILES that broke rather than the\n // landscape by default — a use case with a typo'd element used to be\n // reported as the landscape having errors, which sent the author to the\n // wrong file with a line number that pointed at somebody else's text.\n const broken = [...new Set(land.errors.map((e) => e.sourceFsPath).filter((p): p is string => p !== undefined))];\n const spell = (abs: string): string => relative(docsDir, abs).split(/[\\\\/]/).join(\"/\");\n const named = broken.length === 0 ? \"architecture/landscape.likec4\" : broken.map(spell).sort().join(\", \");\n findings.push({\n severity: \"error\",\n code: \"landscape.invalid\",\n message: `landscape: ${named} has ${land.errors.length} error(s) — cross-check with services/ impossible`,\n // Every line carries its own file, because one project's errors can come\n // from more than one document and a bare `L8:` is unactionable then.\n details: land.errors.map((e) =>\n e.sourceFsPath === undefined || broken.length < 2 ? errorText(e) : `${spell(e.sourceFsPath)} ${errorText(e)}`,\n ),\n });\n return report;\n }\n\n // The generated subsystem views, graded only now that the landscape has\n // parsed: the expected bytes are a function of (tree, landscape elements) —\n // `views/stale.ts` says why a byte compare and why exactly one finding.\n findings.push(...(await viewsStaleFindings(docsDir, tree, land.elements)));\n // Beside staleness, and for the same file: an authored view id that collides\n // with one loam mints into it takes the whole architecture/ project down in\n // the renderer while every check here stays green — see views/ids.ts.\n findings.push(...viewIdFindings(land.viewIds, tree));\n // The three obligation questions that need the map — where each declared rule\n // is applied, which tags resolve to nothing, and which applications no living\n // arch requirement covers. Here rather than beside the vocabulary above\n // because all three are about the parsed landscape, and `land` is only\n // trustworthy past this point.\n findings.push(...(await obligationFindings({ docsDir, vocabulary: obligations, land, services: entries, fleet })));\n\n const services: ReadonlySet<string> = new Set(entries.map((s) => s.id));\n // Where each service actually sits, for every finding below that names a\n // directory a reader is meant to open. `services/<id>/` is right only for an\n // unfiled fleet; a filed service lives at `services/<subsystem>/…/<id>/`, and\n // a finding pointing at the root form sends the fix to a path that does not\n // exist. Built once here because three checks below need the same answer.\n const pathOf = (id: string): string => {\n const entry = entries.find((s) => s.id === id);\n return entry === undefined ? `services/${id}` : serviceTreePath(entry);\n };\n // Ahead of every exemption below, because it is the one defect that can make\n // all of them lie at once: a tag loam grades on, declared on a KIND, landing\n // on an element that stands for one of our own directories — see kind-tags.ts.\n findings.push(...kindTagFindings({ specification: land.specification, elements: land.elements, services }));\n const drawn = serviceLevelElements(land.elements, services);\n // Which services the landscape models, answered by the same resolver every\n // edge join uses — so \"modelled\" here and \"inbound edge\" in the spine check\n // can never disagree about what an element stands for.\n const landSvcOf = serviceResolver(land.elements, services);\n const modelled: ReadonlySet<string> = new Set(land.elements.map((e) => landSvcOf(e.id)));\n\n // Two boxes standing for one service directory. Every join in loam is\n // `element -> service`, computed by picking the FIRST element that resolves —\n // so with two of them, which one wins is readdir order, and the edges of the\n // loser are attributed to a service they do not belong to. Silent until now,\n // and unfixable by staring at either element on its own.\n const perService = new Map<string, Elem[]>();\n for (const e of drawn) {\n if (e.tags.includes(EXTERNAL_TAG)) continue;\n const id = elementService(e);\n perService.set(id, [...(perService.get(id) ?? []), e]);\n }\n for (const [id, elems] of perService) {\n if (elems.length < 2) continue;\n // A collision only matters where it decides something: a real directory to\n // attribute to, or a binding somebody wrote down on purpose.\n if (!services.has(id) && !elems.some((e) => e.service !== undefined)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.binding-duplicate\",\n subject: id,\n message: `landscape: ${elems.length} elements resolve to service '${id}' (${elems.map((e) => e.id).join(\", \")}) — every element→service join picks one of them arbitrarily, so the others' edges are filed under a service that does not own them; keep one element per services/<id>/`,\n });\n }\n\n for (const id of services) {\n if (modelled.has(id)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.service-unmodelled\",\n subject: id,\n message: `landscape: ${pathOf(id)}/ exists but nothing in architecture/landscape.likec4 models it — add an element, or bind one with metadata { service '${id}' }`,\n });\n }\n\n // A binding is a claim about this repo wherever it is written — including\n // inside another element, which the old top-level filter never looked at, so\n // a typo one level down bound an edge to a service that does not exist and\n // nothing said so. Every element with a binding answers for it, at any depth.\n for (const e of land.elements) {\n if (e.tags.includes(EXTERNAL_TAG) || e.service === undefined) continue;\n if (services.has(e.service)) continue;\n findings.push({\n severity: \"error\",\n code: \"landscape.binding-unknown\",\n subject: e.service,\n message: `landscape: '${e.title}' binds to service '${e.service}', but services/${e.service}/ does not exist`,\n });\n }\n\n // Walked over the SYSTEM census — the same derivation the scorecard counts,\n // so the map's exemptions and the fleet rollup cannot drift — with the two\n // residual skips that are not exemptions: a bound element is the binding\n // pass's subject above, and a title naming a real directory is documented.\n for (const e of drawnSystems(land.elements, services)) {\n if (e.service !== undefined) continue; // graded by the binding pass above\n if (services.has(e.title)) continue;\n findings.push({\n severity: \"warn\",\n code: \"landscape.service-undocumented\",\n subject: e.title,\n message: `landscape: '${e.title}' has no services/${e.title}/ — bind it with metadata { service '<id>' }, or tag it #${EXTERNAL_TAG} if it is not ours`,\n });\n }\n\n // The shape advisories run LAST, over the same drawn set and resolver the\n // structural checks used. Any one of them suppresses `landscape.matched`\n // below on purpose: a map with a shape warning did not fully \"agree\".\n findings.push(\n ...fleetShapeFindings({ drawn, relationships: land.relationships, services, resolve: landSvcOf, pathOf }),\n );\n\n // The use cases the SAME `architecture/` project declares — every\n // `dynamic view` in the landscape and in every `architecture/usecases/*.likec4`\n // — graded against the model above (usecases/usecases.ts owns the opt-in and\n // the grades). They ride this target for `viewsStaleFindings`' reason: a view\n // belongs to no service, and only the fleet run has both the whole model and\n // the enumerated `services/` in view at once.\n //\n // The views come from `preloaded` rather than from `land`, and that is the\n // file-naming rule made mechanical instead of remembered: only the PROJECT\n // load gives a view the `sourcePath` a finding has to name, while the\n // single-file fallback a few lines above calls every document `source.c4` —\n // so a message built from that load would send its reader to\n // `architecture/source.c4`, a file that has never existed. No preload, no\n // grading, and nothing to get wrong.\n //\n // Read second, and the ladder APPLIED BY ITS OWN FUNCTION rather than\n // re-spelled here: an absent or unreadable capabilities.yaml means silence for\n // the whole family, not a fleet full of unresolved tags, so `null` travels\n // rather than an empty list — which the join would read as \"the fleet declares\n // no capabilities\" and grade every tag against. `gradableCapabilityIds` is the\n // one statement of that rule (`core/capabilities/findings.ts`), and this is a\n // caller of it precisely so a fourth un-gradable vocabulary state cannot be\n // fixed in core while the command layer keeps handing the join a whole key set.\n // `capabilityFleetFindings` above has already paid for this read through the\n // fleet context's memo.\n const vocabulary =\n fleet === undefined ? await readCapabilityVocabulary(docsDir) : await fleet.capabilities(docsDir);\n // The requirement index behind the `#req-` tag, read here rather than inside\n // the use-case package so that package never learns what a capability\n // document is. Under `--all` every document was already parsed by\n // `capabilityFleetFindings` above and the fleet memo answers from cache.\n const capabilityReqs = await capabilityRequirementIndex(\n vocabulary,\n fleet === undefined ? async (p) => parseRequirements(await readRequirementsDocument(p)) : (p) => fleet.readRequirements(p),\n );\n findings.push(\n ...useCaseFindings({\n views: preloaded?.views ?? [],\n elements: land.elements,\n relationships: land.relationships,\n services,\n resolve: landSvcOf,\n capabilities: gradableCapabilityIds(vocabulary),\n requirementsOf: (capability) => capabilityReqs.byCapability.get(capability),\n }),\n );\n\n if (findings.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"landscape.matched\",\n message: `landscape: ${services.size} service(s) modelled — architecture/landscape.likec4 and services/ agree`,\n });\n }\n return report;\n}\n"]}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * A service whose model has no LikeC4 project file beside it —
3
+ * `services/<…>/<id>/model.likec4` exists and `services/<…>/<id>/likec4.config.json`
4
+ * does not — graded as ONE warning per service on the fleet target.
5
+ *
6
+ * WHY A WARNING. The root `likec4.config.json` excludes `services/**` (it
7
+ * must: every model declares its own `specification` block, and a renderer
8
+ * merging the tree reports each one as a duplicate), so a model with no
9
+ * project of its own is a box on the fleet map with nothing renderable inside
10
+ * it from the docs root. That is no picture, and no picture is not a wrong
11
+ * picture: every fact loam grades about the model — its parse, its bindings,
12
+ * its edges into the spine — holds exactly as it did, so nothing here may
13
+ * gate. ROADMAP's clause that rendering "must stay outside validation so view
14
+ * computation cannot slow or change the gate" is about computing views; this
15
+ * check computes nothing and reads nothing — it is one `existsSync` per
16
+ * service, on `viewsStaleFindings`' precedent of grading a renderer-only file
17
+ * by its presence. And the sibling that grades the ROOT file's absence
18
+ * (`doctor.likec4-config-missing`, `core/doctor/doctor.ts`) calls a repo
19
+ * without it "only unrenderable" and warns; the per-service absence is the
20
+ * same condition one directory down and earns the same severity.
21
+ *
22
+ * WHY `validate --all` AND NOT `doctor`. `doctor` is the step-0 preflight
23
+ * of the adopt protocol — wiring, the config, the fleet map's readability —
24
+ * and the protocol's step 5 is the `validate --all` run whose findings the
25
+ * agent branches on. The report that motivated this check had its own
26
+ * `doctor --json` answer `healthy: true` with 0 findings on a tree where every
27
+ * adopted model was unrenderable from the root: the model is written in step
28
+ * 3, so its renderability is a fact that first exists at step 5, and a
29
+ * preflight that ran before the model did could not have said otherwise.
30
+ * Never in `validate --service`, `status` or `doctor`: the writer is a fleet
31
+ * verb (`subsystem sync`) and the finding belongs beside the file that verb
32
+ * owns.
33
+ *
34
+ * WHY PER SERVICE. `subject` is the service id so a `--json` worklist filters
35
+ * by team the way `landscape.service-unmodelled` already lets it — one fleet
36
+ * finding listing every gap would have to be re-parsed to find out whose it
37
+ * is. The `scope` location is the service's tree path, through
38
+ * `serviceTreePath` on the ENTRY the survey hands back — never a join by id:
39
+ * this grade is emitted before the map's early returns precisely so it holds
40
+ * on a broken tree, and a tree with two services sharing a leaf name under
41
+ * different subsystems (`subsystem.name-collision`) is exactly such a tree,
42
+ * still enumerated in full. Joined by id, both findings named the first
43
+ * directory while `subsystem sync` wrote both files at the right paths.
44
+ *
45
+ * WHY THE ROOT GATE, and it does double duty. Semantically: the writer
46
+ * creates a per-service project only when the root `likec4.config.json`
47
+ * exists (the docs root is then a LikeC4 workspace and a nested project is
48
+ * registered inside it — measured at the 1.59.2 pin), so a repo without the
49
+ * root file has `doctor` telling it to write that first and nothing to sync
50
+ * into; grading a gap the writer would not fill is a loop no command can
51
+ * clear. The same rule silences a directory whose name is not a legal service
52
+ * id: the survey skips it (`render/projects.ts` says why), `service.id-invalid`
53
+ * already names it, and a warning here would advertise a repair `sync`
54
+ * refuses to perform. Fixture-wise: `makeProject` writes NO root file, so
55
+ * every existing fixture stays silent under this gate, and the two suites
56
+ * that pin a fleet's warning set exactly (`test/examples.test.ts`,
57
+ * `test/self-model.test.ts`) ship the per-service files and keep their
58
+ * counts. A future fixture that scaffolds with `init --create` and pins its
59
+ * warnings WILL move by one per model-bearing service, and that is this gate
60
+ * working, not breaking.
61
+ */
62
+ import { existsSync } from "node:fs";
63
+ import { serviceTreePath } from "../../../../core/kernel/ids/dirs.js";
64
+ import { ARTIFACT_FILES, LIKEC4_PROJECT_FILENAME, rootProjectPath, serviceRenderPaths } from "../../../../core/repo/paths.js";
65
+ import { surveyProjects } from "../../../../core/repo/tree/render/projects.js";
66
+ /**
67
+ * `entries` is the fleet enumeration at whatever depth each service lives;
68
+ * the predicate is `surveyProjects` — the ONE spelling of "has a model,
69
+ * lacks a project file" that `subsystem sync` also fills — so the grader can
70
+ * never name a gap the writer would leave.
71
+ */
72
+ export function projectFindings(docsDir, entries) {
73
+ if (!existsSync(rootProjectPath(docsDir)))
74
+ return [];
75
+ return surveyProjects(entries, serviceRenderPaths).gaps.map(({ service }) => {
76
+ const tree = serviceTreePath(service);
77
+ return {
78
+ severity: "warn",
79
+ code: "service.likec4-config-missing",
80
+ subject: service.id,
81
+ // `scope`, not `primary`: the file does not exist yet, and the
82
+ // actionable place is the directory the writer will create it in.
83
+ locations: [{ path: tree, role: "scope" }],
84
+ message: `${service.id}: ${tree}/${ARTIFACT_FILES.model} exists and ${tree}/${LIKEC4_PROJECT_FILENAME} does not — ` +
85
+ "the root LikeC4 project excludes services/**, so this model renders only as a project of its own, " +
86
+ "and nothing renders it from the docs root now. Run `loam subsystem sync`; it writes the file (create-only) — never write it by hand.",
87
+ };
88
+ });
89
+ }
90
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../../../src/commands/validate/fleet/views/projects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAgB,MAAM,qCAAqC,CAAC;AAEpF,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAC9H,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAG/E;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB,EAAE,OAAgC;IAChF,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACrD,OAAO,cAAc,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1E,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACtC,OAAO;YACL,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,+BAA+B;YACrC,OAAO,EAAE,OAAO,CAAC,EAAE;YACnB,+DAA+D;YAC/D,kEAAkE;YAClE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAgB,EAAE,CAAC;YACnD,OAAO,EACL,GAAG,OAAO,CAAC,EAAE,KAAK,IAAI,IAAI,cAAc,CAAC,KAAK,eAAe,IAAI,IAAI,uBAAuB,cAAc;gBAC1G,oGAAoG;gBACpG,sIAAsI;SACzI,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * A service whose model has no LikeC4 project file beside it —\n * `services/<…>/<id>/model.likec4` exists and `services/<…>/<id>/likec4.config.json`\n * does not — graded as ONE warning per service on the fleet target.\n *\n * WHY A WARNING. The root `likec4.config.json` excludes `services/**` (it\n * must: every model declares its own `specification` block, and a renderer\n * merging the tree reports each one as a duplicate), so a model with no\n * project of its own is a box on the fleet map with nothing renderable inside\n * it from the docs root. That is no picture, and no picture is not a wrong\n * picture: every fact loam grades about the model — its parse, its bindings,\n * its edges into the spine — holds exactly as it did, so nothing here may\n * gate. ROADMAP's clause that rendering \"must stay outside validation so view\n * computation cannot slow or change the gate\" is about computing views; this\n * check computes nothing and reads nothing — it is one `existsSync` per\n * service, on `viewsStaleFindings`' precedent of grading a renderer-only file\n * by its presence. And the sibling that grades the ROOT file's absence\n * (`doctor.likec4-config-missing`, `core/doctor/doctor.ts`) calls a repo\n * without it \"only unrenderable\" and warns; the per-service absence is the\n * same condition one directory down and earns the same severity.\n *\n * WHY `validate --all` AND NOT `doctor`. `doctor` is the step-0 preflight\n * of the adopt protocol — wiring, the config, the fleet map's readability —\n * and the protocol's step 5 is the `validate --all` run whose findings the\n * agent branches on. The report that motivated this check had its own\n * `doctor --json` answer `healthy: true` with 0 findings on a tree where every\n * adopted model was unrenderable from the root: the model is written in step\n * 3, so its renderability is a fact that first exists at step 5, and a\n * preflight that ran before the model did could not have said otherwise.\n * Never in `validate --service`, `status` or `doctor`: the writer is a fleet\n * verb (`subsystem sync`) and the finding belongs beside the file that verb\n * owns.\n *\n * WHY PER SERVICE. `subject` is the service id so a `--json` worklist filters\n * by team the way `landscape.service-unmodelled` already lets it — one fleet\n * finding listing every gap would have to be re-parsed to find out whose it\n * is. The `scope` location is the service's tree path, through\n * `serviceTreePath` on the ENTRY the survey hands back — never a join by id:\n * this grade is emitted before the map's early returns precisely so it holds\n * on a broken tree, and a tree with two services sharing a leaf name under\n * different subsystems (`subsystem.name-collision`) is exactly such a tree,\n * still enumerated in full. Joined by id, both findings named the first\n * directory while `subsystem sync` wrote both files at the right paths.\n *\n * WHY THE ROOT GATE, and it does double duty. Semantically: the writer\n * creates a per-service project only when the root `likec4.config.json`\n * exists (the docs root is then a LikeC4 workspace and a nested project is\n * registered inside it — measured at the 1.59.2 pin), so a repo without the\n * root file has `doctor` telling it to write that first and nothing to sync\n * into; grading a gap the writer would not fill is a loop no command can\n * clear. The same rule silences a directory whose name is not a legal service\n * id: the survey skips it (`render/projects.ts` says why), `service.id-invalid`\n * already names it, and a warning here would advertise a repair `sync`\n * refuses to perform. Fixture-wise: `makeProject` writes NO root file, so\n * every existing fixture stays silent under this gate, and the two suites\n * that pin a fleet's warning set exactly (`test/examples.test.ts`,\n * `test/self-model.test.ts`) ship the per-service files and keep their\n * counts. A future fixture that scaffolds with `init --create` and pins its\n * warnings WILL move by one per model-bearing service, and that is this gate\n * working, not breaking.\n */\nimport { existsSync } from \"node:fs\";\nimport { serviceTreePath, type DocsDir } from \"../../../../core/kernel/ids/dirs.js\";\nimport type { ServiceEntry } from \"../../../../core/repo/entries.js\";\nimport { ARTIFACT_FILES, LIKEC4_PROJECT_FILENAME, rootProjectPath, serviceRenderPaths } from \"../../../../core/repo/paths.js\";\nimport { surveyProjects } from \"../../../../core/repo/tree/render/projects.js\";\nimport type { Finding } from \"../../../../core/vocabulary/report.js\";\n\n/**\n * `entries` is the fleet enumeration at whatever depth each service lives;\n * the predicate is `surveyProjects` — the ONE spelling of \"has a model,\n * lacks a project file\" that `subsystem sync` also fills — so the grader can\n * never name a gap the writer would leave.\n */\nexport function projectFindings(docsDir: DocsDir, entries: readonly ServiceEntry[]): Finding[] {\n if (!existsSync(rootProjectPath(docsDir))) return [];\n return surveyProjects(entries, serviceRenderPaths).gaps.map(({ service }) => {\n const tree = serviceTreePath(service);\n return {\n severity: \"warn\",\n code: \"service.likec4-config-missing\",\n subject: service.id,\n // `scope`, not `primary`: the file does not exist yet, and the\n // actionable place is the directory the writer will create it in.\n locations: [{ path: tree, role: \"scope\" as const }],\n message:\n `${service.id}: ${tree}/${ARTIFACT_FILES.model} exists and ${tree}/${LIKEC4_PROJECT_FILENAME} does not — ` +\n \"the root LikeC4 project excludes services/**, so this model renders only as a project of its own, \" +\n \"and nothing renders it from the docs root now. Run `loam subsystem sync`; it writes the file (create-only) — never write it by hand.\",\n };\n });\n}\n"]}
@@ -220,7 +220,9 @@ const MAP_REST = `- \`loam validate --service <id>\` grades one service's own ax
220
220
  \`landscape.platform-candidate\`, \`landscape.datastore-private\` and
221
221
  \`landscape.datastore-shared\`, \`service.id-invalid\` (fleet scope only, and
222
222
  graded before the map is opened, so it stands even when the landscape is
223
- missing or unreadable), a per-service \`sources.unverifiable-from-here\`, and
223
+ missing or unreadable), \`service.likec4-config-missing\` (warn, per
224
+ service: a model with no \`likec4.config.json\` beside it renders nowhere from
225
+ the docs root — \`loam subsystem sync\` writes the file), a per-service \`sources.unverifiable-from-here\`, and
224
226
  one check on the docs repo's own AGENTS.md, \`agents.stale\`. That last one is
225
227
  the reason to distrust THAT file rather than this page: it fires when its
226
228
  version stamp on line 1 (\`<!-- generated by loam vX.Y.Z -->\`) is missing or
@@ -1 +1 @@
1
- {"version":3,"file":"command-map.js","sourceRoot":"","sources":["../../../../src/core/agent/agents-md/command-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuB7B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B9B,CAAC;AAEF;;;GAGG;AACH,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BjB,CAAC;AAEF,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyJhB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,GAAG,cAAc,GAAG,QAAQ,CAAC","sourcesContent":["/**\n * Reading loam's output, in its two deliveries.\n *\n * {@link READING_OUTPUT} and {@link REFERENCE_PAGES} are what AGENTS.md keeps:\n * the `--json` envelope, the rule to branch on codes rather than prose, and the\n * index of the pages this file no longer carries. {@link CODE_MAP} is the\n * code-by-code map of `status`, `validate`, `doctor`, `rebase`, `dependencies`,\n * `explore`, `seed` and `instructions` — the part `loam instructions loam-codes`\n * prints. ../workflows/reference/codes.ts composes it with `./map/`,\n * `./map/lenses/` and `./refusals.ts`, in exactly the order ../agents-md.ts used\n * to concatenate them, so the assembled page is the section byte for byte.\n *\n * WHY THE MAP LEFT THE FILE, given that this package's whole subject is a file.\n * AGENTS.md is auto-loaded from the working directory by every agents.md-aware\n * host, on every session, and `loam init` writes it once and never refreshes it.\n * It reached 109,399 bytes; Codex truncates the AGENTS.md chain at 32,768 bytes\n * and Windsurf caps a workspace rule file at 12,000 characters, both silently.\n * Roughly seventy percent of the document was being dropped with no error and no\n * way for the reader to tell which half it held — and this section alone was\n * 44,433 of those bytes, 41% of the file. It is also the half a reader consults\n * at a moment it can name (a run reported something) rather than the half it\n * needs to form a question at all, which is what decided which side of the split\n * it went. ../workflows/reference/reference.ts carries the rest of that argument.\n *\n * The move is a move and not a deletion: every byte is still shipped, still in\n * the same corpus test/codes-drift.test.ts reads (AGENTS_MD + PROTOCOLS), and\n * still one command away rather than one file away.\n *\n * WHAT THE MAP IS, AND WHAT IT DELIBERATELY IS NOT. The unique content is which\n * codes which INVOCATION can raise: nothing else in the corpus says that, and an\n * agent choosing what to run needs it. What it does not carry is a per-code\n * gloss of what each code MEANS. That half used to be written twice — once here,\n * once in the /loam-check fix tables — in two independently worded places, in a\n * file `loam init` writes once and never refreshes, so the two drifted with\n * nothing able to say which had rotted. `loam explain <code>` now answers\n * meaning, severity and fix for every code named below, out of the running\n * binary. test/agent-contract.test.ts holds that pointer honest: every code\n * backticked in the corpus must resolve through `explainSubject`, so a family\n * added here without an explanation fails by name. Keep the code list; add the\n * gloss only where it says something `explain` cannot — a condition on when the\n * invocation raises it at all, or a cross-reference between two codes.\n *\n * ONE PARAGRAPH THE MOVE MADE FALSE, and the reason to look for others like it.\n * The `agents.stale` entry read \"one check on this very file … the reason to\n * distrust what you are reading\", which was true while these bytes WERE the\n * scaffolded AGENTS.md. On a page printed by the binary it is exactly backwards\n * — this page cannot be stale, and the file it warns about is somewhere else —\n * so the entry names AGENTS.md instead. A section that talks about its own\n * document does not survive being moved to another one; that is the class to\n * check for whenever anything else leaves this file.\n *\n * READING_OUTPUT and REFERENCE_PAGES are sections of the AGENTS.md template.\n * ../agents-md.ts assembles the document by PLAIN CONCATENATION — no join\n * separator — so every section starts at the first character of its opening line\n * and ends with the newline that closes its last one. Keep that shape when\n * editing, or two sections glue onto one line in every docs repo loam scaffolds\n * from now on. CODE_MAP owes the same shape for the same reason, one level up:\n * reference/codes.ts concatenates it with the modules listed above.\n */\nimport { STATUS_COMMAND } from \"./map/status.js\";\n\n/**\n * The section AGENTS.md keeps. Three facts an agent needs BEFORE it can form a\n * question — that every command speaks JSON, that the envelope survives a bad\n * invocation, and that codes are the contract and prose is not — plus the two\n * places the detail went.\n */\nexport const READING_OUTPUT = `## Reading loam's output\n\nEvery command takes \\`--json\\`, and the envelope holds even when the INVOCATION\nis wrong: with \\`--json\\` anywhere in the arguments, an unknown flag, unknown\ncommand or missing argument still yields \\`{ ok: false, error: { code:\n\"invalid-option\" } }\\` on stdout with exit 1 (commander's own diagnostic goes\nto stderr; \\`--help\\` and \\`--version\\` output pass through untouched). Without\n\\`--json\\` the same refusal is plain text on stderr with nothing on stdout.\n\nBranch on \\`findings[].code\\`, never on the prose — the wording changes, the codes do\nnot.\n\n**What a code MEANS is one command away**, and the answer describes the loam you\nare running rather than the one that scaffolded this repo. \\`loam explain <code>\\`\ngives one code's meaning, its severity in each scope that grades it, and its fix;\n\\`loam explain --codes\\` lists the whole vocabulary and \\`loam explain --codes --json\\`\nis the machine-readable form. Reach for it rather than for a generated pointer:\nthis page is printed by the binary that defines the codes.\n\n**Which codes an INVOCATION can raise** is the other half, and it is the one\nthing nothing else carries. It is a reference page rather than a section here:\n\\`loam instructions loam-codes\\`.\n\n`;\n\n/**\n * The index. A reference nobody can find is content deleted with extra steps,\n * so the exact command is spelled per page rather than described — and\n * test/agents.test.ts asserts that every name in REFERENCES appears here inside\n * its own `loam instructions` line, which is what makes a renamed page fail\n * loudly instead of leaving a pointer at nothing.\n */\nexport const REFERENCE_PAGES = `## The reference pages\n\nThis file is the orientation: the layout, the cycle, what gates and what only\nadvises, and what the words mean. Four things it does NOT carry — the grammars\nyou consult while writing ONE document, and the inventory of what each command\ncan report — are **reference pages**, printed by the binary rather than written\nhere. That is why: this file is auto-loaded on every session and never\nrefreshed, while a page out of the binary describes the loam you are actually\nrunning, and is read at the one moment it is needed.\n\n- \\`loam instructions loam-codes\\` — which codes each invocation can raise:\n \\`validate --service\\` / \\`--feature\\` / \\`--all\\`, \\`status\\`, \\`doctor\\`, \\`gate\\`,\n \\`context\\`, \\`diff\\`, \\`explore\\`, \\`dependencies\\`, \\`rebase\\`, \\`seed\\`, \\`subsystem\\`,\n \\`mcp\\`, the use-case views, the containment refusals, the error envelope, and\n the OpenSpec migration surface.\n- \\`loam instructions loam-spine\\` — the ID spine: every join between the\n artifacts (\\`operationId\\`, message name, \\`Covers:\\`, \\`Requires:\\`,\n \\`Capability:\\`, \\`Realizes:\\`), the \\`Based-On:\\` baseline pins on both the\n requirement and the contract axis, how to draw a shared broker, and how to\n declare a message produced outside the fleet.\n- \\`loam instructions loam-authoring\\` — the grammars you author against:\n the architecture spec axis (\\`arch.spec.md\\` and its \\`Covers:\\` line), the\n generated Gherkin suite, and frontmatter with the vouch chain behind it.\n- \\`loam instructions loam-done-check\\` — the done-check: how \\`loam verify\\`\n derives its claims, the three channels that may answer them, the federated\n recording form, and what separates **verified** from **attested**.\n\nEach takes no arguments and prints whole. \\`loam instructions\\` with no argument\nlists them beside the six workflow protocols; \\`loam explain <code>\\` answers any\nsingle code without opening a page at all.\n\n`;\n\n/**\n * The reference page's own opening: what the map is, where the /loam-check fix\n * tables sit beside it, and the one family `loam explain` cannot answer.\n */\nconst MAP_INTRO = `**What this page is.** It says which codes an INVOCATION can raise. It does\nNOT say what they mean: that half is one command away and describes the loam you\nare running rather than the one that scaffolded this repo. \\`loam explain <code>\\`\ngives one code's meaning, its severity in each scope that grades it, and its fix;\n\\`loam explain --codes\\` lists the whole vocabulary and \\`loam explain --codes --json\\`\nis the machine-readable form. The one family \\`explain\\` does not answer\nis the OpenSpec migration surface (\\`openspec.*\\`, \\`mapping.*\\`) — those commands run\nbefore a repository has a governed loop to look a code up from — so those entries\nalone keep their notes here.\n\nThe code-by-code fix table is a different document again: it lives in the\n\\`/loam-check\\` command \\`loam init\\` lays\ndown. \\`init\\` writes that body twice for every tool it configures: once as a slash\ncommand in the tool's own command directory (you type it), and once as an Agent\nSkill at \\`<tool-dir>/skills/loam-check/SKILL.md\\` (the model loads it by itself when\nthe task matches the skill's description). Same body, two ways in.\n\nWhich tools get them is detected from the repo: \\`loam init\\` scans for the\ndot-directories of the tools it knows (\\`.claude\\`, \\`.cursor\\`, \\`.gemini\\`, …) and\nwrites for the ones it finds, falling back to Claude Code when it finds none.\n\\`loam init --tools <ids|all>\\` overrides the scan — an unknown id is refused\n(\\`invalid-option\\`) rather than skipped — and \\`--no-commands\\` / \\`--no-skills\\`\neach suppress one delivery. The tools written for are recorded in\n\\`loam.json\\` as \\`agentTools\\`, which is how \\`loam doctor\\` tells a file missing\nbecause the binary grew a new command from one missing because nobody ever\nselected that tool.\n\nThe map:\n\n`;\n\nconst MAP_REST = `- \\`loam validate --service <id>\\` grades one service's own axes: \\`service.unknown\\`,\n \\`service.no-model\\`, \\`service.no-spec\\`, \\`service.no-openapi\\`, \\`c4.invalid\\`,\n \\`c4.no-relationships\\`, \\`requirements.missing-scenarios\\`,\n \\`requirements.stepless-scenario\\`, \\`requirements.assertionless-scenario\\`,\n \\`requirements.examples-unbound\\`, \\`requirements.examples-unreferenced\\`,\n \\`spec.merge-conflict\\`, \\`spec.duplicate-requirement\\`, \\`spec.no-requirements\\`,\n \\`spec.repeated-operations\\`, \\`spec.repeated-covers\\`, \\`openapi.invalid\\`,\n \\`openapi.duplicate-operationid\\`, \\`openapi.response-undescribed\\`,\n \\`openapi.ref-unresolved\\`, \\`api.ungoverned\\`, \\`api.ops-unlinked\\`,\n \\`api.requirement-deprecated\\`, \\`spec-api.op-undefined\\`,\n \\`spine.landscape-invalid\\`, \\`spine.op-undefined\\`, \\`spine.op-link-missing\\`,\n \\`spine.op-deprecated\\`; the async contract axis (AsyncAPI 3):\n \\`service.no-asyncapi\\`, \\`asyncapi.invalid\\`, \\`asyncapi.duplicate-message\\`,\n \\`asyncapi.payload-undescribed\\`, \\`asyncapi.ref-unresolved\\`,\n \\`spine.message-undefined\\`, \\`spec-event.message-undefined\\`,\n \\`spine.message-unproduced\\`, \\`spine.message-external\\`,\n \\`asyncapi.message-contested\\`, \\`event.messages-unlinked\\`, \\`event.ungoverned\\`,\n \\`event.covered\\`; and the architecture spec axis: \\`covers.unknown\\`,\n \\`health.invalid\\`, \\`health.uncovered\\`, \\`health.dependency-unmodelled\\`.\n Run inside the service's own repo, once a generated suite exists under\n \\`<gherkinDir>/loam/\\`, it also grades that suite against the living specs:\n \\`gherkin.missing\\`, \\`gherkin.stale\\`, \\`gherkin.orphaned\\`. A service that never\n ran \\`loam gherkin\\` stays quiet — that whole trio is conditional on the suite\n existing here — and a file tagged with a feature still in flight answers to\n that feature, not to the living spec it has not merged into yet.\n- \\`loam validate --feature <id>\\` grades a change's three axes against each other and\n against the fleet in flight: \\`delta.invalid\\`, \\`delta.nothing-tagged\\`,\n \\`delta.service-unknown\\`, \\`delta.service-id-invalid\\`, \\`spec-api.op-undefined\\`,\n \\`spec-api.op-pending\\`, \\`c4-api.op-undefined\\`, \\`c4-api.op-pending\\`,\n \\`c4-api.op-deprecated\\`, \\`c4.op-ungoverned\\`, \\`c4.op-link-missing\\`,\n \\`c4.service-binding-invalid\\`, \\`api.op-unconsumed\\`,\n \\`service.no-requirement-delta\\`, \\`archedge.uncovered\\`, and — the same four\n codes service scope raises, graded here on the feature's own spec.md and\n arch.spec.md deltas, because every one of those breaches merges into the\n living document — \\`spec.repeated-operations\\`, \\`spec.repeated-covers\\`,\n \\`spec.merge-conflict\\` and \\`requirements.stepless-scenario\\`. Then the\n architecture spec axis (\\`c4.uncovered\\`, plus \\`covers.unknown\\` on the\n feature's arch.spec.md deltas), the delta-shape group:\n \\`delta.unknown-section\\`, \\`delta.no-delta-sections\\`,\n \\`delta.requirement-not-merged\\`, \\`delta.modified-unknown\\`, \\`delta.removed-unknown\\`,\n \\`delta.added-duplicate\\`, \\`delta.added-near-duplicate\\`, \\`delta.modified-pending\\`,\n \\`delta.removed-pending\\`, \\`delta.added-conflict\\`, \\`delta.modified-conflict\\`,\n \\`delta.living-duplicate-requirement\\`, and the API-removal group:\n \\`openapi.remove-marker-anonymous\\`, \\`openapi.remove-op-consumed\\`.\n The event axis is graded in feature scope too — the \\`asyncapi.*\\`\n baseline/removal/conflict codes plus \\`c4-event.*\\` and \\`spec-event.*\\`.\n Where a finding names another feature in flight (\\`delta.*-pending\\`,\n \\`delta.added-conflict\\`, \\`delta.modified-conflict\\`, \\`spec-api.op-pending\\`,\n \\`c4-api.op-pending\\`) the ORDER is the answer, and \\`loam dependencies --json\\`\n is what computes it: \\`order\\` is a dependency-first sequence, \\`conflicts\\`\n the pairs no ordering fixes, \\`cycles\\` the ones that need a human.\n- \\`loam validate --all\\` runs both of those for everything and adds the fleet\n cross-check: \\`landscape.missing\\`, \\`landscape.invalid\\`,\n \\`landscape.merge-conflict\\`, \\`landscape.service-unmodelled\\`,\n \\`landscape.service-undocumented\\`, \\`landscape.binding-unknown\\`,\n \\`landscape.binding-duplicate\\`, the fleet-shape advisories\n \\`landscape.platform-candidate\\`, \\`landscape.datastore-private\\` and\n \\`landscape.datastore-shared\\`, \\`service.id-invalid\\` (fleet scope only, and\n graded before the map is opened, so it stands even when the landscape is\n missing or unreadable), a per-service \\`sources.unverifiable-from-here\\`, and\n one check on the docs repo's own AGENTS.md, \\`agents.stale\\`. That last one is\n the reason to distrust THAT file rather than this page: it fires when its\n version stamp on line 1 (\\`<!-- generated by loam vX.Y.Z -->\\`) is missing or\n older than the running binary, which is exactly when what it says may name a\n loam that no longer exists. It is never refreshed automatically — your edits\n outrank the template, so detection is all loam does, and a hand-curated file\n silences it the same way, by keeping the stamp current. This page has no such\n failure mode: it is printed by the binary it describes.\n \\`loam validate --all\\`'s \\`--json\\` payload also carries the additive \\`scorecard\\` key — per-axis ceiling-vs-actual fleet\n aggregates, the text report appending the same table; derived per run and never stored, so week-over-week is the pipeline's job: capture the key per run into a metrics store.\n \\`scorecard.adoption\\` counts the services PARTICIPATING in each contract axis\n (requirements, arch, openapi, asyncapi, permissions, capabilities; the\n denominator is \\`scorecard.services\\`) — an axis at 0 of N reads \"not started\",\n which is expected during staged adoption and distinct from partially adopted.\n Text mode folds the warnings such an axis alone causes under one banner per\n axis and says so; \\`--json\\` carries every finding unchanged, and the summary\n counts, exit codes and \\`--strict\\` are identical either way.\n- \\`loam doctor\\` is read-only local/fleet preflight — the first thing to run in a\n repo that behaves as though the fleet were empty. Every finding carries a\n \\`fix\\` field naming the exact command or edit, so this is the one family you\n rarely need \\`loam explain\\` for. Its blockers are \\`doctor.config-missing\\`,\n \\`doctor.config-invalid\\`, \\`doctor.docs-missing\\`, \\`doctor.services-missing\\`, and\n the three that say the fleet map cannot be read at all —\n \\`doctor.landscape-merge-conflict\\`, \\`doctor.landscape-invalid\\` and\n \\`doctor.landscape-unreadable\\`. It also grades what a WRITE that did not finish\n left in the docs repo, reported as \\`writePath\\` beside the findings:\n \\`doctor.docs-locked\\`, \\`doctor.commit-interrupted\\`, \\`doctor.commit-unreadable\\`\n and \\`doctor.staging-temps\\`. When this repo is a service repo that same scan\n covers \\`<gherkinDir>/loam/\\`, the root \\`loam gherkin\\` commits into.\n Accessibility, portability and incomplete binding stay warnings:\n \\`doctor.docs-unreadable\\`, \\`doctor.docs-readonly\\`, \\`doctor.docs-absolute\\`,\n \\`doctor.inventory-unreadable\\`, \\`doctor.landscape-missing\\`,\n \\`doctor.service-unbound\\` (never raised inside the docs repo itself, where\n having no service binding is the correct state), \\`doctor.service-unknown\\`\n (the same state \\`loam status\\` reports as \\`next.adopt-bound\\`),\n \\`doctor.likec4-config-missing\\`, and the two about this repo's own generated\n command and skill files: \\`doctor.agent-files-missing\\` and\n \\`doctor.agent-files-stale\\`.\n EXPECT the second one on the first \\`loam doctor\\` after an upgrade. It is not\n evidence that anything was edited or broken: staleness reads the version\n stamp only. Re-running \\`loam init\\` refreshes an unchanged pointer only when\n its current bytes still match the digest recorded in \\`loam.json\\`; an\n unrecorded or customized file stays in place for a human to review.\n- \\`loam rebase <FEAT>\\` writes the baseline pins: \\`Based-On:\\` under every\n MODIFIED/REMOVED requirement in the feature's spec deltas, and\n \\`x-loam-based-on\\` on every operation in its openapi.yaml, from the living text\n as it stands right now. Run it when you finish authoring a delta and again\n after you resolve a collision — it is the one mechanism standing between two\n features in flight and silently reverting each other's landed work. It refuses\n rather than inventing a pin, and re-pinning is not resolving: see\n \"The requirement baseline\".\n- \\`loam dependencies [<FEAT>]\\` derives the ordering of the features in flight\n from the artifacts themselves — requirement identities and OpenAPI\n operationIds, never from validator prose. It is the answer to every\n \"another feature in flight does this first\" finding, and to\n \\`delta.added-conflict\\` / \\`delta.modified-conflict\\`, which no ordering fixes.\n- \\`loam explore [<service>...]\\` reads the fleet around a change nobody has\n written down yet, and writes nothing. It exists because \\`loam new\\`'s\n \\`--touches\\` list is the hardest call in the cycle and the only one nothing\n downstream catches: a list short by one service produces a feature that\n validates, archives and ships with a consumer nobody updated. What it derives\n is mechanical — the one-hop ring, each service's rung, its living operations,\n who already calls whom, and which active features cover the same services. It\n does NOT decide which of those neighbours you actually change; that is a\n judgement about intent, and loam does not make those. \\`--op <operationId>\\`\n seeds from the operation's defining service; \\`--capability <id>\\` (repeatable)\n seeds from a declared capability's realizing services, and an id that seeds\n nothing lands in the additive \\`unresolvedCapabilities\\` — beside\n \\`loam list capabilities\\`, the rollup those seeds are read from.\n- \\`loam seed --from fleet.yaml\\` is the onboarding entry point for an empty\n fleet: a tiny human-authored YAML — a \\`services:\\` list of ids, optional\n \\`subsystems:\\`, \\`externals:\\` and \\`calls:\\` lines like\n \\`checkout -> payments\\` — templated mechanically into\n architecture/landscape.likec4 (one bound element per service, plain edges, no\n guessed operationIds) plus one services/<id>/ directory per service, in one\n journaled transaction. Not an extractor: the human stated every fact, and\n \"who calls whom\" is the one thing no generator can read off a repository.\n The seeded landscape carries a line-1 stamp; while it stands unedited,\n editing fleet.yaml and re-running regenerates the map, and the first hand\n edit makes the file yours — seed then refuses \\`seed-landscape-edited\\`\n rather than overwrite. Then adopt each service: \\`loam adopt --service <id>\\`.\n- \\`loam instructions [<page>] [args...]\\` prints one of the six workflow\n protocols — \\`loam-adopt\\`, \\`loam-feature\\`, \\`loam-implement\\`, \\`loam-check\\`,\n \\`loam-verify\\`, \\`loam-ship\\` — with \\`$1\\`, \\`$2\\` filled in from the arguments you\n pass, or one of the four reference pages — \\`loam-codes\\`, \\`loam-spine\\`,\n \\`loam-authoring\\`, \\`loam-done-check\\` — which take no arguments. With no\n argument at all it lists both sets. The pages ship inside the binary, so they\n describe the loam you are about to run rather than the one that scaffolded the\n repository; the command and skill files \\`loam init\\` writes are pointers at it,\n and so is AGENTS.md's own \"The reference pages\" section. It reads no\n \\`loam.json\\` and no docs repo, deliberately: \\`loam-adopt\\`'s own first step is\n to run \\`loam init\\` when there is no config, so it cannot be the step that\n requires one.\n`;\n\n/**\n * The reference page's body, up to the modules that continue it. The bullets\n * are ordered so `status` — the orientation command, the one to run when you\n * have lost the session — is read first.\n */\nexport const CODE_MAP = MAP_INTRO + STATUS_COMMAND + MAP_REST;\n"]}
1
+ {"version":3,"file":"command-map.js","sourceRoot":"","sources":["../../../../src/core/agent/agents-md/command-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;CAuB7B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B9B,CAAC;AAEF;;;GAGG;AACH,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BjB,CAAC;AAEF,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2JhB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,GAAG,cAAc,GAAG,QAAQ,CAAC","sourcesContent":["/**\n * Reading loam's output, in its two deliveries.\n *\n * {@link READING_OUTPUT} and {@link REFERENCE_PAGES} are what AGENTS.md keeps:\n * the `--json` envelope, the rule to branch on codes rather than prose, and the\n * index of the pages this file no longer carries. {@link CODE_MAP} is the\n * code-by-code map of `status`, `validate`, `doctor`, `rebase`, `dependencies`,\n * `explore`, `seed` and `instructions` — the part `loam instructions loam-codes`\n * prints. ../workflows/reference/codes.ts composes it with `./map/`,\n * `./map/lenses/` and `./refusals.ts`, in exactly the order ../agents-md.ts used\n * to concatenate them, so the assembled page is the section byte for byte.\n *\n * WHY THE MAP LEFT THE FILE, given that this package's whole subject is a file.\n * AGENTS.md is auto-loaded from the working directory by every agents.md-aware\n * host, on every session, and `loam init` writes it once and never refreshes it.\n * It reached 109,399 bytes; Codex truncates the AGENTS.md chain at 32,768 bytes\n * and Windsurf caps a workspace rule file at 12,000 characters, both silently.\n * Roughly seventy percent of the document was being dropped with no error and no\n * way for the reader to tell which half it held — and this section alone was\n * 44,433 of those bytes, 41% of the file. It is also the half a reader consults\n * at a moment it can name (a run reported something) rather than the half it\n * needs to form a question at all, which is what decided which side of the split\n * it went. ../workflows/reference/reference.ts carries the rest of that argument.\n *\n * The move is a move and not a deletion: every byte is still shipped, still in\n * the same corpus test/codes-drift.test.ts reads (AGENTS_MD + PROTOCOLS), and\n * still one command away rather than one file away.\n *\n * WHAT THE MAP IS, AND WHAT IT DELIBERATELY IS NOT. The unique content is which\n * codes which INVOCATION can raise: nothing else in the corpus says that, and an\n * agent choosing what to run needs it. What it does not carry is a per-code\n * gloss of what each code MEANS. That half used to be written twice — once here,\n * once in the /loam-check fix tables — in two independently worded places, in a\n * file `loam init` writes once and never refreshes, so the two drifted with\n * nothing able to say which had rotted. `loam explain <code>` now answers\n * meaning, severity and fix for every code named below, out of the running\n * binary. test/agent-contract.test.ts holds that pointer honest: every code\n * backticked in the corpus must resolve through `explainSubject`, so a family\n * added here without an explanation fails by name. Keep the code list; add the\n * gloss only where it says something `explain` cannot — a condition on when the\n * invocation raises it at all, or a cross-reference between two codes.\n *\n * ONE PARAGRAPH THE MOVE MADE FALSE, and the reason to look for others like it.\n * The `agents.stale` entry read \"one check on this very file … the reason to\n * distrust what you are reading\", which was true while these bytes WERE the\n * scaffolded AGENTS.md. On a page printed by the binary it is exactly backwards\n * — this page cannot be stale, and the file it warns about is somewhere else —\n * so the entry names AGENTS.md instead. A section that talks about its own\n * document does not survive being moved to another one; that is the class to\n * check for whenever anything else leaves this file.\n *\n * READING_OUTPUT and REFERENCE_PAGES are sections of the AGENTS.md template.\n * ../agents-md.ts assembles the document by PLAIN CONCATENATION — no join\n * separator — so every section starts at the first character of its opening line\n * and ends with the newline that closes its last one. Keep that shape when\n * editing, or two sections glue onto one line in every docs repo loam scaffolds\n * from now on. CODE_MAP owes the same shape for the same reason, one level up:\n * reference/codes.ts concatenates it with the modules listed above.\n */\nimport { STATUS_COMMAND } from \"./map/status.js\";\n\n/**\n * The section AGENTS.md keeps. Three facts an agent needs BEFORE it can form a\n * question — that every command speaks JSON, that the envelope survives a bad\n * invocation, and that codes are the contract and prose is not — plus the two\n * places the detail went.\n */\nexport const READING_OUTPUT = `## Reading loam's output\n\nEvery command takes \\`--json\\`, and the envelope holds even when the INVOCATION\nis wrong: with \\`--json\\` anywhere in the arguments, an unknown flag, unknown\ncommand or missing argument still yields \\`{ ok: false, error: { code:\n\"invalid-option\" } }\\` on stdout with exit 1 (commander's own diagnostic goes\nto stderr; \\`--help\\` and \\`--version\\` output pass through untouched). Without\n\\`--json\\` the same refusal is plain text on stderr with nothing on stdout.\n\nBranch on \\`findings[].code\\`, never on the prose — the wording changes, the codes do\nnot.\n\n**What a code MEANS is one command away**, and the answer describes the loam you\nare running rather than the one that scaffolded this repo. \\`loam explain <code>\\`\ngives one code's meaning, its severity in each scope that grades it, and its fix;\n\\`loam explain --codes\\` lists the whole vocabulary and \\`loam explain --codes --json\\`\nis the machine-readable form. Reach for it rather than for a generated pointer:\nthis page is printed by the binary that defines the codes.\n\n**Which codes an INVOCATION can raise** is the other half, and it is the one\nthing nothing else carries. It is a reference page rather than a section here:\n\\`loam instructions loam-codes\\`.\n\n`;\n\n/**\n * The index. A reference nobody can find is content deleted with extra steps,\n * so the exact command is spelled per page rather than described — and\n * test/agents.test.ts asserts that every name in REFERENCES appears here inside\n * its own `loam instructions` line, which is what makes a renamed page fail\n * loudly instead of leaving a pointer at nothing.\n */\nexport const REFERENCE_PAGES = `## The reference pages\n\nThis file is the orientation: the layout, the cycle, what gates and what only\nadvises, and what the words mean. Four things it does NOT carry — the grammars\nyou consult while writing ONE document, and the inventory of what each command\ncan report — are **reference pages**, printed by the binary rather than written\nhere. That is why: this file is auto-loaded on every session and never\nrefreshed, while a page out of the binary describes the loam you are actually\nrunning, and is read at the one moment it is needed.\n\n- \\`loam instructions loam-codes\\` — which codes each invocation can raise:\n \\`validate --service\\` / \\`--feature\\` / \\`--all\\`, \\`status\\`, \\`doctor\\`, \\`gate\\`,\n \\`context\\`, \\`diff\\`, \\`explore\\`, \\`dependencies\\`, \\`rebase\\`, \\`seed\\`, \\`subsystem\\`,\n \\`mcp\\`, the use-case views, the containment refusals, the error envelope, and\n the OpenSpec migration surface.\n- \\`loam instructions loam-spine\\` — the ID spine: every join between the\n artifacts (\\`operationId\\`, message name, \\`Covers:\\`, \\`Requires:\\`,\n \\`Capability:\\`, \\`Realizes:\\`), the \\`Based-On:\\` baseline pins on both the\n requirement and the contract axis, how to draw a shared broker, and how to\n declare a message produced outside the fleet.\n- \\`loam instructions loam-authoring\\` — the grammars you author against:\n the architecture spec axis (\\`arch.spec.md\\` and its \\`Covers:\\` line), the\n generated Gherkin suite, and frontmatter with the vouch chain behind it.\n- \\`loam instructions loam-done-check\\` — the done-check: how \\`loam verify\\`\n derives its claims, the three channels that may answer them, the federated\n recording form, and what separates **verified** from **attested**.\n\nEach takes no arguments and prints whole. \\`loam instructions\\` with no argument\nlists them beside the six workflow protocols; \\`loam explain <code>\\` answers any\nsingle code without opening a page at all.\n\n`;\n\n/**\n * The reference page's own opening: what the map is, where the /loam-check fix\n * tables sit beside it, and the one family `loam explain` cannot answer.\n */\nconst MAP_INTRO = `**What this page is.** It says which codes an INVOCATION can raise. It does\nNOT say what they mean: that half is one command away and describes the loam you\nare running rather than the one that scaffolded this repo. \\`loam explain <code>\\`\ngives one code's meaning, its severity in each scope that grades it, and its fix;\n\\`loam explain --codes\\` lists the whole vocabulary and \\`loam explain --codes --json\\`\nis the machine-readable form. The one family \\`explain\\` does not answer\nis the OpenSpec migration surface (\\`openspec.*\\`, \\`mapping.*\\`) — those commands run\nbefore a repository has a governed loop to look a code up from — so those entries\nalone keep their notes here.\n\nThe code-by-code fix table is a different document again: it lives in the\n\\`/loam-check\\` command \\`loam init\\` lays\ndown. \\`init\\` writes that body twice for every tool it configures: once as a slash\ncommand in the tool's own command directory (you type it), and once as an Agent\nSkill at \\`<tool-dir>/skills/loam-check/SKILL.md\\` (the model loads it by itself when\nthe task matches the skill's description). Same body, two ways in.\n\nWhich tools get them is detected from the repo: \\`loam init\\` scans for the\ndot-directories of the tools it knows (\\`.claude\\`, \\`.cursor\\`, \\`.gemini\\`, …) and\nwrites for the ones it finds, falling back to Claude Code when it finds none.\n\\`loam init --tools <ids|all>\\` overrides the scan — an unknown id is refused\n(\\`invalid-option\\`) rather than skipped — and \\`--no-commands\\` / \\`--no-skills\\`\neach suppress one delivery. The tools written for are recorded in\n\\`loam.json\\` as \\`agentTools\\`, which is how \\`loam doctor\\` tells a file missing\nbecause the binary grew a new command from one missing because nobody ever\nselected that tool.\n\nThe map:\n\n`;\n\nconst MAP_REST = `- \\`loam validate --service <id>\\` grades one service's own axes: \\`service.unknown\\`,\n \\`service.no-model\\`, \\`service.no-spec\\`, \\`service.no-openapi\\`, \\`c4.invalid\\`,\n \\`c4.no-relationships\\`, \\`requirements.missing-scenarios\\`,\n \\`requirements.stepless-scenario\\`, \\`requirements.assertionless-scenario\\`,\n \\`requirements.examples-unbound\\`, \\`requirements.examples-unreferenced\\`,\n \\`spec.merge-conflict\\`, \\`spec.duplicate-requirement\\`, \\`spec.no-requirements\\`,\n \\`spec.repeated-operations\\`, \\`spec.repeated-covers\\`, \\`openapi.invalid\\`,\n \\`openapi.duplicate-operationid\\`, \\`openapi.response-undescribed\\`,\n \\`openapi.ref-unresolved\\`, \\`api.ungoverned\\`, \\`api.ops-unlinked\\`,\n \\`api.requirement-deprecated\\`, \\`spec-api.op-undefined\\`,\n \\`spine.landscape-invalid\\`, \\`spine.op-undefined\\`, \\`spine.op-link-missing\\`,\n \\`spine.op-deprecated\\`; the async contract axis (AsyncAPI 3):\n \\`service.no-asyncapi\\`, \\`asyncapi.invalid\\`, \\`asyncapi.duplicate-message\\`,\n \\`asyncapi.payload-undescribed\\`, \\`asyncapi.ref-unresolved\\`,\n \\`spine.message-undefined\\`, \\`spec-event.message-undefined\\`,\n \\`spine.message-unproduced\\`, \\`spine.message-external\\`,\n \\`asyncapi.message-contested\\`, \\`event.messages-unlinked\\`, \\`event.ungoverned\\`,\n \\`event.covered\\`; and the architecture spec axis: \\`covers.unknown\\`,\n \\`health.invalid\\`, \\`health.uncovered\\`, \\`health.dependency-unmodelled\\`.\n Run inside the service's own repo, once a generated suite exists under\n \\`<gherkinDir>/loam/\\`, it also grades that suite against the living specs:\n \\`gherkin.missing\\`, \\`gherkin.stale\\`, \\`gherkin.orphaned\\`. A service that never\n ran \\`loam gherkin\\` stays quiet — that whole trio is conditional on the suite\n existing here — and a file tagged with a feature still in flight answers to\n that feature, not to the living spec it has not merged into yet.\n- \\`loam validate --feature <id>\\` grades a change's three axes against each other and\n against the fleet in flight: \\`delta.invalid\\`, \\`delta.nothing-tagged\\`,\n \\`delta.service-unknown\\`, \\`delta.service-id-invalid\\`, \\`spec-api.op-undefined\\`,\n \\`spec-api.op-pending\\`, \\`c4-api.op-undefined\\`, \\`c4-api.op-pending\\`,\n \\`c4-api.op-deprecated\\`, \\`c4.op-ungoverned\\`, \\`c4.op-link-missing\\`,\n \\`c4.service-binding-invalid\\`, \\`api.op-unconsumed\\`,\n \\`service.no-requirement-delta\\`, \\`archedge.uncovered\\`, and — the same four\n codes service scope raises, graded here on the feature's own spec.md and\n arch.spec.md deltas, because every one of those breaches merges into the\n living document — \\`spec.repeated-operations\\`, \\`spec.repeated-covers\\`,\n \\`spec.merge-conflict\\` and \\`requirements.stepless-scenario\\`. Then the\n architecture spec axis (\\`c4.uncovered\\`, plus \\`covers.unknown\\` on the\n feature's arch.spec.md deltas), the delta-shape group:\n \\`delta.unknown-section\\`, \\`delta.no-delta-sections\\`,\n \\`delta.requirement-not-merged\\`, \\`delta.modified-unknown\\`, \\`delta.removed-unknown\\`,\n \\`delta.added-duplicate\\`, \\`delta.added-near-duplicate\\`, \\`delta.modified-pending\\`,\n \\`delta.removed-pending\\`, \\`delta.added-conflict\\`, \\`delta.modified-conflict\\`,\n \\`delta.living-duplicate-requirement\\`, and the API-removal group:\n \\`openapi.remove-marker-anonymous\\`, \\`openapi.remove-op-consumed\\`.\n The event axis is graded in feature scope too — the \\`asyncapi.*\\`\n baseline/removal/conflict codes plus \\`c4-event.*\\` and \\`spec-event.*\\`.\n Where a finding names another feature in flight (\\`delta.*-pending\\`,\n \\`delta.added-conflict\\`, \\`delta.modified-conflict\\`, \\`spec-api.op-pending\\`,\n \\`c4-api.op-pending\\`) the ORDER is the answer, and \\`loam dependencies --json\\`\n is what computes it: \\`order\\` is a dependency-first sequence, \\`conflicts\\`\n the pairs no ordering fixes, \\`cycles\\` the ones that need a human.\n- \\`loam validate --all\\` runs both of those for everything and adds the fleet\n cross-check: \\`landscape.missing\\`, \\`landscape.invalid\\`,\n \\`landscape.merge-conflict\\`, \\`landscape.service-unmodelled\\`,\n \\`landscape.service-undocumented\\`, \\`landscape.binding-unknown\\`,\n \\`landscape.binding-duplicate\\`, the fleet-shape advisories\n \\`landscape.platform-candidate\\`, \\`landscape.datastore-private\\` and\n \\`landscape.datastore-shared\\`, \\`service.id-invalid\\` (fleet scope only, and\n graded before the map is opened, so it stands even when the landscape is\n missing or unreadable), \\`service.likec4-config-missing\\` (warn, per\n service: a model with no \\`likec4.config.json\\` beside it renders nowhere from\n the docs root — \\`loam subsystem sync\\` writes the file), a per-service \\`sources.unverifiable-from-here\\`, and\n one check on the docs repo's own AGENTS.md, \\`agents.stale\\`. That last one is\n the reason to distrust THAT file rather than this page: it fires when its\n version stamp on line 1 (\\`<!-- generated by loam vX.Y.Z -->\\`) is missing or\n older than the running binary, which is exactly when what it says may name a\n loam that no longer exists. It is never refreshed automatically — your edits\n outrank the template, so detection is all loam does, and a hand-curated file\n silences it the same way, by keeping the stamp current. This page has no such\n failure mode: it is printed by the binary it describes.\n \\`loam validate --all\\`'s \\`--json\\` payload also carries the additive \\`scorecard\\` key — per-axis ceiling-vs-actual fleet\n aggregates, the text report appending the same table; derived per run and never stored, so week-over-week is the pipeline's job: capture the key per run into a metrics store.\n \\`scorecard.adoption\\` counts the services PARTICIPATING in each contract axis\n (requirements, arch, openapi, asyncapi, permissions, capabilities; the\n denominator is \\`scorecard.services\\`) — an axis at 0 of N reads \"not started\",\n which is expected during staged adoption and distinct from partially adopted.\n Text mode folds the warnings such an axis alone causes under one banner per\n axis and says so; \\`--json\\` carries every finding unchanged, and the summary\n counts, exit codes and \\`--strict\\` are identical either way.\n- \\`loam doctor\\` is read-only local/fleet preflight — the first thing to run in a\n repo that behaves as though the fleet were empty. Every finding carries a\n \\`fix\\` field naming the exact command or edit, so this is the one family you\n rarely need \\`loam explain\\` for. Its blockers are \\`doctor.config-missing\\`,\n \\`doctor.config-invalid\\`, \\`doctor.docs-missing\\`, \\`doctor.services-missing\\`, and\n the three that say the fleet map cannot be read at all —\n \\`doctor.landscape-merge-conflict\\`, \\`doctor.landscape-invalid\\` and\n \\`doctor.landscape-unreadable\\`. It also grades what a WRITE that did not finish\n left in the docs repo, reported as \\`writePath\\` beside the findings:\n \\`doctor.docs-locked\\`, \\`doctor.commit-interrupted\\`, \\`doctor.commit-unreadable\\`\n and \\`doctor.staging-temps\\`. When this repo is a service repo that same scan\n covers \\`<gherkinDir>/loam/\\`, the root \\`loam gherkin\\` commits into.\n Accessibility, portability and incomplete binding stay warnings:\n \\`doctor.docs-unreadable\\`, \\`doctor.docs-readonly\\`, \\`doctor.docs-absolute\\`,\n \\`doctor.inventory-unreadable\\`, \\`doctor.landscape-missing\\`,\n \\`doctor.service-unbound\\` (never raised inside the docs repo itself, where\n having no service binding is the correct state), \\`doctor.service-unknown\\`\n (the same state \\`loam status\\` reports as \\`next.adopt-bound\\`),\n \\`doctor.likec4-config-missing\\`, and the two about this repo's own generated\n command and skill files: \\`doctor.agent-files-missing\\` and\n \\`doctor.agent-files-stale\\`.\n EXPECT the second one on the first \\`loam doctor\\` after an upgrade. It is not\n evidence that anything was edited or broken: staleness reads the version\n stamp only. Re-running \\`loam init\\` refreshes an unchanged pointer only when\n its current bytes still match the digest recorded in \\`loam.json\\`; an\n unrecorded or customized file stays in place for a human to review.\n- \\`loam rebase <FEAT>\\` writes the baseline pins: \\`Based-On:\\` under every\n MODIFIED/REMOVED requirement in the feature's spec deltas, and\n \\`x-loam-based-on\\` on every operation in its openapi.yaml, from the living text\n as it stands right now. Run it when you finish authoring a delta and again\n after you resolve a collision — it is the one mechanism standing between two\n features in flight and silently reverting each other's landed work. It refuses\n rather than inventing a pin, and re-pinning is not resolving: see\n \"The requirement baseline\".\n- \\`loam dependencies [<FEAT>]\\` derives the ordering of the features in flight\n from the artifacts themselves — requirement identities and OpenAPI\n operationIds, never from validator prose. It is the answer to every\n \"another feature in flight does this first\" finding, and to\n \\`delta.added-conflict\\` / \\`delta.modified-conflict\\`, which no ordering fixes.\n- \\`loam explore [<service>...]\\` reads the fleet around a change nobody has\n written down yet, and writes nothing. It exists because \\`loam new\\`'s\n \\`--touches\\` list is the hardest call in the cycle and the only one nothing\n downstream catches: a list short by one service produces a feature that\n validates, archives and ships with a consumer nobody updated. What it derives\n is mechanical — the one-hop ring, each service's rung, its living operations,\n who already calls whom, and which active features cover the same services. It\n does NOT decide which of those neighbours you actually change; that is a\n judgement about intent, and loam does not make those. \\`--op <operationId>\\`\n seeds from the operation's defining service; \\`--capability <id>\\` (repeatable)\n seeds from a declared capability's realizing services, and an id that seeds\n nothing lands in the additive \\`unresolvedCapabilities\\` — beside\n \\`loam list capabilities\\`, the rollup those seeds are read from.\n- \\`loam seed --from fleet.yaml\\` is the onboarding entry point for an empty\n fleet: a tiny human-authored YAML — a \\`services:\\` list of ids, optional\n \\`subsystems:\\`, \\`externals:\\` and \\`calls:\\` lines like\n \\`checkout -> payments\\` — templated mechanically into\n architecture/landscape.likec4 (one bound element per service, plain edges, no\n guessed operationIds) plus one services/<id>/ directory per service, in one\n journaled transaction. Not an extractor: the human stated every fact, and\n \"who calls whom\" is the one thing no generator can read off a repository.\n The seeded landscape carries a line-1 stamp; while it stands unedited,\n editing fleet.yaml and re-running regenerates the map, and the first hand\n edit makes the file yours — seed then refuses \\`seed-landscape-edited\\`\n rather than overwrite. Then adopt each service: \\`loam adopt --service <id>\\`.\n- \\`loam instructions [<page>] [args...]\\` prints one of the six workflow\n protocols — \\`loam-adopt\\`, \\`loam-feature\\`, \\`loam-implement\\`, \\`loam-check\\`,\n \\`loam-verify\\`, \\`loam-ship\\` — with \\`$1\\`, \\`$2\\` filled in from the arguments you\n pass, or one of the four reference pages — \\`loam-codes\\`, \\`loam-spine\\`,\n \\`loam-authoring\\`, \\`loam-done-check\\` — which take no arguments. With no\n argument at all it lists both sets. The pages ship inside the binary, so they\n describe the loam you are about to run rather than the one that scaffolded the\n repository; the command and skill files \\`loam init\\` writes are pointers at it,\n and so is AGENTS.md's own \"The reference pages\" section. It reads no\n \\`loam.json\\` and no docs repo, deliberately: \\`loam-adopt\\`'s own first step is\n to run \\`loam init\\` when there is no config, so it cannot be the step that\n requires one.\n`;\n\n/**\n * The reference page's body, up to the modules that continue it. The bullets\n * are ordered so `status` — the orientation command, the one to run when you\n * have lost the session — is read first.\n */\nexport const CODE_MAP = MAP_INTRO + STATUS_COMMAND + MAP_REST;\n"]}
@@ -25,7 +25,16 @@ export const SUBSYSTEM_COMMANDS = `- \`loam subsystem <verb>\` manages the group
25
25
  \`unfiledServices\`); \`history <name>\` asks git how a service or subsystem
26
26
  moved and answers nothing — exit 0, no finding — when git will not say;
27
27
  \`sync\` regenerates \`architecture/subsystems.likec4\`, the one repair for
28
- \`subsystem.views-stale\`; a landscape view claiming a generated id is
28
+ \`subsystem.views-stale\`, and also writes one create-only
29
+ \`services/<…>/<id>/likec4.config.json\` per service model (never rewritten, never
30
+ removed; the team may edit it; commit the new files before a \`move\`, which
31
+ refuses over untracked paths) — the one thing that makes a model renderable
32
+ from the docs root beside the fleet map, and the repair for
33
+ \`service.likec4-config-missing\`. A flat fleet with no subsystems still owes
34
+ this run: the verb is the tree's, the file is the renderer's. With more than
35
+ one project \`likec4 validate\` at the docs root needs \`--project <name>\`
36
+ (\`build\` and \`export\` take every project); a
37
+ landscape view claiming a generated id is
29
38
  \`subsystem.view-id-collision\`. Each generated view is titled by its PATH —
30
39
  every marked ancestor's label and its own, which the renderer reads as
31
40
  folders — where a label is the marker's \`title\` or the directory name; the
@@ -1 +1 @@
1
- {"version":3,"file":"subsystem.js","sourceRoot":"","sources":["../../../../../src/core/agent/agents-md/map/subsystem.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BjC,CAAC","sourcesContent":["/**\n * The `loam subsystem` half of the command map — its own section module\n * because command-map.ts sat against the file-line limit and the agents-md\n * package against the five-file cap, so the tree surface's documentation\n * lives one package down and is concatenated after the map.\n *\n * Same assembly contract as every section: ../agents-md.ts concatenates with\n * NO join separator, so this string starts at the first character of its\n * opening line and ends with the newline that closes its last one.\n */\nexport const SUBSYSTEM_COMMANDS = `- \\`loam subsystem <verb>\\` manages the grouping tree under \\`services/\\` — the\n navigable tree no identity depends on: a service id stays its leaf directory\n name, every join key is byte-identical before and after a move, and the tree\n carries no policy. Verbs: \\`new <name> [--under <sub>] [--title] [--description]\n [--owner]\\` creates a group (an empty one is legal — its \\`subsystem.yaml\\` is a\n marker, never a member list); \\`move <name>... --into <sub|.>\\` relocates\n services and whole subtrees in ONE journaled transaction over the renames\n plus the regenerated views file (\\`--into .\\` unfiles; the renames and the\n regenerated views file are staged in git without committing, where git answers); \\`rename <old> <new>\\` renames a\n group through the same transaction (services are never renamed — the id is\n the identity); \\`rm <name>\\` removes an EMPTY group; \\`list\\` shows the tree\n with member counts and the unfiled count (unfiled is permanent and normal —\n a count, never a finding; \\`loam list --json\\` carries the same facts as\n additive keys \\`services[].subsystem\\`, \\`subsystems[]\\` and\n \\`unfiledServices\\`); \\`history <name>\\` asks git how a service or subsystem\n moved and answers nothing — exit 0, no finding — when git will not say;\n \\`sync\\` regenerates \\`architecture/subsystems.likec4\\`, the one repair for\n \\`subsystem.views-stale\\`; a landscape view claiming a generated id is\n \\`subsystem.view-id-collision\\`. Each generated view is titled by its PATH —\n every marked ancestor's label and its own, which the renderer reads as\n folders — where a label is the marker's \\`title\\` or the directory name; the\n marker's \\`description\\` rides along. So editing \\`subsystem.yaml\\` changes\n what the renderer LABELS the group, a parent's title changes every view\n beneath it, and either makes the file stale until the next \\`sync\\`. It is generated wholesale:\n an authored diagram of the same services belongs in the landscape or in a\n file of your own name, never here. \\`loam adopt --subsystem <name>\\` briefs a new\n service's baseline directly into a group, so adoption need not land unfiled.\n`;\n"]}
1
+ {"version":3,"file":"subsystem.js","sourceRoot":"","sources":["../../../../../src/core/agent/agents-md/map/subsystem.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCjC,CAAC","sourcesContent":["/**\n * The `loam subsystem` half of the command map — its own section module\n * because command-map.ts sat against the file-line limit and the agents-md\n * package against the five-file cap, so the tree surface's documentation\n * lives one package down and is concatenated after the map.\n *\n * Same assembly contract as every section: ../agents-md.ts concatenates with\n * NO join separator, so this string starts at the first character of its\n * opening line and ends with the newline that closes its last one.\n */\nexport const SUBSYSTEM_COMMANDS = `- \\`loam subsystem <verb>\\` manages the grouping tree under \\`services/\\` — the\n navigable tree no identity depends on: a service id stays its leaf directory\n name, every join key is byte-identical before and after a move, and the tree\n carries no policy. Verbs: \\`new <name> [--under <sub>] [--title] [--description]\n [--owner]\\` creates a group (an empty one is legal — its \\`subsystem.yaml\\` is a\n marker, never a member list); \\`move <name>... --into <sub|.>\\` relocates\n services and whole subtrees in ONE journaled transaction over the renames\n plus the regenerated views file (\\`--into .\\` unfiles; the renames and the\n regenerated views file are staged in git without committing, where git answers); \\`rename <old> <new>\\` renames a\n group through the same transaction (services are never renamed — the id is\n the identity); \\`rm <name>\\` removes an EMPTY group; \\`list\\` shows the tree\n with member counts and the unfiled count (unfiled is permanent and normal —\n a count, never a finding; \\`loam list --json\\` carries the same facts as\n additive keys \\`services[].subsystem\\`, \\`subsystems[]\\` and\n \\`unfiledServices\\`); \\`history <name>\\` asks git how a service or subsystem\n moved and answers nothing — exit 0, no finding — when git will not say;\n \\`sync\\` regenerates \\`architecture/subsystems.likec4\\`, the one repair for\n \\`subsystem.views-stale\\`, and also writes one create-only\n \\`services/<…>/<id>/likec4.config.json\\` per service model (never rewritten, never\n removed; the team may edit it; commit the new files before a \\`move\\`, which\n refuses over untracked paths) — the one thing that makes a model renderable\n from the docs root beside the fleet map, and the repair for\n \\`service.likec4-config-missing\\`. A flat fleet with no subsystems still owes\n this run: the verb is the tree's, the file is the renderer's. With more than\n one project \\`likec4 validate\\` at the docs root needs \\`--project <name>\\`\n (\\`build\\` and \\`export\\` take every project); a\n landscape view claiming a generated id is\n \\`subsystem.view-id-collision\\`. Each generated view is titled by its PATH —\n every marked ancestor's label and its own, which the renderer reads as\n folders — where a label is the marker's \\`title\\` or the directory name; the\n marker's \\`description\\` rides along. So editing \\`subsystem.yaml\\` changes\n what the renderer LABELS the group, a parent's title changes every view\n beneath it, and either makes the file stale until the next \\`sync\\`. It is generated wholesale:\n an authored diagram of the same services belongs in the landscape or in a\n file of your own name, never here. \\`loam adopt --subsystem <name>\\` briefs a new\n service's baseline directly into a group, so adoption need not land unfiled.\n`;\n"]}
@@ -10,7 +10,7 @@ export const LOAM_ADOPT = {
10
10
  "wire this repo (`loam init`, then `loam doctor`) if it has no ./loam.json yet",
11
11
  "`loam adopt` — the brief: the order to read the service in, every file to write, the grammar of each, and what the fleet map already says",
12
12
  "walk the service in the brief's order — shape first, then one surface at a time — keeping the list of every path you open, because that list becomes `sources`",
13
- "write the artifacts, everything `status: draft`",
13
+ "write the artifacts, everything `status: draft`, then `loam subsystem sync` for the per-service LikeC4 project file — loam's, never yours",
14
14
  "validate the service, then validate the whole fleet — a baseline that passes one and fails the other is documented and invisible",
15
15
  "hand back with what you could not determine from the code, which directories you never opened, the branch-to-scenario count per operation, and three behaviours your documents do not describe — then let a human vouch",
16
16
  ],
@@ -70,7 +70,14 @@ thin baseline that validates is thin, not done.
70
70
  tying the document to the repository. Never pad it with paths you did not read:
71
71
  it is a record, and \`loam vouch\` hashes exactly what it names.
72
72
  3. Write the artifacts under \`services/$1/\`, in the order the brief lists them, and
73
- make the landscape edit the brief asks for.
73
+ make the landscape edit the brief asks for. Then run \`loam subsystem sync\` (from
74
+ this repo or the docs repo — it resolves \`docsDir\`): once the docs root carries
75
+ the \`likec4.config.json\` step 0's doctor asks for, it writes
76
+ \`services/$1/likec4.config.json\` — and the same file beside any other service
77
+ model in the docs repo still missing one — the LikeC4 project that makes a
78
+ model.likec4 renderable beside the fleet map from the docs root. Those files are
79
+ loam's — never write one yourself, never list it in \`sources\`, never diff it —
80
+ and \`sync\` leaves them untracked for you to commit.
74
81
  Everything \`status: draft\`. Never write \`last_verified\`, \`sources_digest\`,
75
82
  \`content_digest\` or \`sources_files\`.
76
83
  4. \`loam validate --service $1 --json\`. Fix every error. \`sources.unvouched\` is
@@ -84,8 +91,11 @@ thin baseline that validates is thin, not done.
84
91
  \`landscape.binding-unknown\` / \`landscape.binding-duplicate\` mean it landed wrong,
85
92
  and \`landscape.missing\` means there is no map at all. A baseline that passes step 4
86
93
  and fails this one is documented and invisible.
94
+ \`service.likec4-config-missing\` (a warning) means step 3's sync did not run; the
95
+ fix is that command.
87
96
  Done, stated once: step 4 clean when run from inside the service's own repo, and
88
- this run reporting no \`landscape.*\` finding. The fleet run is never SILENT —
97
+ this run reporting no \`landscape.*\` finding and no \`service.likec4-config-missing\`.
98
+ The fleet run is never SILENT —
89
99
  \`sources.unverifiable-from-here\` (severity ok) appears per service as a
90
100
  confirmation, not work — so "keep going until validation is quiet" is the wrong
91
101
  loop; the two runs are the test.