@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,388 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { loadConfig } from "../core/config.js";
5
+ import { InvalidIdError, assertServiceId } from "../core/ids.js";
6
+ import { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from "../core/json.js";
7
+ import { elementService, loadFile, serviceOf } from "../core/likec4.js";
8
+ import { readOpenapi } from "../core/openapi.js";
9
+ // The summary walk below descends four levels into a document nobody has
10
+ // validated, and `isRecord` is what it asks at each step: a cast there would
11
+ // assert a shape the parser never promised, and a sequence or a scalar in any of
12
+ // those slots would be indexed as a mapping.
13
+ import { isRecord } from "../core/records.js";
14
+ import { DocsRepoUnavailableError, compareIds, featurePaths, featureSpecPaths, listServices, missingFeatureMessage, nearestIds, resolveFeature, } from "../core/repo.js";
15
+ import { parseRequirements } from "../core/spec.js";
16
+ export function registerDelta(program) {
17
+ program
18
+ .command("delta")
19
+ .argument("<featureId>", "feature id, e.g. FEAT-101")
20
+ .description("Project a feature onto a service: why + requirement delta + C4 changes")
21
+ .option("--service <id>", "service to project onto (defaults to the configured service)")
22
+ .option("--json", "emit the machine contract instead of the human view")
23
+ .action(async (featureId, opts) => {
24
+ const json = opts.json === true;
25
+ const config = await loadConfig();
26
+ if (!config) {
27
+ reportNoConfig(json);
28
+ return;
29
+ }
30
+ // The id grammar first, and on the RAW argument: `--service ../../etc`
31
+ // reaches `featureSpecPaths` and reads from outside the feature directory.
32
+ // One grammar for the whole tool (core/ids.ts).
33
+ if (opts.service !== undefined) {
34
+ try {
35
+ assertServiceId(opts.service, "--service");
36
+ }
37
+ catch (err) {
38
+ if (!(err instanceof InvalidIdError))
39
+ throw err;
40
+ fail(json, "invalid-option", err.message);
41
+ return;
42
+ }
43
+ }
44
+ // The feature is resolved BEFORE the service is chosen, because every
45
+ // sentence about the service ("which ones does this feature touch?") is
46
+ // only answerable once the feature is known.
47
+ const feature = await resolveFeature(config.docsDir, featureId, "exclude");
48
+ if (!feature) {
49
+ fail(json, "unknown-target", await missingFeatureMessage(config.docsDir, featureId));
50
+ return;
51
+ }
52
+ // Canonical id from here on — the delta's tags carry `#FEAT-5`, so a raw
53
+ // `FEAT-5-slug` argument used to empty the architecture slice silently.
54
+ const { id } = feature;
55
+ const paths = featurePaths(feature.dir);
56
+ const deltaDoc = existsSync(paths.delta) ? await loadFile(paths.delta) : null;
57
+ // Services the feature speaks about at all: one with a requirement delta,
58
+ // or one its C4 delta introduces. This is the list a refusal quotes.
59
+ const featureServices = [
60
+ ...new Set([...feature.services, ...introducedServices(deltaDoc, id)]),
61
+ ].sort(compareIds);
62
+ const service = opts.service ?? config.service;
63
+ if (!service) {
64
+ // The refusal NAMES the choices. "No service. Pass --service <id>" told
65
+ // the reader the shape of the flag and nothing about the answer, so the
66
+ // next step was always to go list the feature's specs/ by hand — and an
67
+ // agent had no way to recover at all. The list rides the JSON envelope
68
+ // for the same reason.
69
+ const message = `No service to project ${id} onto. Pass --service <id> or set 'service' in loam.json.` +
70
+ (featureServices.length === 0
71
+ ? ` ${id} carries no per-service delta yet.`
72
+ : ` ${id} touches: ${featureServices.join(", ")}.`);
73
+ if (json)
74
+ emitJsonError("invalid-option", message, { feature: id, services: featureServices });
75
+ else {
76
+ console.error(message);
77
+ process.exitCode = 1;
78
+ }
79
+ return;
80
+ }
81
+ // A service nobody has heard of produces a perfectly plausible EMPTY
82
+ // report — no requirements, no edges, "no architecture change" — which an
83
+ // agent consuming this as a task brief reads as "nothing to do here".
84
+ // A typo must never be indistinguishable from a finished service.
85
+ const living = await livingServices(config.docsDir);
86
+ if (!featureServices.includes(service) && !living.includes(service)) {
87
+ const near = nearestIds(service, [...new Set([...featureServices, ...living])]);
88
+ fail(json, "unknown-target", `No service '${service}': it is neither a services/${service}/ in the docs repo nor a service ${id} touches` +
89
+ (featureServices.length === 0 ? "" : ` (${featureServices.join(", ")})`) +
90
+ (near.length === 0 ? "." : ` — did you mean ${near.map((n) => `'${n}'`).join(" or ")}?`));
91
+ return;
92
+ }
93
+ // Why — business intent
94
+ const intent = existsSync(paths.intent)
95
+ ? stripFrontmatter(await readFile(paths.intent, "utf8")).trim()
96
+ : null;
97
+ // Requirement delta for this service (OpenSpec style), business and arch:
98
+ // the same projection covers both axes, in the same shape, so the payload
99
+ // stays one task — the arch requirements are the integration/ops half the
100
+ // business ones never mention.
101
+ const specPaths = featureSpecPaths(feature.dir, service);
102
+ const reqs = existsSync(specPaths.spec)
103
+ ? parseRequirements(await readFile(specPaths.spec, "utf8"))
104
+ : [];
105
+ const archReqs = existsSync(specPaths.archSpec)
106
+ ? parseRequirements(await readFile(specPaths.archSpec, "utf8"))
107
+ : [];
108
+ // The contract axis. Until now no command surfaced it at all: the feature's
109
+ // openapi.yaml was read by validate and by archive, and the one command
110
+ // whose output IS the implementation task never mentioned it — so "add the
111
+ // endpoint" was the half of the work the brief left out.
112
+ const api = await apiChanges(specPaths.openapi);
113
+ // C4 architecture slice
114
+ const arch = archSlice(deltaDoc, service, id);
115
+ // An unparseable delta.likec4 empties the C4 slice, and a consumer
116
+ // reading this projection as a task brief — the JSON payload and the
117
+ // printed view alike — would take that as "no architecture change": the
118
+ // vacuously-green pattern. The output stays as informative as ever (and
119
+ // `ok` stays true under --json: the command ran); the exit code is what
120
+ // stops a pipeline from building on it, so it is set BEFORE the format
121
+ // fork — the guard is about the delta, not about how it is rendered.
122
+ //
123
+ // The contract axis fails the same way and now earns the same guard: an
124
+ // openapi.yaml that does not parse projected as an empty operation list,
125
+ // and nothing upstream catches it either — `loam validate` grades
126
+ // `openapi.invalid` on LIVING service contracts only, never on a
127
+ // feature's delta.
128
+ if (arch.errors.length > 0 || api.unreadable)
129
+ process.exitCode = 1;
130
+ if (json) {
131
+ const reqJson = (r) => ({
132
+ kind: r.kind,
133
+ id: r.id,
134
+ name: r.name,
135
+ text: r.text.join("\n").trim(),
136
+ operations: r.operations,
137
+ covers: r.covers,
138
+ // Scenarios go out verbatim: they are the acceptance criteria and the
139
+ // source for the tests whoever picks this up is expected to write.
140
+ scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),
141
+ });
142
+ emitJson({
143
+ feature: id,
144
+ service,
145
+ services: featureServices,
146
+ path: repoPath(config.docsDir, feature.dir),
147
+ intent,
148
+ requirements: reqs.map(reqJson),
149
+ // Same shape, separate section: an arch requirement's scenarios are
150
+ // integration/ops tests, and a consumer must not have to parse prose
151
+ // to tell the two apart.
152
+ archRequirements: archReqs.map(reqJson),
153
+ api: api.changes,
154
+ // `api` stays exactly the operations array it has always been — a
155
+ // consumer indexing it must not have to learn a new shape — so the
156
+ // readability of the document rides alongside as its own key,
157
+ // spelled the way `loam show` already spells it for a living
158
+ // contract. Additive, which the envelope permits (core/json.ts).
159
+ openapi: {
160
+ unreadable: api.unreadable,
161
+ ...(api.error === undefined ? {} : { error: api.error }),
162
+ },
163
+ architecture: arch,
164
+ });
165
+ return;
166
+ }
167
+ console.log(`${id} · ${service}\n`);
168
+ if (intent) {
169
+ console.log(indent(intent, " "));
170
+ console.log();
171
+ }
172
+ if (existsSync(specPaths.spec))
173
+ printRequirements(reqs, "Requirements");
174
+ else
175
+ console.log("Requirements: (none for this service)\n");
176
+ if (existsSync(specPaths.archSpec))
177
+ printRequirements(archReqs, "Arch requirements");
178
+ if (existsSync(specPaths.openapi))
179
+ printApi(api);
180
+ if (existsSync(paths.delta))
181
+ printArchSlice(arch, service);
182
+ });
183
+ }
184
+ /** Every service in the docs repo, or none when there is no docs repo to ask. */
185
+ async function livingServices(docsDir) {
186
+ try {
187
+ return (await listServices(docsDir)).map((s) => s.id);
188
+ }
189
+ catch (err) {
190
+ // "There is no docs repo at all" is a different diagnosis with its own
191
+ // command (`loam doctor`); turning it into "unknown service" here would
192
+ // send the reader after a typo that is not there.
193
+ if (!(err instanceof DocsRepoUnavailableError))
194
+ throw err;
195
+ return [];
196
+ }
197
+ }
198
+ /** Services the feature's C4 delta introduces — its own tagged top-level elements. */
199
+ function introducedServices(doc, featureId) {
200
+ if (doc === null || doc.errors.length > 0)
201
+ return [];
202
+ return doc.elements.filter((e) => e.tags.includes(featureId)).map(elementService);
203
+ }
204
+ /**
205
+ * The feature's OpenAPI delta for one service, as slots rather than names.
206
+ *
207
+ * The operation set comes from core/openapi.ts — the same structure-aware reader
208
+ * every other check uses, so this view cannot see an operation validate does not.
209
+ * `summary` is the one field that reader does not carry, and it is the sentence
210
+ * an implementer most wants, so it is looked up by the path+method the reader
211
+ * already resolved: a second read of the same document, never a second opinion
212
+ * about what an operation IS.
213
+ */
214
+ async function apiChanges(openapiPath) {
215
+ if (!existsSync(openapiPath))
216
+ return { changes: [], unreadable: false };
217
+ // `readOpenapi`, not `operations`: the operation list alone cannot tell a
218
+ // contract that defines nothing from one that could not be read, and this is
219
+ // the command whose output IS the implementation task.
220
+ const api = await readOpenapi(openapiPath);
221
+ if (api.unreadable) {
222
+ return { changes: [], unreadable: true, ...(api.error === undefined ? {} : { error: api.error }) };
223
+ }
224
+ const ops = api.ops;
225
+ if (ops.length === 0)
226
+ return { changes: [], unreadable: false };
227
+ let doc;
228
+ try {
229
+ doc = parseYaml(await readFile(openapiPath, "utf8"));
230
+ }
231
+ catch {
232
+ // Unreadable is impossible here (the reader above said otherwise), but a
233
+ // summary is decoration and must never be the reason a task brief fails to
234
+ // print.
235
+ doc = null;
236
+ }
237
+ const summaryOf = (op) => {
238
+ if (!isRecord(doc))
239
+ return null;
240
+ const paths = doc["paths"];
241
+ if (!isRecord(paths))
242
+ return null;
243
+ const item = paths[op.path];
244
+ if (!isRecord(item))
245
+ return null;
246
+ const entry = item[op.method];
247
+ if (!isRecord(entry))
248
+ return null;
249
+ const summary = entry["summary"];
250
+ return typeof summary === "string" && summary.length > 0 ? summary : null;
251
+ };
252
+ return {
253
+ changes: ops
254
+ .map((op) => ({
255
+ path: op.path,
256
+ method: op.method.toUpperCase(),
257
+ operationId: op.id,
258
+ summary: summaryOf(op),
259
+ remove: op.remove,
260
+ }))
261
+ .sort((a, b) => compareIds(a.path, b.path) || compareIds(a.method, b.method)),
262
+ unreadable: false,
263
+ };
264
+ }
265
+ /** The feature's tagged edges around one service, plus whether the service is new. */
266
+ function archSlice(doc, service, featureId) {
267
+ const empty = { isNew: false, inbound: [], outbound: [], errors: [] };
268
+ if (doc === null)
269
+ return empty;
270
+ const { errors, elements, relationships } = doc;
271
+ if (errors.length > 0) {
272
+ return { ...empty, errors: errors.map((e) => (typeof e.line === "number" ? `L${e.line}: ${e.message}` : e.message)) };
273
+ }
274
+ // Which service an element stands for is the binding's call, not the title's.
275
+ const svcOf = (id) => serviceOf(elements, id);
276
+ const edge = (r, other) => ({
277
+ service: other,
278
+ op: r.op ?? null,
279
+ title: r.title ?? null,
280
+ });
281
+ const featRels = relationships.filter((r) => r.tags.includes(featureId));
282
+ return {
283
+ isNew: elements.some((e) => elementService(e) === service && e.tags.includes(featureId)),
284
+ inbound: featRels.filter((r) => svcOf(r.target) === service).map((r) => edge(r, svcOf(r.source))),
285
+ outbound: featRels.filter((r) => svcOf(r.source) === service).map((r) => edge(r, svcOf(r.target))),
286
+ errors: [],
287
+ };
288
+ }
289
+ function stripFrontmatter(md) {
290
+ if (!md.startsWith("---"))
291
+ return md;
292
+ const close = md.indexOf("\n---", 3);
293
+ if (close === -1)
294
+ return md;
295
+ const nl = md.indexOf("\n", close + 1);
296
+ return nl === -1 ? "" : md.slice(nl + 1).trimStart();
297
+ }
298
+ /**
299
+ * The requirement delta, in full.
300
+ *
301
+ * The text view used to print headings and scenario NAMES only, which made it a
302
+ * table of contents for a file the reader then had to open — while `--json`
303
+ * carried the requirement body and the Given/When/Then lines verbatim. The two
304
+ * are the same briefing, so they carry the same content: a person reading this
305
+ * in a terminal is being asked to implement it, exactly like the agent reading
306
+ * the payload.
307
+ */
308
+ function printRequirements(reqs, label) {
309
+ if (reqs.length === 0) {
310
+ console.log(`${label}: (none)\n`);
311
+ return;
312
+ }
313
+ console.log(`${label}:`);
314
+ for (const r of reqs) {
315
+ const tag = r.kind === "BASE" ? "" : `[${r.kind}] `;
316
+ const n = r.scenarios.length;
317
+ console.log(` ${tag}${r.name} (${n} scenario${n === 1 ? "" : "s"})`);
318
+ const body = r.text.join("\n").trim();
319
+ if (body.length > 0)
320
+ console.log(indent(body, " "));
321
+ if (r.operations.length > 0)
322
+ console.log(` Operations: ${r.operations.join(", ")}`);
323
+ if (r.covers.length > 0)
324
+ console.log(` Covers: ${r.covers.join(", ")}`);
325
+ for (const s of r.scenarios) {
326
+ console.log(`\n Scenario: ${s.name}`);
327
+ // Verbatim, bullets and markdown emphasis included: these lines are the
328
+ // acceptance criteria, and paraphrasing them here would put a second
329
+ // wording of the contract in front of whoever implements it.
330
+ for (const line of s.lines)
331
+ console.log(` ${line}`);
332
+ }
333
+ console.log();
334
+ }
335
+ }
336
+ function printApi(api) {
337
+ if (api.unreadable) {
338
+ // Worded like `loam show`'s answer for the same failure on a living
339
+ // contract, and deliberately WITHOUT a "run `loam validate`" hint: validate
340
+ // grades `openapi.invalid` on living service contracts only, so it has
341
+ // nothing to say about this file and sending the reader there is a dead end.
342
+ console.log("API: openapi.yaml does not parse");
343
+ if (api.error !== undefined)
344
+ console.log(` ${api.error}`);
345
+ console.log();
346
+ return;
347
+ }
348
+ if (api.changes.length === 0) {
349
+ console.log("API: (the openapi delta defines no operations)\n");
350
+ return;
351
+ }
352
+ console.log("API (this feature's openapi.yaml for the service):");
353
+ for (const op of api.changes) {
354
+ const marker = op.remove ? "REMOVE " : "";
355
+ console.log(` ${marker}${op.method} ${op.path} ${op.operationId}${op.summary === null ? "" : ` — ${op.summary}`}`);
356
+ }
357
+ console.log();
358
+ }
359
+ function printArchSlice(arch, service) {
360
+ if (arch.errors.length > 0) {
361
+ console.log("Architecture: delta.likec4 has errors — run `loam validate`.");
362
+ return;
363
+ }
364
+ console.log("Architecture:");
365
+ if (arch.isNew)
366
+ console.log(` NEW service — create ${service}`);
367
+ if (arch.outbound.length > 0) {
368
+ console.log(" Outbound (new calls from this service):");
369
+ for (const e of arch.outbound)
370
+ console.log(` → ${e.service} "${e.title ?? ""}"`);
371
+ }
372
+ if (arch.inbound.length > 0) {
373
+ console.log(" Inbound (this service is newly called):");
374
+ for (const e of arch.inbound)
375
+ console.log(` ← ${e.service} "${e.title ?? ""}"`);
376
+ }
377
+ if (!arch.isNew && arch.outbound.length === 0 && arch.inbound.length === 0) {
378
+ console.log(" (no architecture change for this service)");
379
+ }
380
+ console.log();
381
+ }
382
+ function indent(text, pad) {
383
+ return text
384
+ .split("\n")
385
+ .map((l) => (l.length > 0 ? pad + l : l))
386
+ .join("\n");
387
+ }
388
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/commands/delta.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 { parse as parseYaml } from \"yaml\";\nimport { loadConfig } from \"../core/config.js\";\nimport { InvalidIdError, assertServiceId } from \"../core/ids.js\";\nimport { emitJson, emitJsonError, fail, repoPath, reportNoConfig } from \"../core/json.js\";\nimport { elementService, loadFile, serviceOf, type Elem, type LoadedDoc, type Rel } from \"../core/likec4.js\";\nimport { readOpenapi, type Operation } from \"../core/openapi.js\";\n// The summary walk below descends four levels into a document nobody has\n// validated, and `isRecord` is what it asks at each step: a cast there would\n// assert a shape the parser never promised, and a sequence or a scalar in any of\n// those slots would be indexed as a mapping.\nimport { isRecord } from \"../core/records.js\";\nimport {\n DocsRepoUnavailableError,\n compareIds,\n featurePaths,\n featureSpecPaths,\n listServices,\n missingFeatureMessage,\n nearestIds,\n resolveFeature,\n} from \"../core/repo.js\";\nimport { parseRequirements, type Requirement } from \"../core/spec.js\";\n\ninterface DeltaOptions {\n service?: string;\n json?: boolean;\n}\n\n/** One end of a feature edge, as seen from the projected service. */\ninterface Edge {\n service: string;\n op: string | null;\n title: string | null;\n}\n\ninterface ArchSlice {\n isNew: boolean;\n inbound: Edge[];\n outbound: Edge[];\n errors: string[];\n}\n\n/**\n * One operation the feature's OpenAPI delta defines for this service. The\n * projection is a task brief, and \"implement createSplit\" without the path and\n * the method is not a task — it is a name to go look up in a file the reader\n * was not told about.\n */\ninterface ApiChange {\n path: string;\n method: string;\n operationId: string;\n summary: string | null;\n /** `x-loam-remove: true` — this operation is being retired, not added. */\n remove: boolean;\n}\n\n/**\n * The contract axis of the brief: the operations, and whether the document they\n * came from could be read at all.\n *\n * The two have to travel together. A feature `openapi.yaml` that does not parse\n * yields zero operations, which is the same answer as a delta that genuinely\n * changes no endpoints — and this projection is a task brief, so \"no contract\n * work here\" over a YAML error is the vacuously-green trap the architecture\n * axis already guards against.\n */\ninterface ApiSlice {\n changes: ApiChange[];\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n}\n\nexport function registerDelta(program: Command): void {\n program\n .command(\"delta\")\n .argument(\"<featureId>\", \"feature id, e.g. FEAT-101\")\n .description(\"Project a feature onto a service: why + requirement delta + C4 changes\")\n .option(\"--service <id>\", \"service to project onto (defaults to the configured service)\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureId: string, opts: DeltaOptions) => {\n const json = opts.json === true;\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n\n // The id grammar first, and on the RAW argument: `--service ../../etc`\n // reaches `featureSpecPaths` and reads from outside the feature directory.\n // One grammar for the whole tool (core/ids.ts).\n if (opts.service !== undefined) {\n try {\n assertServiceId(opts.service, \"--service\");\n } catch (err) {\n if (!(err instanceof InvalidIdError)) throw err;\n fail(json, \"invalid-option\", err.message);\n return;\n }\n }\n\n // The feature is resolved BEFORE the service is chosen, because every\n // sentence about the service (\"which ones does this feature touch?\") is\n // only answerable once the feature is known.\n const feature = await resolveFeature(config.docsDir, featureId, \"exclude\");\n if (!feature) {\n fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureId));\n return;\n }\n // Canonical id from here on — the delta's tags carry `#FEAT-5`, so a raw\n // `FEAT-5-slug` argument used to empty the architecture slice silently.\n const { id } = feature;\n const paths = featurePaths(feature.dir);\n\n const deltaDoc = existsSync(paths.delta) ? await loadFile(paths.delta) : null;\n // Services the feature speaks about at all: one with a requirement delta,\n // or one its C4 delta introduces. This is the list a refusal quotes.\n const featureServices = [\n ...new Set([...feature.services, ...introducedServices(deltaDoc, id)]),\n ].sort(compareIds);\n\n const service = opts.service ?? config.service;\n if (!service) {\n // The refusal NAMES the choices. \"No service. Pass --service <id>\" told\n // the reader the shape of the flag and nothing about the answer, so the\n // next step was always to go list the feature's specs/ by hand — and an\n // agent had no way to recover at all. The list rides the JSON envelope\n // for the same reason.\n const message =\n `No service to project ${id} onto. Pass --service <id> or set 'service' in loam.json.` +\n (featureServices.length === 0\n ? ` ${id} carries no per-service delta yet.`\n : ` ${id} touches: ${featureServices.join(\", \")}.`);\n if (json) emitJsonError(\"invalid-option\", message, { feature: id, services: featureServices });\n else {\n console.error(message);\n process.exitCode = 1;\n }\n return;\n }\n\n // A service nobody has heard of produces a perfectly plausible EMPTY\n // report — no requirements, no edges, \"no architecture change\" — which an\n // agent consuming this as a task brief reads as \"nothing to do here\".\n // A typo must never be indistinguishable from a finished service.\n const living = await livingServices(config.docsDir);\n if (!featureServices.includes(service) && !living.includes(service)) {\n const near = nearestIds(service, [...new Set([...featureServices, ...living])]);\n fail(\n json,\n \"unknown-target\",\n `No service '${service}': it is neither a services/${service}/ in the docs repo nor a service ${id} touches` +\n (featureServices.length === 0 ? \"\" : ` (${featureServices.join(\", \")})`) +\n (near.length === 0 ? \".\" : ` — did you mean ${near.map((n) => `'${n}'`).join(\" or \")}?`),\n );\n return;\n }\n\n // Why — business intent\n const intent = existsSync(paths.intent)\n ? stripFrontmatter(await readFile(paths.intent, \"utf8\")).trim()\n : null;\n\n // Requirement delta for this service (OpenSpec style), business and arch:\n // the same projection covers both axes, in the same shape, so the payload\n // stays one task — the arch requirements are the integration/ops half the\n // business ones never mention.\n const specPaths = featureSpecPaths(feature.dir, service);\n const reqs = existsSync(specPaths.spec)\n ? parseRequirements(await readFile(specPaths.spec, \"utf8\"))\n : [];\n const archReqs = existsSync(specPaths.archSpec)\n ? parseRequirements(await readFile(specPaths.archSpec, \"utf8\"))\n : [];\n\n // The contract axis. Until now no command surfaced it at all: the feature's\n // openapi.yaml was read by validate and by archive, and the one command\n // whose output IS the implementation task never mentioned it — so \"add the\n // endpoint\" was the half of the work the brief left out.\n const api = await apiChanges(specPaths.openapi);\n\n // C4 architecture slice\n const arch = archSlice(deltaDoc, service, id);\n\n // An unparseable delta.likec4 empties the C4 slice, and a consumer\n // reading this projection as a task brief — the JSON payload and the\n // printed view alike — would take that as \"no architecture change\": the\n // vacuously-green pattern. The output stays as informative as ever (and\n // `ok` stays true under --json: the command ran); the exit code is what\n // stops a pipeline from building on it, so it is set BEFORE the format\n // fork — the guard is about the delta, not about how it is rendered.\n //\n // The contract axis fails the same way and now earns the same guard: an\n // openapi.yaml that does not parse projected as an empty operation list,\n // and nothing upstream catches it either — `loam validate` grades\n // `openapi.invalid` on LIVING service contracts only, never on a\n // feature's delta.\n if (arch.errors.length > 0 || api.unreadable) process.exitCode = 1;\n\n if (json) {\n const reqJson = (r: Requirement): Record<string, unknown> => ({\n kind: r.kind,\n id: r.id,\n name: r.name,\n text: r.text.join(\"\\n\").trim(),\n operations: r.operations,\n covers: r.covers,\n // Scenarios go out verbatim: they are the acceptance criteria and the\n // source for the tests whoever picks this up is expected to write.\n scenarios: r.scenarios.map((s) => ({ name: s.name, lines: s.lines })),\n });\n emitJson({\n feature: id,\n service,\n services: featureServices,\n path: repoPath(config.docsDir, feature.dir),\n intent,\n requirements: reqs.map(reqJson),\n // Same shape, separate section: an arch requirement's scenarios are\n // integration/ops tests, and a consumer must not have to parse prose\n // to tell the two apart.\n archRequirements: archReqs.map(reqJson),\n api: api.changes,\n // `api` stays exactly the operations array it has always been — a\n // consumer indexing it must not have to learn a new shape — so the\n // readability of the document rides alongside as its own key,\n // spelled the way `loam show` already spells it for a living\n // contract. Additive, which the envelope permits (core/json.ts).\n openapi: {\n unreadable: api.unreadable,\n ...(api.error === undefined ? {} : { error: api.error }),\n },\n architecture: arch,\n });\n return;\n }\n\n console.log(`${id} · ${service}\\n`);\n if (intent) {\n console.log(indent(intent, \" \"));\n console.log();\n }\n if (existsSync(specPaths.spec)) printRequirements(reqs, \"Requirements\");\n else console.log(\"Requirements: (none for this service)\\n\");\n if (existsSync(specPaths.archSpec)) printRequirements(archReqs, \"Arch requirements\");\n if (existsSync(specPaths.openapi)) printApi(api);\n if (existsSync(paths.delta)) printArchSlice(arch, service);\n });\n}\n\n/** Every service in the docs repo, or none when there is no docs repo to ask. */\nasync function livingServices(docsDir: string): Promise<string[]> {\n try {\n return (await listServices(docsDir)).map((s) => s.id);\n } catch (err) {\n // \"There is no docs repo at all\" is a different diagnosis with its own\n // command (`loam doctor`); turning it into \"unknown service\" here would\n // send the reader after a typo that is not there.\n if (!(err instanceof DocsRepoUnavailableError)) throw err;\n return [];\n }\n}\n\n/** Services the feature's C4 delta introduces — its own tagged top-level elements. */\nfunction introducedServices(doc: LoadedDoc | null, featureId: string): string[] {\n if (doc === null || doc.errors.length > 0) return [];\n return doc.elements.filter((e: Elem) => e.tags.includes(featureId)).map(elementService);\n}\n\n/**\n * The feature's OpenAPI delta for one service, as slots rather than names.\n *\n * The operation set comes from core/openapi.ts — the same structure-aware reader\n * every other check uses, so this view cannot see an operation validate does not.\n * `summary` is the one field that reader does not carry, and it is the sentence\n * an implementer most wants, so it is looked up by the path+method the reader\n * already resolved: a second read of the same document, never a second opinion\n * about what an operation IS.\n */\nasync function apiChanges(openapiPath: string): Promise<ApiSlice> {\n if (!existsSync(openapiPath)) return { changes: [], unreadable: false };\n // `readOpenapi`, not `operations`: the operation list alone cannot tell a\n // contract that defines nothing from one that could not be read, and this is\n // the command whose output IS the implementation task.\n const api = await readOpenapi(openapiPath);\n if (api.unreadable) {\n return { changes: [], unreadable: true, ...(api.error === undefined ? {} : { error: api.error }) };\n }\n const ops = api.ops;\n if (ops.length === 0) return { changes: [], unreadable: false };\n let doc: unknown;\n try {\n doc = parseYaml(await readFile(openapiPath, \"utf8\"));\n } catch {\n // Unreadable is impossible here (the reader above said otherwise), but a\n // summary is decoration and must never be the reason a task brief fails to\n // print.\n doc = null;\n }\n const summaryOf = (op: Operation): string | null => {\n if (!isRecord(doc)) return null;\n const paths = doc[\"paths\"];\n if (!isRecord(paths)) return null;\n const item = paths[op.path];\n if (!isRecord(item)) return null;\n const entry = item[op.method];\n if (!isRecord(entry)) return null;\n const summary = entry[\"summary\"];\n return typeof summary === \"string\" && summary.length > 0 ? summary : null;\n };\n return {\n changes: ops\n .map((op): ApiChange => ({\n path: op.path,\n method: op.method.toUpperCase(),\n operationId: op.id,\n summary: summaryOf(op),\n remove: op.remove,\n }))\n .sort((a, b) => compareIds(a.path, b.path) || compareIds(a.method, b.method)),\n unreadable: false,\n };\n}\n\n/** The feature's tagged edges around one service, plus whether the service is new. */\nfunction archSlice(doc: LoadedDoc | null, service: string, featureId: string): ArchSlice {\n const empty: ArchSlice = { isNew: false, inbound: [], outbound: [], errors: [] };\n if (doc === null) return empty;\n\n const { errors, elements, relationships } = doc;\n if (errors.length > 0) {\n return { ...empty, errors: errors.map((e) => (typeof e.line === \"number\" ? `L${e.line}: ${e.message}` : e.message)) };\n }\n\n // Which service an element stands for is the binding's call, not the title's.\n const svcOf = (id: string): string => serviceOf(elements, id);\n const edge = (r: Rel, other: string): Edge => ({\n service: other,\n op: r.op ?? null,\n title: r.title ?? null,\n });\n const featRels = relationships.filter((r) => r.tags.includes(featureId));\n\n return {\n isNew: elements.some((e) => elementService(e) === service && e.tags.includes(featureId)),\n inbound: featRels.filter((r) => svcOf(r.target) === service).map((r) => edge(r, svcOf(r.source))),\n outbound: featRels.filter((r) => svcOf(r.source) === service).map((r) => edge(r, svcOf(r.target))),\n errors: [],\n };\n}\n\nfunction stripFrontmatter(md: string): string {\n if (!md.startsWith(\"---\")) return md;\n const close = md.indexOf(\"\\n---\", 3);\n if (close === -1) return md;\n const nl = md.indexOf(\"\\n\", close + 1);\n return nl === -1 ? \"\" : md.slice(nl + 1).trimStart();\n}\n\n/**\n * The requirement delta, in full.\n *\n * The text view used to print headings and scenario NAMES only, which made it a\n * table of contents for a file the reader then had to open — while `--json`\n * carried the requirement body and the Given/When/Then lines verbatim. The two\n * are the same briefing, so they carry the same content: a person reading this\n * in a terminal is being asked to implement it, exactly like the agent reading\n * the payload.\n */\nfunction printRequirements(reqs: Requirement[], label: string): void {\n if (reqs.length === 0) {\n console.log(`${label}: (none)\\n`);\n return;\n }\n console.log(`${label}:`);\n for (const r of reqs) {\n const tag = r.kind === \"BASE\" ? \"\" : `[${r.kind}] `;\n const n = r.scenarios.length;\n console.log(` ${tag}${r.name} (${n} scenario${n === 1 ? \"\" : \"s\"})`);\n const body = r.text.join(\"\\n\").trim();\n if (body.length > 0) console.log(indent(body, \" \"));\n if (r.operations.length > 0) console.log(` Operations: ${r.operations.join(\", \")}`);\n if (r.covers.length > 0) console.log(` Covers: ${r.covers.join(\", \")}`);\n for (const s of r.scenarios) {\n console.log(`\\n Scenario: ${s.name}`);\n // Verbatim, bullets and markdown emphasis included: these lines are the\n // acceptance criteria, and paraphrasing them here would put a second\n // wording of the contract in front of whoever implements it.\n for (const line of s.lines) console.log(` ${line}`);\n }\n console.log();\n }\n}\n\nfunction printApi(api: ApiSlice): void {\n if (api.unreadable) {\n // Worded like `loam show`'s answer for the same failure on a living\n // contract, and deliberately WITHOUT a \"run `loam validate`\" hint: validate\n // grades `openapi.invalid` on living service contracts only, so it has\n // nothing to say about this file and sending the reader there is a dead end.\n console.log(\"API: openapi.yaml does not parse\");\n if (api.error !== undefined) console.log(` ${api.error}`);\n console.log();\n return;\n }\n if (api.changes.length === 0) {\n console.log(\"API: (the openapi delta defines no operations)\\n\");\n return;\n }\n console.log(\"API (this feature's openapi.yaml for the service):\");\n for (const op of api.changes) {\n const marker = op.remove ? \"REMOVE \" : \"\";\n console.log(\n ` ${marker}${op.method} ${op.path} ${op.operationId}${op.summary === null ? \"\" : ` — ${op.summary}`}`,\n );\n }\n console.log();\n}\n\nfunction printArchSlice(arch: ArchSlice, service: string): void {\n if (arch.errors.length > 0) {\n console.log(\"Architecture: delta.likec4 has errors — run `loam validate`.\");\n return;\n }\n console.log(\"Architecture:\");\n if (arch.isNew) console.log(` NEW service — create ${service}`);\n if (arch.outbound.length > 0) {\n console.log(\" Outbound (new calls from this service):\");\n for (const e of arch.outbound) console.log(` → ${e.service} \"${e.title ?? \"\"}\"`);\n }\n if (arch.inbound.length > 0) {\n console.log(\" Inbound (this service is newly called):\");\n for (const e of arch.inbound) console.log(` ← ${e.service} \"${e.title ?? \"\"}\"`);\n }\n if (!arch.isNew && arch.outbound.length === 0 && arch.inbound.length === 0) {\n console.log(\" (no architecture change for this service)\");\n }\n console.log();\n}\n\nfunction indent(text: string, pad: string): string {\n return text\n .split(\"\\n\")\n .map((l) => (l.length > 0 ? pad + l : l))\n .join(\"\\n\");\n}\n"]}
@@ -0,0 +1,83 @@
1
+ import { loadConfig } from "../core/config.js";
2
+ import { analyzeDependencies, } from "../core/dependencies.js";
3
+ import { FleetContext } from "../core/fleet-context.js";
4
+ import { emitJson, fail, reportNoConfig } from "../core/json.js";
5
+ import { missingFeatureMessage, resolveFeature } from "../core/repo.js";
6
+ export function registerDependencies(program) {
7
+ program
8
+ .command("dependencies")
9
+ .argument("[featureId]", "active feature id; omit for the whole in-flight graph")
10
+ .description("Show dependencies and conflicts derived from active feature artifacts")
11
+ .option("--json", "emit the machine contract instead of the human view")
12
+ .action(async (featureArg, opts) => {
13
+ const json = opts.json === true;
14
+ const config = await loadConfig();
15
+ if (!config) {
16
+ reportNoConfig(json);
17
+ return;
18
+ }
19
+ const context = new FleetContext();
20
+ const feature = featureArg === undefined
21
+ ? null
22
+ : await resolveFeature(config.docsDir, featureArg, "exclude", context);
23
+ if (featureArg !== undefined && feature === null) {
24
+ // The shared miss message rather than a local one: this command resolves
25
+ // in `exclude` mode, so the commonest miss is an id that HAS shipped, and
26
+ // "no active feature" leaves the reader to discover that themselves. The
27
+ // helper reuses the context already built above, so saying so costs one
28
+ // enumeration this invocation has already paid for.
29
+ fail(json, "unknown-target", await missingFeatureMessage(config.docsDir, featureArg, context));
30
+ return;
31
+ }
32
+ const graph = await analyzeDependencies(config.docsDir, feature?.id, context);
33
+ if (json) {
34
+ emitJson({
35
+ command: "dependencies",
36
+ docsDir: config.docsDir,
37
+ feature: feature?.id ?? null,
38
+ ...graph,
39
+ });
40
+ return;
41
+ }
42
+ printGraph(graph, feature?.id);
43
+ });
44
+ }
45
+ function reasonText(reason) {
46
+ return reason.kind === "operation"
47
+ ? `${reason.service} operation ${reason.operationId}`
48
+ : `${reason.service}/${reason.axis} ${reason.identity}`;
49
+ }
50
+ function printGraph(graph, featureId) {
51
+ const scope = featureId === undefined ? "active features" : `${featureId} and prerequisites`;
52
+ console.log(`dependencies — ${scope} (${graph.nodes.length})`);
53
+ console.log(` order: ${graph.order.length === 0 ? "(none)" : graph.order.join(" → ")}`);
54
+ for (const node of graph.nodes) {
55
+ console.log(`\n ${node.id}${node.dependsOn.length === 0 ? " (independent)" : ""}`);
56
+ for (const edge of graph.edges.filter((candidate) => candidate.from === node.id)) {
57
+ console.log(` depends on ${edge.to}`);
58
+ for (const reason of edge.reasons)
59
+ console.log(` - ${reasonText(reason)}`);
60
+ }
61
+ }
62
+ if (graph.conflicts.length > 0) {
63
+ console.log("\n conflicts");
64
+ for (const conflict of graph.conflicts) {
65
+ const where = conflict.kind === "requirement"
66
+ ? `${conflict.service}/${conflict.axis}`
67
+ : conflict.service;
68
+ // "added by" vs "changed by": the two collisions want different fixes.
69
+ // Two features ADDING the same identity is usually duplicated work that
70
+ // should become one feature; two features CHANGING it is usually genuine
71
+ // parallel work whose second archive would silently overwrite the first's
72
+ // full text, so the losers need to be re-based, not merged.
73
+ const how = conflict.change === "added" ? "added by" : "changed by";
74
+ console.log(` ! ${where} ${conflict.identity} — ${how} ${conflict.features.join(", ")}`);
75
+ }
76
+ }
77
+ if (graph.cycles.length > 0) {
78
+ console.log("\n cycles");
79
+ for (const cycle of graph.cycles)
80
+ console.log(` ! ${cycle.join(" ↔ ")}`);
81
+ }
82
+ }
83
+ //# sourceMappingURL=dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/commands/dependencies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACL,mBAAmB,GAGpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAMxE,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,cAAc,CAAC;SACvB,QAAQ,CAAC,aAAa,EAAE,uDAAuD,CAAC;SAChF,WAAW,CAAC,uEAAuE,CAAC;SACpF,MAAM,CAAC,QAAQ,EAAE,qDAAqD,CAAC;SACvE,MAAM,CAAC,KAAK,EAAE,UAA8B,EAAE,IAAyB,EAAE,EAAE;QAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,cAAc,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,UAAU,KAAK,SAAS;YACtC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACzE,IAAI,UAAU,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACjD,yEAAyE;YACzE,0EAA0E;YAC1E,yEAAyE;YACzE,wEAAwE;YACxE,oDAAoD;YACpD,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAC/F,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAC9E,IAAI,IAAI,EAAE,CAAC;YACT,QAAQ,CAAC;gBACP,OAAO,EAAE,cAAc;gBACvB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,IAAI;gBAC5B,GAAG,KAAK;aACT,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,UAAU,CAAC,MAAwB;IAC1C,OAAO,MAAM,CAAC,IAAI,KAAK,WAAW;QAChC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,cAAc,MAAM,CAAC,WAAW,EAAE;QACrD,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,UAAU,CAAC,KAAsB,EAAE,SAAkB;IAC5D,MAAM,KAAK,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,oBAAoB,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAEzF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACjF,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO,CAAC,GAAG,CAAC,WAAW,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC7B,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,KAAK,aAAa;gBAC3C,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;gBACxC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YACrB,uEAAuE;YACvE,wEAAwE;YACxE,yEAAyE;YACzE,0EAA0E;YAC1E,4DAA4D;YAC5D,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC;YACpE,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,QAAQ,CAAC,QAAQ,MAAM,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC","sourcesContent":["import type { Command } from \"commander\";\nimport { loadConfig } from \"../core/config.js\";\nimport {\n analyzeDependencies,\n type DependencyGraph,\n type DependencyReason,\n} from \"../core/dependencies.js\";\nimport { FleetContext } from \"../core/fleet-context.js\";\nimport { emitJson, fail, reportNoConfig } from \"../core/json.js\";\nimport { missingFeatureMessage, resolveFeature } from \"../core/repo.js\";\n\ninterface DependenciesOptions {\n json?: boolean;\n}\n\nexport function registerDependencies(program: Command): void {\n program\n .command(\"dependencies\")\n .argument(\"[featureId]\", \"active feature id; omit for the whole in-flight graph\")\n .description(\"Show dependencies and conflicts derived from active feature artifacts\")\n .option(\"--json\", \"emit the machine contract instead of the human view\")\n .action(async (featureArg: string | undefined, opts: DependenciesOptions) => {\n const json = opts.json === true;\n const config = await loadConfig();\n if (!config) {\n reportNoConfig(json);\n return;\n }\n\n const context = new FleetContext();\n const feature = featureArg === undefined\n ? null\n : await resolveFeature(config.docsDir, featureArg, \"exclude\", context);\n if (featureArg !== undefined && feature === null) {\n // The shared miss message rather than a local one: this command resolves\n // in `exclude` mode, so the commonest miss is an id that HAS shipped, and\n // \"no active feature\" leaves the reader to discover that themselves. The\n // helper reuses the context already built above, so saying so costs one\n // enumeration this invocation has already paid for.\n fail(json, \"unknown-target\", await missingFeatureMessage(config.docsDir, featureArg, context));\n return;\n }\n\n const graph = await analyzeDependencies(config.docsDir, feature?.id, context);\n if (json) {\n emitJson({\n command: \"dependencies\",\n docsDir: config.docsDir,\n feature: feature?.id ?? null,\n ...graph,\n });\n return;\n }\n printGraph(graph, feature?.id);\n });\n}\n\nfunction reasonText(reason: DependencyReason): string {\n return reason.kind === \"operation\"\n ? `${reason.service} operation ${reason.operationId}`\n : `${reason.service}/${reason.axis} ${reason.identity}`;\n}\n\nfunction printGraph(graph: DependencyGraph, featureId?: string): void {\n const scope = featureId === undefined ? \"active features\" : `${featureId} and prerequisites`;\n console.log(`dependencies — ${scope} (${graph.nodes.length})`);\n console.log(` order: ${graph.order.length === 0 ? \"(none)\" : graph.order.join(\" → \")}`);\n\n for (const node of graph.nodes) {\n console.log(`\\n ${node.id}${node.dependsOn.length === 0 ? \" (independent)\" : \"\"}`);\n for (const edge of graph.edges.filter((candidate) => candidate.from === node.id)) {\n console.log(` depends on ${edge.to}`);\n for (const reason of edge.reasons) console.log(` - ${reasonText(reason)}`);\n }\n }\n\n if (graph.conflicts.length > 0) {\n console.log(\"\\n conflicts\");\n for (const conflict of graph.conflicts) {\n const where = conflict.kind === \"requirement\"\n ? `${conflict.service}/${conflict.axis}`\n : conflict.service;\n // \"added by\" vs \"changed by\": the two collisions want different fixes.\n // Two features ADDING the same identity is usually duplicated work that\n // should become one feature; two features CHANGING it is usually genuine\n // parallel work whose second archive would silently overwrite the first's\n // full text, so the losers need to be re-based, not merged.\n const how = conflict.change === \"added\" ? \"added by\" : \"changed by\";\n console.log(` ! ${where} ${conflict.identity} — ${how} ${conflict.features.join(\", \")}`);\n }\n }\n\n if (graph.cycles.length > 0) {\n console.log(\"\\n cycles\");\n for (const cycle of graph.cycles) console.log(` ! ${cycle.join(\" ↔ \")}`);\n }\n}\n"]}