@ybotok/loam 0.0.0 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/COMPARISON.md +63 -0
  3. package/LICENSE +21 -0
  4. package/MIGRATING-from-OpenSpec.md +202 -0
  5. package/README.md +246 -9
  6. package/SCHEMA.md +425 -0
  7. package/SECURITY.md +9 -0
  8. package/dist/cli.js +142 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/commands/adopt.js +240 -0
  11. package/dist/commands/adopt.js.map +1 -0
  12. package/dist/commands/archive.js +1313 -0
  13. package/dist/commands/archive.js.map +1 -0
  14. package/dist/commands/delta.js +388 -0
  15. package/dist/commands/delta.js.map +1 -0
  16. package/dist/commands/dependencies.js +83 -0
  17. package/dist/commands/dependencies.js.map +1 -0
  18. package/dist/commands/docs-repo-gate.js +104 -0
  19. package/dist/commands/docs-repo-gate.js.map +1 -0
  20. package/dist/commands/doctor.js +85 -0
  21. package/dist/commands/doctor.js.map +1 -0
  22. package/dist/commands/explore.js +195 -0
  23. package/dist/commands/explore.js.map +1 -0
  24. package/dist/commands/format.js +17 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/gherkin.js +309 -0
  27. package/dist/commands/gherkin.js.map +1 -0
  28. package/dist/commands/init.js +250 -0
  29. package/dist/commands/init.js.map +1 -0
  30. package/dist/commands/instructions.js +70 -0
  31. package/dist/commands/instructions.js.map +1 -0
  32. package/dist/commands/list.js +291 -0
  33. package/dist/commands/list.js.map +1 -0
  34. package/dist/commands/migrate-openspec.js +950 -0
  35. package/dist/commands/migrate-openspec.js.map +1 -0
  36. package/dist/commands/new.js +413 -0
  37. package/dist/commands/new.js.map +1 -0
  38. package/dist/commands/rebase.js +356 -0
  39. package/dist/commands/rebase.js.map +1 -0
  40. package/dist/commands/show.js +320 -0
  41. package/dist/commands/show.js.map +1 -0
  42. package/dist/commands/status.js +254 -0
  43. package/dist/commands/status.js.map +1 -0
  44. package/dist/commands/unarchive.js +317 -0
  45. package/dist/commands/unarchive.js.map +1 -0
  46. package/dist/commands/validate.js +1591 -0
  47. package/dist/commands/validate.js.map +1 -0
  48. package/dist/commands/verify.js +775 -0
  49. package/dist/commands/verify.js.map +1 -0
  50. package/dist/commands/vouch.js +343 -0
  51. package/dist/commands/vouch.js.map +1 -0
  52. package/dist/core/agent.js +2065 -0
  53. package/dist/core/agent.js.map +1 -0
  54. package/dist/core/agents-stamp.js +68 -0
  55. package/dist/core/agents-stamp.js.map +1 -0
  56. package/dist/core/arch.js +130 -0
  57. package/dist/core/arch.js.map +1 -0
  58. package/dist/core/brief.js +733 -0
  59. package/dist/core/brief.js.map +1 -0
  60. package/dist/core/coherence.js +577 -0
  61. package/dist/core/coherence.js.map +1 -0
  62. package/dist/core/concurrency.js +51 -0
  63. package/dist/core/concurrency.js.map +1 -0
  64. package/dist/core/config.js +180 -0
  65. package/dist/core/config.js.map +1 -0
  66. package/dist/core/delta.js +476 -0
  67. package/dist/core/delta.js.map +1 -0
  68. package/dist/core/dependencies.js +356 -0
  69. package/dist/core/dependencies.js.map +1 -0
  70. package/dist/core/docs.js +144 -0
  71. package/dist/core/docs.js.map +1 -0
  72. package/dist/core/doctor.js +559 -0
  73. package/dist/core/doctor.js.map +1 -0
  74. package/dist/core/document-bytes.js +59 -0
  75. package/dist/core/document-bytes.js.map +1 -0
  76. package/dist/core/explore.js +225 -0
  77. package/dist/core/explore.js.map +1 -0
  78. package/dist/core/fleet-context.js +225 -0
  79. package/dist/core/fleet-context.js.map +1 -0
  80. package/dist/core/frontmatter.js +199 -0
  81. package/dist/core/frontmatter.js.map +1 -0
  82. package/dist/core/gherkin.js +463 -0
  83. package/dist/core/gherkin.js.map +1 -0
  84. package/dist/core/health.js +67 -0
  85. package/dist/core/health.js.map +1 -0
  86. package/dist/core/ids.js +122 -0
  87. package/dist/core/ids.js.map +1 -0
  88. package/dist/core/issue.js +9 -0
  89. package/dist/core/issue.js.map +1 -0
  90. package/dist/core/json.js +74 -0
  91. package/dist/core/json.js.map +1 -0
  92. package/dist/core/likec4.js +479 -0
  93. package/dist/core/likec4.js.map +1 -0
  94. package/dist/core/maturity.js +66 -0
  95. package/dist/core/maturity.js.map +1 -0
  96. package/dist/core/openapi-merge.js +552 -0
  97. package/dist/core/openapi-merge.js.map +1 -0
  98. package/dist/core/openapi.js +255 -0
  99. package/dist/core/openapi.js.map +1 -0
  100. package/dist/core/openspec-inventory.js +1440 -0
  101. package/dist/core/openspec-inventory.js.map +1 -0
  102. package/dist/core/path-safety.js +168 -0
  103. package/dist/core/path-safety.js.map +1 -0
  104. package/dist/core/provenance.js +720 -0
  105. package/dist/core/provenance.js.map +1 -0
  106. package/dist/core/records.js +33 -0
  107. package/dist/core/records.js.map +1 -0
  108. package/dist/core/repo.js +436 -0
  109. package/dist/core/repo.js.map +1 -0
  110. package/dist/core/report.js +47 -0
  111. package/dist/core/report.js.map +1 -0
  112. package/dist/core/results.js +199 -0
  113. package/dist/core/results.js.map +1 -0
  114. package/dist/core/spec.js +481 -0
  115. package/dist/core/spec.js.map +1 -0
  116. package/dist/core/staging.js +890 -0
  117. package/dist/core/staging.js.map +1 -0
  118. package/dist/core/status.js +1104 -0
  119. package/dist/core/status.js.map +1 -0
  120. package/dist/core/steps.js +72 -0
  121. package/dist/core/steps.js.map +1 -0
  122. package/dist/core/verify.js +807 -0
  123. package/dist/core/verify.js.map +1 -0
  124. package/dist/core/version.js +24 -0
  125. package/dist/core/version.js.map +1 -0
  126. package/package.json +63 -4
