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

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 (92) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +6 -6
  3. package/ROADMAP.md +155 -18
  4. package/SCHEMA.md +33 -1
  5. package/dist/commands/archive/plan/gate.js +18 -12
  6. package/dist/commands/archive/plan/gate.js.map +1 -1
  7. package/dist/commands/archive/plan/landscape.js +19 -0
  8. package/dist/commands/archive/plan/landscape.js.map +1 -1
  9. package/dist/commands/archive/run.js +48 -3
  10. package/dist/commands/archive/run.js.map +1 -1
  11. package/dist/commands/seed/plan.js +1 -1
  12. package/dist/commands/seed/plan.js.map +1 -1
  13. package/dist/commands/subsystem/sync.js +1 -1
  14. package/dist/commands/subsystem/sync.js.map +1 -1
  15. package/dist/commands/subsystem/txn/txn.js +1 -1
  16. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  17. package/dist/commands/subsystem/txn/views.js +23 -11
  18. package/dist/commands/subsystem/txn/views.js.map +1 -1
  19. package/dist/commands/validate/arch-coverage.js +11 -1
  20. package/dist/commands/validate/arch-coverage.js.map +1 -1
  21. package/dist/commands/validate/fleet/obligations.js +43 -9
  22. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  23. package/dist/commands/validate/fleet/usecases/steps.js +3 -3
  24. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  25. package/dist/commands/validate/fleet/views/ids.js +6 -1
  26. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  27. package/dist/commands/validate/fleet/views/stale.js +15 -39
  28. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  29. package/dist/commands/validate/service/service.js +1 -0
  30. package/dist/commands/validate/service/service.js.map +1 -1
  31. package/dist/commands/validate/service/specs.js +33 -1
  32. package/dist/commands/validate/service/specs.js.map +1 -1
  33. package/dist/core/agent/agents-md/map/subsystem.js +8 -1
  34. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  35. package/dist/core/agent/agents-md.js +4 -0
  36. package/dist/core/agent/agents-md.js.map +1 -1
  37. package/dist/core/agent/workflows/check.js +3 -0
  38. package/dist/core/agent/workflows/check.js.map +1 -1
  39. package/dist/core/brief/unchecked.js +1 -0
  40. package/dist/core/brief/unchecked.js.map +1 -1
  41. package/dist/core/c4/arch.js +72 -180
  42. package/dist/core/c4/arch.js.map +1 -1
  43. package/dist/core/c4/likec4.js +2 -0
  44. package/dist/core/c4/likec4.js.map +1 -1
  45. package/dist/core/c4/parsed/deployment.js +158 -0
  46. package/dist/core/c4/parsed/deployment.js.map +1 -0
  47. package/dist/core/c4/project/load.js +4 -0
  48. package/dist/core/c4/project/load.js.map +1 -1
  49. package/dist/core/c4/project/staged.js +220 -0
  50. package/dist/core/c4/project/staged.js.map +1 -0
  51. package/dist/core/c4/resolve/service.js +46 -0
  52. package/dist/core/c4/resolve/service.js.map +1 -1
  53. package/dist/core/c4/resolve/steps.js +144 -0
  54. package/dist/core/c4/resolve/steps.js.map +1 -0
  55. package/dist/core/c4/splice/delta-blocks.js +15 -2
  56. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  57. package/dist/core/c4/workspace.js +2 -0
  58. package/dist/core/c4/workspace.js.map +1 -1
  59. package/dist/core/coherence/coherence.js +36 -0
  60. package/dist/core/coherence/coherence.js.map +1 -1
  61. package/dist/core/deployment/delta.js +188 -0
  62. package/dist/core/deployment/delta.js.map +1 -0
  63. package/dist/core/deployment/objects.js +145 -0
  64. package/dist/core/deployment/objects.js.map +1 -0
  65. package/dist/core/pack/living.js +19 -0
  66. package/dist/core/pack/living.js.map +1 -1
  67. package/dist/core/repo/authored/paths.js +18 -0
  68. package/dist/core/repo/authored/paths.js.map +1 -1
  69. package/dist/core/repo/paths.js +44 -3
  70. package/dist/core/repo/paths.js.map +1 -1
  71. package/dist/core/repo/tree/render/body.js +219 -0
  72. package/dist/core/repo/tree/render/body.js.map +1 -0
  73. package/dist/core/repo/tree/render/stale.js +26 -0
  74. package/dist/core/repo/tree/render/stale.js.map +1 -0
  75. package/dist/core/repo/tree/render/text.js +84 -0
  76. package/dist/core/repo/tree/render/text.js.map +1 -0
  77. package/dist/core/repo/tree/{views.js → render/views.js} +38 -34
  78. package/dist/core/repo/tree/render/views.js.map +1 -0
  79. package/dist/core/status/feature/artifacts.js +29 -4
  80. package/dist/core/status/feature/artifacts.js.map +1 -1
  81. package/dist/core/status/fleet/fleet.js +11 -1
  82. package/dist/core/status/fleet/fleet.js.map +1 -1
  83. package/dist/core/usecases/delta/overlay.js +28 -191
  84. package/dist/core/usecases/delta/overlay.js.map +1 -1
  85. package/dist/core/usecases/fleet.js.map +1 -1
  86. package/dist/core/usecases/operations.js +2 -2
  87. package/dist/core/usecases/operations.js.map +1 -1
  88. package/dist/core/vocabulary/issue.js +3 -0
  89. package/dist/core/vocabulary/issue.js.map +1 -1
  90. package/examples/docs/architecture/subsystems.likec4 +4 -0
  91. package/package.json +1 -1
  92. package/dist/core/repo/tree/views.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ybotok/loam",