@@ -0,0 +1,320 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { loadConfig } from "../core/config.js";
4
+ import { FleetContext } from "../core/fleet-context.js";
5
+ import { emitJson, fail, repoPath, reportNoConfig } from "../core/json.js";
6
+ import { listField, readFrontmatter, stringField } from "../core/frontmatter.js";
7
+ import { loadFile, serviceResolver } from "../core/likec4.js";
8
+ import { readOpenapi } from "../core/openapi.js";
9
+ import { DocsRepoUnavailableError, countMarkdown, featurePaths, featureSpecPaths, landscapePath, listServices, resolveFeature, servicePaths, } from "../core/repo.js";
10
+ import { parseRequirements } from "../core/spec.js";
11
+ import { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from "./docs-repo-gate.js";
12
+ import { plural } from "./format.js";
13
+ export function registerShow(program) {
14
+ program
15
+ .command("show")
16
+ .argument("<target>", "service id or feature id")
17
+ .description("Show everything loam knows about a service or a feature")
18
+ .option("--json", "emit the machine contract instead of the human view")
19
+ .option("--type <kind>", "force the reading: service | feature")
20
+ .action(async (target, opts) => {
21
+ const json = opts.json === true;
22
+ if (opts.type !== undefined && opts.type !== "service" && opts.type !== "feature") {
23
+ fail(json, "invalid-option", `Unknown --type '${opts.type}'. Expected: service | feature.`);
24
+ return;
25
+ }
26
+ const forced = opts.type;
27
+ const config = await loadConfig();
28
+ if (!config) {
29
+ reportNoConfig(json);
30
+ return;
31
+ }
32
+ const { docsDir } = config;
33
+ // "No service or feature 'x' in <dir>" is a lie when <dir> is not a docs
34
+ // repo at all — the same refusal validate and list owe (docsRepoReady).
35
+ // `docs`, not `services`: a feature is readable from a repo whose
36
+ // services/ is missing, and if the target turns out to be a service the
37
+ // enumeration below refuses with `services-missing` anyway.
38
+ if (!docsRepoReady(json, docsDir, "docs"))
39
+ return;
40
+ // One read index for the invocation. Deciding the target is a service
41
+ // enumerates the fleet, and so does resolving the landscape's edges to the
42
+ // services that own them — `loam show <service>` walked services/ twice for
43
+ // one answer. The context memoises only within this invocation, so a later
44
+ // run still sees whatever is on disk then.
45
+ const context = new FleetContext();
46
+ try {
47
+ // A feature id is distinctive (FEAT-101); a service name is arbitrary. When
48
+ // both could match, the feature wins and --type forces the other reading.
49
+ const feature = forced === "service" ? null : await resolveFeature(docsDir, target, "include", context);
50
+ if (feature) {
51
+ await showFeature(docsDir, feature, json);
52
+ return;
53
+ }
54
+ const isService = forced !== "feature" && (await listServices(docsDir, context)).some((s) => s.id === target);
55
+ if (isService) {
56
+ await showService(docsDir, target, json, context);
57
+ return;
58
+ }
59
+ const looked = forced ? forced : "service or feature";
60
+ fail(json, "unknown-target", `No ${looked} '${target}' in ${docsDir}.`);
61
+ }
62
+ catch (err) {
63
+ if (err instanceof DocsRepoUnavailableError) {
64
+ reportDocsRepoError(json, err);
65
+ return;
66
+ }
67
+ // An artifact that exists but cannot be read used to escape as a stack
68
+ // trace (`internal` in --json), naming nothing. `show` reads one target,
69
+ // so it has nothing to fall back on — but it can at least say WHICH file.
70
+ reportRepositoryUnavailable(json, err, `'${target}' cannot be shown`, docsDir);
71
+ }
72
+ });
73
+ }
74
+ async function showService(docsDir, id, json, context) {
75
+ const paths = servicePaths(docsDir, id);
76
+ const has = {
77
+ model: existsSync(paths.model),
78
+ spec: existsSync(paths.spec),
79
+ // The architecture-obligations axis. It was invisible here and in `list`,
80
+ // which made the whole axis unnavigable: a service could carry twenty arch
81
+ // requirements — the outbox, the retries, the alerts — and every command a
82
+ // reader uses to find out what a service documents showed none of them.
83
+ archSpec: existsSync(paths.archSpec),
84
+ openapi: existsSync(paths.openapi),
85
+ runbook: existsSync(paths.runbook),
86
+ health: existsSync(paths.health),
87
+ };
88
+ const model = has.model
89
+ ? await loadFile(paths.model)
90
+ : { errors: [], elements: [], relationships: [] };
91
+ const reqs = has.spec ? parseRequirements(await readFile(paths.spec, "utf8")) : [];
92
+ const archReqs = has.archSpec ? parseRequirements(await readFile(paths.archSpec, "utf8")) : [];
93
+ const adrs = await countMarkdown(paths.adrsDir);
94
+ // `readOpenapi`, not `operationIds`: a contract that exists but does not parse
95
+ // came back from operationIds as an EMPTY operation list, indistinguishable
96
+ // from a service with no endpoints. `show` is what a reader opens to find out
97
+ // what a service offers, and answering "nothing" over a YAML error is the
98
+ // worst possible lie for that question. `x-loam-remove` entries are filtered
99
+ // out for the same reason validate filters them: they are deletion markers
100
+ // from a delta, not operations anybody can call.
101
+ const api = await readOpenapi(paths.openapi);
102
+ const ops = api.ops.filter((o) => !o.remove).map((o) => o.id);
103
+ const fm = await readFrontmatter(paths.spec);
104
+ const provenance = {
105
+ status: stringField(fm, "status") ?? null,
106
+ owner: stringField(fm, "owner") ?? null,
107
+ last_verified: stringField(fm, "last_verified") ?? null,
108
+ sources: listField(fm, "sources"),
109
+ };
110
+ const governs = (op) => reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);
111
+ const { inbound, outbound } = await landscapeEdges(docsDir, id, context);
112
+ const requirementJson = (r) => ({
113
+ id: r.id,
114
+ name: r.name,
115
+ kind: r.kind,
116
+ scenarios: r.scenarios.length,
117
+ operations: r.operations,
118
+ covers: r.covers,
119
+ });
120
+ if (json) {
121
+ emitJson({
122
+ type: "service",
123
+ id,
124
+ path: repoPath(docsDir, paths.dir),
125
+ has,
126
+ adrs,
127
+ frontmatter: provenance,
128
+ model: {
129
+ elements: model.elements.length,
130
+ relationships: model.relationships.length,
131
+ errors: model.errors.map(errorText),
132
+ },
133
+ requirements: reqs.map(requirementJson),
134
+ archSpec: {
135
+ requirements: archReqs.length,
136
+ scenarios: scenarioCount(archReqs),
137
+ entries: archReqs.map(requirementJson),
138
+ },
139
+ openapi: {
140
+ unreadable: api.unreadable,
141
+ ...(api.error === undefined ? {} : { error: api.error }),
142
+ },
143
+ operations: ops.map((op) => ({ id: op, governedBy: governs(op) })),
144
+ landscape: { inbound, outbound },
145
+ });
146
+ return;
147
+ }
148
+ const badge = [provenance.status, provenance.owner].filter((s) => s !== null).join(" · ");
149
+ console.log(`${id} ${repoPath(docsDir, paths.dir)}${badge ? ` ${badge}` : ""}\n`);
150
+ if (provenance.sources.length > 0 || provenance.last_verified !== null) {
151
+ console.log(" provenance");
152
+ if (provenance.last_verified !== null)
153
+ console.log(` verified ${provenance.last_verified}`);
154
+ for (const s of provenance.sources)
155
+ console.log(` sources ${s}`);
156
+ console.log("");
157
+ }
158
+ console.log(" artifacts");
159
+ const modelNote = model.errors.length > 0
160
+ ? `${model.errors.length} error(s)`
161
+ : `${plural(model.elements.length, "element")} · ${plural(model.relationships.length, "relationship")}`;
162
+ console.log(` ${mark(has.model)} model.likec4 ${has.model ? modelNote : ""}`.trimEnd());
163
+ const specNote = (rs) => `${plural(rs.length, "requirement")} · ${plural(scenarioCount(rs), "scenario")}`;
164
+ console.log(` ${mark(has.spec)} spec.md ${has.spec ? specNote(reqs) : ""}`.trimEnd());
165
+ console.log(` ${mark(has.archSpec)} arch.spec.md ${has.archSpec ? specNote(archReqs) : ""}`.trimEnd());
166
+ const apiNote = api.unreadable ? "does not parse" : plural(ops.length, "operation");
167
+ console.log(` ${mark(has.openapi)} openapi.yaml ${has.openapi ? apiNote : ""}`.trimEnd());
168
+ console.log(` ${mark(has.runbook)} runbook.md`);
169
+ console.log(` ${mark(has.health)} health.yaml`);
170
+ console.log(` ${mark(adrs > 0)} adrs/ ${adrs > 0 ? plural(adrs, "decision") : ""}`.trimEnd());
171
+ const requirementLine = (r) => {
172
+ const govern = r.operations.length > 0 ? ` → ${r.operations.join(", ")}` : "";
173
+ const covers = r.covers.length > 0 ? ` covers ${r.covers.join(", ")}` : "";
174
+ return ` ${r.name} (${plural(r.scenarios.length, "scenario")})${govern}${covers}`;
175
+ };
176
+ if (reqs.length > 0) {
177
+ console.log("\n requirements");
178
+ for (const r of reqs)
179
+ console.log(requirementLine(r));
180
+ }
181
+ if (archReqs.length > 0) {
182
+ console.log("\n arch requirements");
183
+ for (const r of archReqs)
184
+ console.log(requirementLine(r));
185
+ }
186
+ if (api.unreadable) {
187
+ console.log("\n operations");
188
+ console.log(` ✗ openapi.yaml does not parse — nothing here lists what ${id} offers`);
189
+ if (api.error !== undefined)
190
+ console.log(` ${api.error}`);
191
+ }
192
+ else if (ops.length > 0) {
193
+ console.log("\n operations");
194
+ for (const op of ops) {
195
+ const by = governs(op);
196
+ if (by.length > 0)
197
+ console.log(` ✓ ${op} governed by ${by.map((n) => `"${n}"`).join(", ")}`);
198
+ else
199
+ console.log(` ⚠ ${op} not governed by any requirement`);
200
+ }
201
+ }
202
+ if (inbound.length > 0 || outbound.length > 0) {
203
+ console.log("\n landscape");
204
+ for (const e of inbound)
205
+ console.log(` ← ${e.service}${edgeNote(e)}`);
206
+ for (const e of outbound)
207
+ console.log(` → ${e.service}${edgeNote(e)}`);
208
+ }
209
+ if (model.errors.length > 0) {
210
+ console.log("\n model errors");
211
+ for (const e of model.errors)
212
+ console.log(` ✗ ${errorText(e)}`);
213
+ }
214
+ }
215
+ /** Inbound/outbound edges for a service, read off the living landscape. */
216
+ async function landscapeEdges(docsDir, service, context) {
217
+ const path = landscapePath(docsDir);
218
+ if (!existsSync(path))
219
+ return { inbound: [], outbound: [] };
220
+ const land = await loadFile(path);
221
+ if (land.errors.length > 0)
222
+ return { inbound: [], outbound: [] };
223
+ // Edges are filed under the service an element is BOUND to, not under what the
224
+ // box is titled — otherwise renaming a box empties this list without a word —
225
+ // and an edge into a modelled container belongs to the service that owns it.
226
+ const known = new Set((await listServices(docsDir, context)).map((s) => s.id));
227
+ const svcOf = serviceResolver(land.elements, known);
228
+ const inbound = [];
229
+ const outbound = [];
230
+ for (const r of land.relationships) {
231
+ const edge = (service) => ({
232
+ service,
233
+ op: r.op ?? null,
234
+ title: r.title ?? null,
235
+ });
236
+ if (svcOf(r.target) === service)
237
+ inbound.push(edge(svcOf(r.source)));
238
+ else if (svcOf(r.source) === service)
239
+ outbound.push(edge(svcOf(r.target)));
240
+ }
241
+ return { inbound, outbound };
242
+ }
243
+ /* ------------------------------------------------------------------ */
244
+ /* Feature */
245
+ /* ------------------------------------------------------------------ */
246
+ async function showFeature(docsDir, feature, json) {
247
+ const paths = featurePaths(feature.dir);
248
+ const delta = feature.has.delta
249
+ ? await loadFile(paths.delta)
250
+ : { errors: [], elements: [], relationships: [] };
251
+ const taggedEls = delta.elements.filter((e) => e.tags.includes(feature.id));
252
+ const taggedRels = delta.relationships.filter((r) => r.tags.includes(feature.id));
253
+ const services = [];
254
+ for (const svc of feature.services) {
255
+ const specPath = featureSpecPaths(feature.dir, svc).spec;
256
+ const reqs = existsSync(specPath) ? parseRequirements(await readFile(specPath, "utf8")) : [];
257
+ services.push({
258
+ id: svc,
259
+ added: reqs.filter((r) => r.kind === "ADDED").length,
260
+ modified: reqs.filter((r) => r.kind === "MODIFIED").length,
261
+ removed: reqs.filter((r) => r.kind === "REMOVED").length,
262
+ operations: [...new Set(reqs.flatMap((r) => r.operations))],
263
+ });
264
+ }
265
+ if (json) {
266
+ emitJson({
267
+ type: "feature",
268
+ id: feature.id,
269
+ dirName: feature.dirName,
270
+ path: repoPath(docsDir, feature.dir),
271
+ archived: feature.archived,
272
+ has: feature.has,
273
+ delta: {
274
+ elements: taggedEls.length,
275
+ relationships: taggedRels.length,
276
+ errors: delta.errors.map(errorText),
277
+ },
278
+ services,
279
+ });
280
+ return;
281
+ }
282
+ const state = feature.archived ? "archived" : "active";
283
+ console.log(`${feature.id} ${repoPath(docsDir, feature.dir)} ${state}\n`);
284
+ console.log(" artifacts");
285
+ console.log(` ${mark(feature.has.intent)} intent.md`);
286
+ const deltaNote = delta.errors.length > 0
287
+ ? `${delta.errors.length} error(s)`
288
+ : `${plural(taggedEls.length, "element")} · ${plural(taggedRels.length, "relationship")} tagged ${feature.id}`;
289
+ console.log(` ${mark(feature.has.delta)} delta.likec4 ${feature.has.delta ? deltaNote : ""}`.trimEnd());
290
+ if (services.length > 0) {
291
+ console.log("\n services");
292
+ const width = Math.max(...services.map((s) => s.id.length));
293
+ for (const s of services) {
294
+ const ops = s.operations.length > 0 ? ` · ${s.operations.join(", ")}` : "";
295
+ console.log(` ${s.id.padEnd(width)} +${s.added} ~${s.modified} -${s.removed} requirements${ops}`);
296
+ }
297
+ }
298
+ if (delta.errors.length > 0) {
299
+ console.log("\n delta errors");
300
+ for (const e of delta.errors)
301
+ console.log(` ✗ ${errorText(e)}`);
302
+ }
303
+ }
304
+ /* ------------------------------------------------------------------ */
305
+ /** "-" for absent, matching `list`: ✗ is the error glyph here, and a missing runbook is not an error. */
306
+ function mark(present) {
307
+ return present ? "✓" : "-";
308
+ }
309
+ function scenarioCount(reqs) {
310
+ return reqs.reduce((n, r) => n + r.scenarios.length, 0);
311
+ }
312
+ function edgeNote(e) {
313
+ if (e.op)
314
+ return ` ${e.op}`;
315
+ return e.title ? ` "${e.title}"` : "";
316
+ }
317
+ function errorText(e) {
318
+ return typeof e.line === "number" ? `L${e.line}: ${e.message}` : e.message;
319
+ }
320
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/commands/show.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Command } from \"commander\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { loadConfig } from \"../core/config.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { emitJson, fail, repoPath, reportNoConfig } from \"../core/json.js\";\nimport { listField, readFrontmatter, stringField } from \"../core/frontmatter.js\";\nimport { loadFile, serviceResolver, type Elem } from \"../core/likec4.js\";\nimport { readOpenapi } from \"../core/openapi.js\";\nimport {\n DocsRepoUnavailableError,\n countMarkdown,\n featurePaths,\n featureSpecPaths,\n landscapePath,\n listServices,\n resolveFeature,\n servicePaths,\n type FeatureEntry,\n} from \"../core/repo.js\";\nimport { parseRequirements, type Requirement } from \"../core/spec.js\";\nimport { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from \"./docs-repo-gate.js\";\nimport { plural } from \"./format.js\";\n\ntype TargetType = \"service\" | \"feature\";\n\ninterface ShowOptions {\n json?: boolean;\n type?: string;\n}\n\nexport function registerShow(program: Command): void {\n program\n .command(\"show\")\n .argument(\"<target>\", \"service id or feature id\")\n .description(\"Show everything loam knows about a service or a feature\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .option(\"--type <kind>\", \"force the reading: service | feature\")\n .action(async (target: string, opts: ShowOptions) => {\n const json = opts.json === true;\n if (opts.type !== undefined && opts.type !== \"service\" && opts.type !== \"feature\") {\n fail(json, \"invalid-option\", `Unknown --type '${opts.type}'. Expected: service | feature.`);\n return;\n }\n const forced = opts.type as TargetType | undefined;\n\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n const { docsDir } = config;\n // \"No service or feature 'x' in <dir>\" is a lie when <dir> is not a docs\n // repo at all — the same refusal validate and list owe (docsRepoReady).\n // `docs`, not `services`: a feature is readable from a repo whose\n // services/ is missing, and if the target turns out to be a service the\n // enumeration below refuses with `services-missing` anyway.\n if (!docsRepoReady(json, docsDir, \"docs\")) return;\n // One read index for the invocation. Deciding the target is a service\n // enumerates the fleet, and so does resolving the landscape's edges to the\n // services that own them — `loam show <service>` walked services/ twice for\n // one answer. The context memoises only within this invocation, so a later\n // run still sees whatever is on disk then.\n const context = new FleetContext();\n\n try {\n // A feature id is distinctive (FEAT-101); a service name is arbitrary. When\n // both could match, the feature wins and --type forces the other reading.\n const feature =\n forced === \"service\" ? null : await resolveFeature(docsDir, target, \"include\", context);\n if (feature) {\n await showFeature(docsDir, feature, json);\n return;\n }\n const isService =\n forced !== \"feature\" && (await listServices(docsDir, context)).some((s) => s.id === target);\n if (isService) {\n await showService(docsDir, target, json, context);\n return;\n }\n\n const looked = forced ? forced : \"service or feature\";\n fail(json, \"unknown-target\", `No ${looked} '${target}' in ${docsDir}.`);\n } catch (err) {\n if (err instanceof DocsRepoUnavailableError) {\n reportDocsRepoError(json, err);\n return;\n }\n // An artifact that exists but cannot be read used to escape as a stack\n // trace (`internal` in --json), naming nothing. `show` reads one target,\n // so it has nothing to fall back on — but it can at least say WHICH file.\n reportRepositoryUnavailable(json, err, `'${target}' cannot be shown`, docsDir);\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Service */\n/* ------------------------------------------------------------------ */\n\ninterface Edge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\nasync function showService(\n docsDir: string,\n id: string,\n json: boolean,\n context: FleetContext,\n): Promise<void> {\n const paths = servicePaths(docsDir, id);\n const has = {\n model: existsSync(paths.model),\n spec: existsSync(paths.spec),\n // The architecture-obligations axis. It was invisible here and in `list`,\n // which made the whole axis unnavigable: a service could carry twenty arch\n // requirements — the outbox, the retries, the alerts — and every command a\n // reader uses to find out what a service documents showed none of them.\n archSpec: existsSync(paths.archSpec),\n openapi: existsSync(paths.openapi),\n runbook: existsSync(paths.runbook),\n health: existsSync(paths.health),\n };\n\n const model = has.model\n ? await loadFile(paths.model)\n : { errors: [], elements: [], relationships: [] };\n const reqs = has.spec ? parseRequirements(await readFile(paths.spec, \"utf8\")) : [];\n const archReqs = has.archSpec ? parseRequirements(await readFile(paths.archSpec, \"utf8\")) : [];\n const adrs = await countMarkdown(paths.adrsDir);\n // `readOpenapi`, not `operationIds`: a contract that exists but does not parse\n // came back from operationIds as an EMPTY operation list, indistinguishable\n // from a service with no endpoints. `show` is what a reader opens to find out\n // what a service offers, and answering \"nothing\" over a YAML error is the\n // worst possible lie for that question. `x-loam-remove` entries are filtered\n // out for the same reason validate filters them: they are deletion markers\n // from a delta, not operations anybody can call.\n const api = await readOpenapi(paths.openapi);\n const ops = api.ops.filter((o) => !o.remove).map((o) => o.id);\n\n const fm = await readFrontmatter(paths.spec);\n const provenance = {\n status: stringField(fm, \"status\") ?? null,\n owner: stringField(fm, \"owner\") ?? null,\n last_verified: stringField(fm, \"last_verified\") ?? null,\n sources: listField(fm, \"sources\"),\n };\n\n const governs = (op: string): string[] =>\n reqs.filter((r) => r.operations.includes(op)).map((r) => r.name);\n\n const { inbound, outbound } = await landscapeEdges(docsDir, id, context);\n\n const requirementJson = (r: Requirement): Record<string, unknown> => ({\n id: r.id,\n name: r.name,\n kind: r.kind,\n scenarios: r.scenarios.length,\n operations: r.operations,\n covers: r.covers,\n });\n\n if (json) {\n emitJson({\n type: \"service\",\n id,\n path: repoPath(docsDir, paths.dir),\n has,\n adrs,\n frontmatter: provenance,\n model: {\n elements: model.elements.length,\n relationships: model.relationships.length,\n errors: model.errors.map(errorText),\n },\n requirements: reqs.map(requirementJson),\n archSpec: {\n requirements: archReqs.length,\n scenarios: scenarioCount(archReqs),\n entries: archReqs.map(requirementJson),\n },\n openapi: {\n unreadable: api.unreadable,\n ...(api.error === undefined ? {} : { error: api.error }),\n },\n operations: ops.map((op) => ({ id: op, governedBy: governs(op) })),\n landscape: { inbound, outbound },\n });\n return;\n }\n\n const badge = [provenance.status, provenance.owner].filter((s) => s !== null).join(\" · \");\n console.log(`${id} ${repoPath(docsDir, paths.dir)}${badge ? ` ${badge}` : \"\"}\\n`);\n\n if (provenance.sources.length > 0 || provenance.last_verified !== null) {\n console.log(\" provenance\");\n if (provenance.last_verified !== null) console.log(` verified ${provenance.last_verified}`);\n for (const s of provenance.sources) console.log(` sources ${s}`);\n console.log(\"\");\n }\n\n console.log(\" artifacts\");\n const modelNote =\n model.errors.length > 0\n ? `${model.errors.length} error(s)`\n : `${plural(model.elements.length, \"element\")} · ${plural(model.relationships.length, \"relationship\")}`;\n console.log(` ${mark(has.model)} model.likec4 ${has.model ? modelNote : \"\"}`.trimEnd());\n const specNote = (rs: Requirement[]): string =>\n `${plural(rs.length, \"requirement\")} · ${plural(scenarioCount(rs), \"scenario\")}`;\n console.log(` ${mark(has.spec)} spec.md ${has.spec ? specNote(reqs) : \"\"}`.trimEnd());\n console.log(\n ` ${mark(has.archSpec)} arch.spec.md ${has.archSpec ? specNote(archReqs) : \"\"}`.trimEnd(),\n );\n const apiNote = api.unreadable ? \"does not parse\" : plural(ops.length, \"operation\");\n console.log(` ${mark(has.openapi)} openapi.yaml ${has.openapi ? apiNote : \"\"}`.trimEnd());\n console.log(` ${mark(has.runbook)} runbook.md`);\n console.log(` ${mark(has.health)} health.yaml`);\n console.log(` ${mark(adrs > 0)} adrs/ ${adrs > 0 ? plural(adrs, \"decision\") : \"\"}`.trimEnd());\n\n const requirementLine = (r: Requirement): string => {\n const govern = r.operations.length > 0 ? ` → ${r.operations.join(\", \")}` : \"\";\n const covers = r.covers.length > 0 ? ` covers ${r.covers.join(\", \")}` : \"\";\n return ` ${r.name} (${plural(r.scenarios.length, \"scenario\")})${govern}${covers}`;\n };\n if (reqs.length > 0) {\n console.log(\"\\n requirements\");\n for (const r of reqs) console.log(requirementLine(r));\n }\n if (archReqs.length > 0) {\n console.log(\"\\n arch requirements\");\n for (const r of archReqs) console.log(requirementLine(r));\n }\n\n if (api.unreadable) {\n console.log(\"\\n operations\");\n console.log(` ✗ openapi.yaml does not parse — nothing here lists what ${id} offers`);\n if (api.error !== undefined) console.log(` ${api.error}`);\n } else if (ops.length > 0) {\n console.log(\"\\n operations\");\n for (const op of ops) {\n const by = governs(op);\n if (by.length > 0) console.log(` ✓ ${op} governed by ${by.map((n) => `\"${n}\"`).join(\", \")}`);\n else console.log(` ⚠ ${op} not governed by any requirement`);\n }\n }\n\n if (inbound.length > 0 || outbound.length > 0) {\n console.log(\"\\n landscape\");\n for (const e of inbound) console.log(` ← ${e.service}${edgeNote(e)}`);\n for (const e of outbound) console.log(` → ${e.service}${edgeNote(e)}`);\n }\n\n if (model.errors.length > 0) {\n console.log(\"\\n model errors\");\n for (const e of model.errors) console.log(` ✗ ${errorText(e)}`);\n }\n}\n\n/** Inbound/outbound edges for a service, read off the living landscape. */\nasync function landscapeEdges(\n docsDir: string,\n service: string,\n context: FleetContext,\n): Promise<{ inbound: Edge[]; outbound: Edge[] }> {\n const path = landscapePath(docsDir);\n if (!existsSync(path)) return { inbound: [], outbound: [] };\n const land = await loadFile(path);\n if (land.errors.length > 0) return { inbound: [], outbound: [] };\n // Edges are filed under the service an element is BOUND to, not under what the\n // box is titled — otherwise renaming a box empties this list without a word —\n // and an edge into a modelled container belongs to the service that owns it.\n const known = new Set((await listServices(docsDir, context)).map((s) => s.id));\n const svcOf = serviceResolver(land.elements, known);\n\n const inbound: Edge[] = [];\n const outbound: Edge[] = [];\n for (const r of land.relationships) {\n const edge = (service: string): Edge => ({\n service,\n op: r.op ?? null,\n title: r.title ?? null,\n });\n if (svcOf(r.target) === service) inbound.push(edge(svcOf(r.source)));\n else if (svcOf(r.source) === service) outbound.push(edge(svcOf(r.target)));\n }\n return { inbound, outbound };\n}\n\n/* ------------------------------------------------------------------ */\n/* Feature */\n/* ------------------------------------------------------------------ */\n\nasync function showFeature(docsDir: string, feature: FeatureEntry, json: boolean): Promise<void> {\n const paths = featurePaths(feature.dir);\n const delta = feature.has.delta\n ? await loadFile(paths.delta)\n : { errors: [], elements: [] as Elem[], relationships: [] };\n const taggedEls = delta.elements.filter((e) => e.tags.includes(feature.id));\n const taggedRels = delta.relationships.filter((r) => r.tags.includes(feature.id));\n\n const services = [];\n for (const svc of feature.services) {\n const specPath = featureSpecPaths(feature.dir, svc).spec;\n const reqs = existsSync(specPath) ? parseRequirements(await readFile(specPath, \"utf8\")) : [];\n services.push({\n id: svc,\n added: reqs.filter((r) => r.kind === \"ADDED\").length,\n modified: reqs.filter((r) => r.kind === \"MODIFIED\").length,\n removed: reqs.filter((r) => r.kind === \"REMOVED\").length,\n operations: [...new Set(reqs.flatMap((r) => r.operations))],\n });\n }\n\n if (json) {\n emitJson({\n type: \"feature\",\n id: feature.id,\n dirName: feature.dirName,\n path: repoPath(docsDir, feature.dir),\n archived: feature.archived,\n has: feature.has,\n delta: {\n elements: taggedEls.length,\n relationships: taggedRels.length,\n errors: delta.errors.map(errorText),\n },\n services,\n });\n return;\n }\n\n const state = feature.archived ? \"archived\" : \"active\";\n console.log(`${feature.id} ${repoPath(docsDir, feature.dir)} ${state}\\n`);\n\n console.log(\" artifacts\");\n console.log(` ${mark(feature.has.intent)} intent.md`);\n const deltaNote =\n delta.errors.length > 0\n ? `${delta.errors.length} error(s)`\n : `${plural(taggedEls.length, \"element\")} · ${plural(taggedRels.length, \"relationship\")} tagged ${feature.id}`;\n console.log(` ${mark(feature.has.delta)} delta.likec4 ${feature.has.delta ? deltaNote : \"\"}`.trimEnd());\n\n if (services.length > 0) {\n console.log(\"\\n services\");\n const width = Math.max(...services.map((s) => s.id.length));\n for (const s of services) {\n const ops = s.operations.length > 0 ? ` · ${s.operations.join(\", \")}` : \"\";\n console.log(\n ` ${s.id.padEnd(width)} +${s.added} ~${s.modified} -${s.removed} requirements${ops}`,\n );\n }\n }\n\n if (delta.errors.length > 0) {\n console.log(\"\\n delta errors\");\n for (const e of delta.errors) console.log(` ✗ ${errorText(e)}`);\n }\n}\n\n/* ------------------------------------------------------------------ */\n\n/** \"-\" for absent, matching `list`: ✗ is the error glyph here, and a missing runbook is not an error. */\nfunction mark(present: boolean): string {\n return present ? \"✓\" : \"-\";\n}\n\nfunction scenarioCount(reqs: Requirement[]): number {\n return reqs.reduce((n, r) => n + r.scenarios.length, 0);\n}\n\nfunction edgeNote(e: Edge): string {\n if (e.op) return ` ${e.op}`;\n return e.title ? ` \"${e.title}\"` : \"\";\n}\n\nfunction errorText(e: { message: string; line?: number }): string {\n return typeof e.line === \"number\" ? `L${e.line}: ${e.message}` : e.message;\n}\n"]}
@@ -0,0 +1,254 @@
1
+ import { loadConfig } from "../core/config.js";
2
+ import { FleetContext } from "../core/fleet-context.js";
3
+ import { emitJson, fail, reportNoConfig } from "../core/json.js";
4
+ import { findingJson, SEVERITY_MARK } from "../core/report.js";
5
+ import { DocsRepoUnavailableError, featureCandidates, missingFeatureMessage, resolveFeature, } from "../core/repo.js";
6
+ import { featureStatus, fleetStatus, } from "../core/status.js";
7
+ import { docsRepoReady, reportDocsRepoError, reportRepositoryUnavailable } from "./docs-repo-gate.js";
8
+ export function registerStatus(program) {
9
+ program
10
+ .command("status")
11
+ .argument("[feature]", "feature id or directory name; omit for the whole repository")
12
+ .description("Report where the work stands and what to do next, derived from the files")
13
+ .option("--json", "emit the machine contract instead of the human view")
14
+ .option("--service <id>", "narrow the per-service view to one service")
15
+ .action(async (featureArg, opts) => {
16
+ const json = opts.json === true;
17
+ const config = await loadConfig();
18
+ if (!config) {
19
+ reportNoConfig(json);
20
+ return;
21
+ }
22
+ const { docsDir } = config;
23
+ // A docsDir that is not a docs repo is refused, never rendered as a repo
24
+ // with nothing in it — the doctrine docs-repo-gate.ts's docsRepoReady exists
25
+ // for. `status` counts services, so it needs services/ in both modes.
26
+ if (!docsRepoReady(json, docsDir, "services"))
27
+ return;
28
+ const context = new FleetContext();
29
+ // What the refusal below says it was answering when it gave up.
30
+ let scope = "this repository";
31
+ try {
32
+ if (featureArg === undefined) {
33
+ const report = await fleetStatus(docsDir, { service: opts.service, context });
34
+ // A `--service` that names nothing is refused rather than answered
35
+ // with an empty fleet: "no services and no features" and "you
36
+ // misspelled it" are opposite facts, and only one of them is worth
37
+ // acting on.
38
+ if (opts.service !== undefined && report.services.total === 0) {
39
+ fail(json, "unknown-service", `No service '${opts.service}' under ${docsDir}/services/.`);
40
+ return;
41
+ }
42
+ if (json)
43
+ emitJson({ command: "status", docsDir, scope: "fleet", ...fleetJson(report) });
44
+ else
45
+ printFleet(report);
46
+ return;
47
+ }
48
+ // Archived features are IN scope: an agent asking where FEAT-101 stands
49
+ // after it shipped deserves "it shipped", not "no such feature" — the
50
+ // same reading `show` takes. An argument naming two directories is
51
+ // still answered (status writes nothing), with the tie broken the way
52
+ // resolveFeature always breaks it.
53
+ const feature = await resolveFeature(docsDir, featureArg, "include", context);
54
+ if (!feature) {
55
+ fail(json, "unknown-target", await missingFeatureMessage(docsDir, featureArg, context));
56
+ return;
57
+ }
58
+ scope = feature.id;
59
+ // `config.service` is not a lens like `--service`: it says which
60
+ // repository this IS, and three of the steps (`loam gherkin`, `verify
61
+ // --record --service`) refuse anywhere else. Without it status handed a
62
+ // docs-repo reader commands only a service repo can run, and never told
63
+ // a service repo that the work in front of it was its own.
64
+ const report = await featureStatus(docsDir, feature, {
65
+ service: opts.service,
66
+ boundService: config.service,
67
+ context,
68
+ });
69
+ if (opts.service !== undefined && !report.feature.services.includes(opts.service)) {
70
+ fail(json, "unknown-service", `${feature.id} carries no delta for '${opts.service}'. It touches: ${report.feature.services.length > 0 ? report.feature.services.join(", ") : "(no services)"}.`);
71
+ return;
72
+ }
73
+ const ambiguous = await featureCandidates(docsDir, featureArg, "include", context);
74
+ if (json) {
75
+ emitJson({ command: "status", docsDir, scope: "feature", ...featureJson(report) });
76
+ }
77
+ else {
78
+ printFeature(report, ambiguous.length > 1 ? ambiguous.map((c) => c.dirName) : []);
79
+ }
80
+ }
81
+ catch (err) {
82
+ if (err instanceof DocsRepoUnavailableError) {
83
+ reportDocsRepoError(json, err);
84
+ return;
85
+ }
86
+ // One unreadable artifact must not escape as a stack trace and an
87
+ // `internal` envelope that names nothing: the projection is
88
+ // all-or-nothing (a status missing one artifact reads as a feature that
89
+ // does not owe it), so the honest answer is a refusal naming the path.
90
+ // The errno-vs-path reading this branch depends on — the reason one
91
+ // directory where a file belongs used to kill BOTH forms of status —
92
+ // now lives once, with its reasoning, on the shared helper.
93
+ reportRepositoryUnavailable(json, err, `the status of ${scope} would be missing an artifact`, docsDir);
94
+ }
95
+ // No exit code is set on any path above that succeeded, deliberately.
96
+ // `doctor` exits 1 because a broken installation makes every later
97
+ // command lie, and `validate` exits 1 because it IS the fleet gate. This
98
+ // command is a question, and its answer is equally true whether the
99
+ // feature is finished or not — an agent runs it precisely BECAUSE work is
100
+ // outstanding, and exiting 1 there would make every `set -e` script and
101
+ // every CI step read "there is work to do" as a failure.
102
+ });
103
+ }
104
+ /* ------------------------------------------------------------------ */
105
+ /* JSON */
106
+ /* ------------------------------------------------------------------ */
107
+ /**
108
+ * `issues` go out through `findingJson`, the same serializer `validate` uses:
109
+ * an `Issue` is a `Finding` without details, and one breach reported by two
110
+ * commands must arrive in one shape or a consumer needs two parsers.
111
+ */
112
+ function featureJson(r) {
113
+ return {
114
+ interrupted: r.interrupted,
115
+ feature: r.feature,
116
+ service: r.service,
117
+ artifacts: r.artifacts,
118
+ checks: { ...r.checks, issues: r.checks.issues.map(findingJson) },
119
+ verification: r.verification,
120
+ next: r.next,
121
+ };
122
+ }
123
+ function fleetJson(r) {
124
+ return {
125
+ interrupted: r.interrupted,
126
+ service: r.service,
127
+ services: r.services,
128
+ features: r.features,
129
+ order: r.order,
130
+ next: r.next,
131
+ };
132
+ }
133
+ /* ------------------------------------------------------------------ */
134
+ /* Text */
135
+ /* ------------------------------------------------------------------ */
136
+ function pad(values) {
137
+ return Math.max(0, ...values.map((v) => v.length));
138
+ }
139
+ /**
140
+ * The half-merged-repo banner, printed FIRST and on stderr.
141
+ *
142
+ * stderr, alone among this command's output, because it is not part of the
143
+ * answer: it is the reason the answer may be wrong. A reader piping `status`
144
+ * into a pager still sees it, and the human view stays the shape every other
145
+ * run has. `--json` carries the same fact in `interrupted`.
146
+ */
147
+ function printInterrupted(i) {
148
+ console.error(i.unreadable
149
+ ? "⚠ this docs repo holds a .loam-commit that cannot be read — a commit was interrupted and nothing here can be trusted until a human compares the living docs against version control."
150
+ : `⚠ a \`${i.recover}\` was killed mid-commit (${i.host}, pid ${i.pid}, ${i.at}) — ${i.files.length} file(s) may be half-written: ${i.files.join(", ")}. Everything below is derived from them.`);
151
+ console.error("");
152
+ }
153
+ /** `spec (payment-service)` — the artifact id plus the service, when it has one. */
154
+ function artifactLabel(a) {
155
+ return a.service === null ? a.id : `${a.id} (${a.service})`;
156
+ }
157
+ /**
158
+ * The counts come from the record's `claims:` array (verify's `tallyRecord`),
159
+ * never from its `summary:` block. The verdict is printed beside the state
160
+ * because they answer different questions and only together are they honest:
161
+ * `recorded 4/4 confirmed` used to be the whole line for a feature `verify`
162
+ * calls `attested` — complete, and resting on somebody's word about a test.
163
+ */
164
+ function verificationLine(v) {
165
+ if (v.state === "absent")
166
+ return "none";
167
+ if (v.state === "unreadable")
168
+ return "unreadable";
169
+ return (`${v.state} · ${v.verdict} ${v.confirmed}/${v.claims} confirmed` +
170
+ (v.unanswered > 0 ? ` · ${v.unanswered} unanswered` : "") +
171
+ (v.attested > 0 ? ` · ${v.attested} scenario(s) on an agent's word, not a test run` : "") +
172
+ ` (recorded ${v.recorded ?? "?"})`);
173
+ }
174
+ /**
175
+ * The steps, numbered, with the command on its own indented line — doctor's
176
+ * layout, for doctor's reason: what to do and what to type are two different
177
+ * sentences, and someone scanning a half-built feature reads down the `run:`
178
+ * column.
179
+ */
180
+ function printNext(steps) {
181
+ console.log("\n next");
182
+ for (const [i, step] of steps.entries()) {
183
+ console.log(` ${i + 1}. ${step.code}: ${step.statement}`);
184
+ console.log(` run: ${step.command}`);
185
+ }
186
+ }
187
+ function printFeature(r, ambiguous) {
188
+ if (r.interrupted !== null)
189
+ printInterrupted(r.interrupted);
190
+ const f = r.feature;
191
+ console.log(`loam status ${f.id} — ${f.stage}${f.archived ? " (archived)" : ""}`);
192
+ console.log(` feature ${f.dirName} ${f.path}`);
193
+ console.log(` services ${f.services.length > 0 ? f.services.join(", ") : "(none)"}`);
194
+ if (r.service !== null)
195
+ console.log(` narrowed to ${r.service}`);
196
+ if (f.blockedBy.length > 0)
197
+ console.log(` waiting on ${f.blockedBy.join(", ")}`);
198
+ console.log(` coherence ${r.checks.ran
199
+ ? `${r.checks.errors} error(s) · ${r.checks.warnings} warning(s) · ${r.checks.gating} gate archive`
200
+ : "not run (archived)"}`);
201
+ console.log(` verification ${verificationLine(r.verification)}`);
202
+ if (ambiguous.length > 0) {
203
+ console.log(` note '${f.id}' also names ${ambiguous.filter((d) => d !== f.dirName).join(", ")}`);
204
+ }
205
+ console.log("\n artifacts");
206
+ const labels = r.artifacts.map(artifactLabel);
207
+ const labelWidth = pad(labels);
208
+ const statusWidth = pad(r.artifacts.map((a) => a.status));
209
+ for (const [i, a] of r.artifacts.entries()) {
210
+ // `done` on a file that is not there is the one row a reader misreads: it
211
+ // means the feature owes no such artifact, not that one was written. The
212
+ // JSON contract says it with `exists`; the human view has to say it in
213
+ // words, or the table quietly claims files that do not exist.
214
+ const note = a.blockedBy.length > 0
215
+ ? ` (needs ${a.blockedBy.join(", ")})`
216
+ : !a.exists && a.status === "done"
217
+ ? " (not written — none owed)"
218
+ : "";
219
+ console.log(` ${a.status.padEnd(statusWidth)} ${labels[i].padEnd(labelWidth)} ${a.path}${note}`.trimEnd());
220
+ }
221
+ if (r.checks.issues.length > 0) {
222
+ console.log("\n findings");
223
+ for (const issue of r.checks.issues) {
224
+ console.log(` ${SEVERITY_MARK[issue.severity]} ${issue.code}: ${issue.message}`);
225
+ }
226
+ }
227
+ printNext(r.next);
228
+ }
229
+ function printFleet(r) {
230
+ if (r.interrupted !== null)
231
+ printInterrupted(r.interrupted);
232
+ const s = r.services;
233
+ console.log(`loam status — ${s.total} service(s) · ${r.features.length} feature(s) in flight`);
234
+ console.log(` services ${s.undocumented} undocumented · ${s.draft} draft · ${s.vouched} vouched`);
235
+ if (r.service !== null)
236
+ console.log(` narrowed to ${r.service}`);
237
+ if (r.order.length > 1)
238
+ console.log(` order ${r.order.join(" → ")}`);
239
+ if (r.features.length > 0) {
240
+ console.log("\n features");
241
+ const ids = pad(r.features.map((f) => f.id));
242
+ const stages = pad(r.features.map((f) => f.stage));
243
+ for (const f of r.features) {
244
+ const detail = f.blockedBy.length > 0
245
+ ? `waiting on ${f.blockedBy.join(", ")}`
246
+ : f.missing.length > 0
247
+ ? `missing ${f.missing.join(", ")}`
248
+ : f.services.join(", ");
249
+ console.log(` ${f.stage.padEnd(stages)} ${f.id.padEnd(ids)} ${detail}`.trimEnd());
250
+ }
251
+ }
252
+ printNext(r.next);
253
+ }
254
+ //# sourceMappingURL=status.js.map