3
- "version": "0.2.0-alpha.1",
3
+ "version": "0.2.0-alpha.4",
4
4
  "description": "Architecture-first semantic integrity and change governance for evolving software systems: connect C4, requirements, contracts, and verification evidence.",
5
5
  "author": "spentsov",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"views.js","sourceRoot":"","sources":["../../../../src/core/repo/tree/views.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The generated views file: `architecture/subsystems.likec4`, one LikeC4 view\n * per subsystem enumerating the services beneath it. GENERATED, never\n * authored — authored bytes stay authored (archive's verbatim-splice promise),\n * so the tree is mirrored into a file of its own that `loam subsystem sync`\n * owns outright, and staleness against the tree is a byte compare\n * (`subsystem.views-stale` in `validate --all`), never a parse: a spike\n * confirmed a views-only document does NOT parse standalone (its `include`\n * lines reference elements the landscape defines), so nothing in loam ever\n * loads this file (and rule 26 does not change that: this file is GENERATED and\n * holds STATIC views, whose contents loam may not read in any document) — only\n * the LikeC4 renderer, which merges the whole `architecture/` project per\n * likec4.config.json, resolves it.\n *\n * The output is deterministic and line-oriented, and both halves are the\n * contract: subsystems sorted by path, members sorted by id, ONE `include`\n * per line — so two concurrent moves into different groups touch different\n * lines and git merges the file without intervention, exactly as the\n * service-grouped landscape splice already achieves. Byte-reproducible on any\n * machine: no timestamps, no absolute paths, no readdir-order dependence (the\n * walk sorts, and every list is re-sorted here). Plain lexicographic sorts on\n * purpose — the ids' own grammar is ASCII, an INVALID name is still ordered\n * deterministically, and `compareIds`' numeric tokenizing lives in `repo/`,\n * which this package must not import (`walk.ts` says why the edge only points\n * the other way).\n *\n * Membership is TRANSITIVE: a subsystem's view includes every service beneath\n * it at any depth, so a parent's view is the whole group's map rather than an\n * empty frame around its children. An empty view body is legal LikeC4 (same\n * spike), so an empty subsystem renders as one — `subsystem new` must be\n * usable before anything moves in.\n *\n * Include lines name the landscape ELEMENT id, resolved through the same\n * element→service join `validate` uses (`serviceResolver`: explicit\n * `metadata { service }` binding first, then a title naming a real service) —\n * because element ids in real landscapes are not service ids, and a view of\n * raw service ids would silently render empty for every such fleet. A member\n * with no landscape element is omitted from the includes: the omission is\n * already an error (`landscape.service-unmodelled`), and rendering only what\n * the committed landscape resolves keeps the output a function of committed\n * bytes alone.\n */\nimport { type Elem } from \"../../c4/likec4.js\";\nimport { serviceResolver } from \"../../c4/resolve/service.js\";\nimport { servicesUnder } from \"./find.js\";\nimport type { FleetTree, SubsystemEntry, WalkedService } from \"./walk.js\";\n\n/**\n * The header every generated file opens with — stable bytes, part of the\n * byte-compare contract, naming the regenerator so a reader who found the\n * file first knows what owns it.\n */\nconst HEADER =\n \"// generated by loam — the subsystem tree under services/, one view per subsystem.\\n\" +\n \"// Do not edit: `loam subsystem sync` regenerates this file from the directories,\\n\" +\n \"// and `loam validate --all` reports `subsystem.views-stale` when it drifts.\\n\";\n\n/**\n * Render the views file for a walked tree, or null when the tree has no\n * subsystems — the file must then be ABSENT, so a flat fleet never carries a\n * generated artifact that says nothing.\n */\nexport function renderSubsystemViews(tree: FleetTree, elements: Elem[]): string | null {\n if (tree.subsystems.length === 0) return null;\n const elementOf = elementByService(tree, elements);\n const lines: string[] = [HEADER, \"views {\"];\n const subsystems = [...tree.subsystems].sort((a, b) => (pathKey(a) < pathKey(b) ? -1 : 1));\n for (const [i, sub] of subsystems.entries()) {\n if (i > 0) lines.push(\"\");\n lines.push(` // services/${pathKey(sub)}`);\n lines.push(` view ${subsystemViewId(sub)} {`);\n for (const member of membersOf(tree, sub)) {\n const element = elementOf.get(member.id);\n if (element !== undefined) lines.push(` include ${element}`);\n }\n lines.push(\" }\");\n }\n lines.push(\"}\");\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * The landscape element standing for each service id: for every element,\n * resolve it through the shared join, and let the SHALLOWEST element win\n * (document order breaking ties). Shallowest, because `serviceResolver`\n * resolves a service's own containers to the service too, and a view should\n * include the service-level box, not whichever nested container happened to\n * be declared first.\n */\nfunction elementByService(tree: FleetTree, elements: Elem[]): Map<string, string> {\n const known: ReadonlySet<string> = new Set(tree.services.map((s) => s.id));\n const resolve = serviceResolver(elements, known);\n const depth = (id: string): number => id.split(\".\").length;\n const out = new Map<string, string>();\n for (const e of elements) {\n const service = resolve(e.id);\n if (!known.has(service)) continue;\n const held = out.get(service);\n if (held === undefined || depth(e.id) < depth(held)) out.set(service, e.id);\n }\n return out;\n}\n\n/** Services beneath this subsystem at any depth, ordered by id — the transitive membership. */\nfunction membersOf(tree: FleetTree, sub: SubsystemEntry): WalkedService[] {\n return [...servicesUnder(tree, sub)].sort((a, b) => (a.id < b.id ? -1 : 1));\n}\n\n/** The sort key and the comment spelling: the subsystem's path from services/, joined. */\nfunction pathKey(sub: SubsystemEntry): string {\n return sub.path.join(\"/\");\n}\n\n/**\n * A LikeC4 identifier for the view, derived from the subsystem's path so\n * nesting reads in the name — and INJECTIVE over every path the name grammar\n * admits, because the renderer refuses the whole `architecture/` project over\n * a duplicate view id. The naive folding (every non-identifier byte and the\n * join separator alike becoming `_`) collided three DISTINCT healthy names\n * onto one identifier — `a_b`, and `b` under `a`, both rendered\n * `subsystem_a_b` — so `validate --all` was green while the renderer refused\n * the fleet map; `subsystem.name-collision` never fires because the flat\n * namespace really is intact. Hence the encoding: an identifier byte\n * (`[A-Za-z0-9]`) passes through, any other byte becomes `_` + two lowercase\n * hex digits (`.` → `_2e`, `-` → `_2d`, `_` itself → `_5f` — the underscore\n * must be escaped precisely because it is BOTH legal in a name and the\n * separator), and segments join on `__`. Underscore runs then decode\n * uniquely — one is an escape, two a separator, three a separator followed\n * by an escape — so no two distinct legal paths share an identifier. A name\n * `subsystem.name-invalid` already flagged may hold a code point above 0xff\n * whose longer hex could in principle re-collide; it is deterministic still,\n * and that directory is already an error finding by name.\n *\n * EXPORTED, with its prefix, because `subsystem.view-id-collision` has to ask\n * the generator what it mints rather than re-deriving it. A check carrying its\n * own copy of the escaping above would go quiet the day the encoding changed,\n * and going quiet is the failure this whole family exists to prevent.\n */\nexport const SUBSYSTEM_VIEW_PREFIX = \"subsystem_\";\n\nexport function subsystemViewId(sub: SubsystemEntry): string {\n const segment = (name: string): string =>\n [...name].map((ch) => (/[A-Za-z0-9]/.test(ch) ? ch : `_${ch.codePointAt(0)!.toString(16).padStart(2, \"0\")}`)).join(\"\");\n return `subsystem_${sub.path.map(segment).join(\"__\")}`;\n}\n\n/**\n * Does the file on disk already SAY what `renderSubsystemViews` renders?\n *\n * The generator above only ever emits LF. `core.autocrlf=true` is Git for\n * Windows' installer default and a docs repo ships no `.gitattributes`, so an\n * ordinary Windows clone hands the file back with CRLF and not one fact\n * changed. Four call sites asked this question with a byte equality and all\n * four answered \"stale\" on such a clone: `loam validate` reported an error,\n * `loam subsystem sync` reported `updated` and rewrote with LF, git then showed\n * the file permanently modified, and the next checkout restored the CRLF. That\n * is a loop rather than a diagnosis, which is why the comparison lives here\n * now — the module that mints the bytes is the one that owes an answer about\n * them, and three near-copies of this rule would go stale one at a time.\n *\n * Still never a parse: a views-only document does not parse standalone, and no\n * check anywhere reads this file's content. The narrowing is from BYTES to\n * CONTENT, which is what `subsystem.views-stale`'s own message (\"does not match\n * the tree\") already claims. The symmetric cost is accepted and is the one\n * `core/verify/pins/pin.ts` documents for `pinnedDigest`: a pure line-ending\n * change is invisible, correctly, because it is not a change to what the file\n * says.\n *\n * `null` is carried through as itself rather than collapsing into `\"\"`: absent,\n * must-be-absent and present-but-different are three distinct states with three\n * distinct messages, and a fleet whose views file does not exist is not a fleet\n * whose views file is blank.\n */\nexport function viewsAgree(actual: string | null, expected: string | null): boolean {\n const lf = (text: string | null): string | null => (text === null ? null : text.replace(/\\r\\n|\\r/g, \"\\n\"));\n return lf(actual) === lf(expected);\n}\n"]}