@ybotok/loam 0.1.0-beta.1 → 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.
- package/CHANGELOG.md +49 -0
- package/README.md +6 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.js +17 -3
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/archive.js +24 -8
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/delta.js +74 -47
- package/dist/commands/delta.js.map +1 -1
- package/dist/commands/dependencies.js +7 -2
- package/dist/commands/dependencies.js.map +1 -1
- package/dist/commands/docs-repo-gate.js +104 -0
- package/dist/commands/docs-repo-gate.js.map +1 -0
- package/dist/commands/explore.js +195 -0
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/format.js +17 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/gherkin.js +87 -48
- package/dist/commands/gherkin.js.map +1 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/instructions.js +70 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/list.js +6 -76
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/migrate-openspec.js +45 -28
- package/dist/commands/migrate-openspec.js.map +1 -1
- package/dist/commands/new.js +35 -34
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/rebase.js +2 -4
- package/dist/commands/rebase.js.map +1 -1
- package/dist/commands/show.js +26 -31
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/status.js +8 -24
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unarchive.js +12 -5
- package/dist/commands/unarchive.js.map +1 -1
- package/dist/commands/validate.js +115 -133
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.js +33 -17
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/vouch.js +77 -59
- package/dist/commands/vouch.js.map +1 -1
- package/dist/core/agent.js +273 -31
- package/dist/core/agent.js.map +1 -1
- package/dist/core/arch.js +28 -2
- package/dist/core/arch.js.map +1 -1
- package/dist/core/brief.js +3 -3
- package/dist/core/brief.js.map +1 -1
- package/dist/core/coherence.js +37 -13
- package/dist/core/coherence.js.map +1 -1
- package/dist/core/concurrency.js +51 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.js +10 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/delta.js +21 -2
- package/dist/core/delta.js.map +1 -1
- package/dist/core/dependencies.js +46 -42
- package/dist/core/dependencies.js.map +1 -1
- package/dist/core/docs.js +2 -1
- package/dist/core/docs.js.map +1 -1
- package/dist/core/doctor.js +41 -26
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/document-bytes.js +59 -0
- package/dist/core/document-bytes.js.map +1 -0
- package/dist/core/explore.js +225 -0
- package/dist/core/explore.js.map +1 -0
- package/dist/core/fleet-context.js +16 -63
- package/dist/core/fleet-context.js.map +1 -1
- package/dist/core/frontmatter.js +1 -5
- package/dist/core/frontmatter.js.map +1 -1
- package/dist/core/gherkin.js +3 -61
- package/dist/core/gherkin.js.map +1 -1
- package/dist/core/health.js +1 -5
- package/dist/core/health.js.map +1 -1
- package/dist/core/ids.js +34 -5
- package/dist/core/ids.js.map +1 -1
- package/dist/core/json.js +8 -1
- package/dist/core/json.js.map +1 -1
- package/dist/core/likec4.js +6 -1
- package/dist/core/likec4.js.map +1 -1
- package/dist/core/maturity.js +66 -0
- package/dist/core/maturity.js.map +1 -0
- package/dist/core/openapi-merge.js +39 -6
- package/dist/core/openapi-merge.js.map +1 -1
- package/dist/core/openapi.js +17 -5
- package/dist/core/openapi.js.map +1 -1
- package/dist/core/openspec-inventory.js +64 -27
- package/dist/core/openspec-inventory.js.map +1 -1
- package/dist/core/path-safety.js +59 -0
- package/dist/core/path-safety.js.map +1 -1
- package/dist/core/provenance.js +35 -10
- package/dist/core/provenance.js.map +1 -1
- package/dist/core/records.js +33 -0
- package/dist/core/records.js.map +1 -0
- package/dist/core/repo.js +75 -1
- package/dist/core/repo.js.map +1 -1
- package/dist/core/report.js +9 -0
- package/dist/core/report.js.map +1 -1
- package/dist/core/results.js +1 -3
- package/dist/core/results.js.map +1 -1
- package/dist/core/spec.js +3 -3
- package/dist/core/spec.js.map +1 -1
- package/dist/core/staging.js +124 -17
- package/dist/core/staging.js.map +1 -1
- package/dist/core/status.js +122 -33
- package/dist/core/status.js.map +1 -1
- package/dist/core/steps.js +72 -0
- package/dist/core/steps.js.map +1 -0
- package/dist/core/verify.js +18 -4
- package/dist/core/verify.js.map +1 -1
- package/dist/core/version.js +17 -1
- package/dist/core/version.js.map +1 -1
- package/package.json +1 -1
package/dist/core/delta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/core/delta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Does the diff apply to the thing it claims to change?\n *\n * A requirement delta is a diff against a living spec, and nothing used to check\n * that it lands. Every failure mode here is silent today: MODIFIED of a\n * requirement that does not exist is merged as a creation, ADDED of one that\n * does exist REPLACES it (scenarios and all) while the author believes they are\n * adding, and a heading that nearly matches the delta grammar parses as plain\n * prose so archive merges nothing at all and says nothing about it.\n *\n * The last of those has a legal-looking cousin: a requirement under an ordinary\n * prose heading (`## Behavior`, `## Error Handling`) is BASE too, and BASE never\n * merges. Upstream OpenSpec deltas are written that way, so the answer is to name\n * what will be lost rather than to start merging prose sections.\n *\n * These run inside the archive gate, because the merge is where the damage lands.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Issue } from \"./issue.js\";\nimport {\n featureSpecPaths,\n featureSpecServices,\n listFeatures,\n servicePaths,\n SPEC_AXES,\n type SpecAxis,\n} from \"./repo.js\";\nimport {\n KIND_RE,\n REQUIREMENT_DIGEST_LENGTH,\n REQUIREMENT_DIGEST_RE,\n basedOnDeclarations,\n isRequirementsHeading,\n parseRequirements,\n requirementDigest,\n requirementIdProblems,\n sectionHeadings,\n type Requirement,\n} from \"./spec.js\";\nimport type { FleetContext } from \"./fleet-context.js\";\n\n/**\n * `## <word> Requirement(s)` — the shape an author reaches for. Anything matching\n * this but not the real grammar is a near miss: `## ADDED Requirement` (singular),\n * `## NEW Requirements`, `## ADDED Requirements:`. A bare `## Requirements` is not\n * a near miss — quoting the living state inside a delta is legal.\n */\nconst NEAR_SECTION_RE = /^##\\s+[A-Za-z]+\\s+Requirements?\\s*:?\\s*$/i;\n\n/**\n * OpenSpec's fourth delta operation. NEAR_SECTION_RE already catches it, but the\n * generic advice would mislead: a RENAMED body is FROM/TO backtick bullets, not\n * `### Requirement:` headings, so it parses to zero requirements and even the\n * whole-file check below has nothing to count — the heading is the only place to\n * stop it. Rename semantics stay unimplemented (the corpus never uses them); the\n * error says what to write instead.\n */\nconst RENAMED_SECTION_RE = /^##\\s+RENAMED\\s+Requirements?\\s*:?\\s*$/i;\n\n// The one non-delta heading whose requirements are SUPPOSED not to merge — a\n// delta quoting the living state for context — is `## Requirements`, matched by\n// the shared isRequirementsHeading (spec.ts): the same definition that fixes\n// archive's rewrite boundary, so \"legal to quote\" and \"where the merge writes\"\n// cannot drift apart. Every other heading — `## Behavior`, `## Error Handling`,\n// `## Notes` — leaves its requirements as BASE, which the merge skips, so the\n// author wrote a change that will never land.\n\n/** Key for the (service, axis, requirement) triple a feature claims. The two\n * axes are separate namespaces on purpose — an arch requirement cannot conflict\n * with a business requirement of the same name, they merge into different files. */\nfunction key(service: string, axis: SpecAxis, identity: string): string {\n return `${service}\\0${axis.file}\\0${identity}`;\n}\n\n/** Stable ID when present; exact heading for a legacy requirement. */\nfunction requirementKey(requirement: Pick<Requirement, \"id\" | \"name\">): string {\n return requirement.id === undefined ? `name:${requirement.name}` : `id:${requirement.id}`;\n}\n\nexport async function deltaShapeIssues(\n docsDir: string,\n featureDir: string,\n featureId: string,\n context?: FleetContext,\n): Promise<Issue[]> {\n const issues: Issue[] = [];\n const services = await featureSpecServices(featureDir, context);\n if (services.length === 0) return issues;\n\n // What OTHER features in flight claim. Only built when a claim has to be\n // checked — the common case never pays for the scan.\n let inFlight: ActiveClaims | null = null;\n const claimedElsewhere = async (\n which: keyof ActiveClaims,\n service: string,\n axis: SpecAxis,\n requirement: Pick<Requirement, \"id\" | \"name\">,\n ): Promise<string | undefined> => {\n inFlight ??= await activeClaims(docsDir, featureId, context);\n const map = inFlight[which];\n return map.get(key(service, axis, requirementKey(requirement)))\n ?? map.get(key(service, axis, `name:${requirement.name}`));\n };\n const addedElsewhere = (\n service: string,\n axis: SpecAxis,\n requirement: Pick<Requirement, \"id\" | \"name\">,\n ): Promise<string | undefined> => claimedElsewhere(\"added\", service, axis, requirement);\n\n // Both requirement-carrying files per service run the same checks — one code\n // path parameterized by filename, the merge's own factoring. `where` names\n // the arch file in messages so a finding cannot be chased into the wrong\n // document; spec.md keeps its historical spelling.\n for (const service of services) {\n for (const axis of SPEC_AXES) {\n const specPath = featureSpecPaths(featureDir, service)[axis.key];\n if (!existsSync(specPath)) continue;\n const raw = context === undefined\n ? await readFile(specPath, \"utf8\")\n : await context.readText(specPath);\n const where = axis.key === \"spec\" ? service : `${service} (arch.spec.md)`;\n\n for (const heading of sectionHeadings(raw)) {\n if (KIND_RE.test(heading.text) || !NEAR_SECTION_RE.test(heading.text)) continue;\n issues.push({\n severity: \"error\",\n code: \"delta.unknown-section\",\n subject: service,\n message: RENAMED_SECTION_RE.test(heading.text)\n ? `${where}: '${heading.text}' (line ${heading.line}) is OpenSpec rename syntax, which loam does not merge — carry one stable Requirement-ID through a MODIFIED requirement, or express a legacy rename as a REMOVED requirement plus an ADDED one; otherwise the rename happens to nothing`\n : `${where}: '${heading.text}' (line ${heading.line}) is not a delta section — use '## ADDED Requirements', '## MODIFIED Requirements' or '## REMOVED Requirements', or everything under it merges as nothing`,\n });\n }\n\n const reqs = context === undefined ? parseRequirements(raw) : await context.readRequirements(specPath);\n\n for (const problem of requirementIdProblems(reqs)) {\n if (problem.kind === \"invalid\") {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-invalid\",\n subject: service,\n message: `${where}: requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}' — use 1-128 characters matching [A-Za-z][A-Za-z0-9._-]*`,\n });\n } else if (problem.kind === \"repeated\") {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-repeated\",\n subject: service,\n message: `${where}: requirement '${problem.requirement}' declares Requirement-ID ${problem.values.length} times — identity must be declared exactly once`,\n });\n } else {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-duplicate\",\n subject: service,\n message: `${where}: Requirement-ID '${problem.id}' is shared by ${problem.requirements.map((name) => `'${name}'`).join(\", \")} — one ID may identify only one requirement`,\n });\n }\n }\n\n // The `Based-On:` pin as a DOCUMENT question, settled before anything is\n // compared against the living spec: a value that is not a digest, or two\n // of them on one requirement, is a pin nobody can evaluate — and a pin\n // that silently protects nothing is worse than no pin at all, because its\n // author believes they are covered. ADDED is refused for the same reason\n // rather than ignored: a requirement with no living version cannot have\n // been written against one, so the line is a misunderstanding today and a\n // digest-shaped lie on the day someone turns it into a MODIFIED.\n for (const r of reqs) {\n const declared = basedOnDeclarations(r);\n if (declared.length > 1) {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: requirement '${r.name}' declares Based-On ${declared.length} times — a delta is written against exactly one living version`,\n });\n continue;\n }\n const pin = declared[0];\n if (pin === undefined) continue;\n if (!REQUIREMENT_DIGEST_RE.test(pin)) {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: requirement '${r.name}' has invalid Based-On '${pin}' — expected ${REQUIREMENT_DIGEST_LENGTH} lowercase hex characters, as \\`loam rebase\\` writes them`,\n });\n } else if (r.kind === \"ADDED\") {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' carries Based-On '${pin}', but an added requirement has no living version to be based on — drop the line, or make it MODIFIED`,\n });\n }\n }\n\n // The whole-file version of the near-miss check, and the one that cannot be\n // dodged: NEAR_SECTION_RE only recognizes single-word misses (`## NEW\n // Requirements`), so `## NEWLY ADDED Requirements` — or requirements under a\n // prose heading, or under no heading at all — still parses everything as BASE\n // and archive merges nothing, silently. Same failure class as the BOM incident\n // (see spec.ts). A file that MIXES delta sections with quoted BASE requirements\n // is legal authoring; a file with requirements and no delta kind anywhere is a\n // delta that would do nothing, which cannot be what its author meant.\n if (reqs.length > 0 && reqs.every((r) => r.kind === \"BASE\")) {\n const rel = relative(docsDir, specPath).split(/[\\\\/]/).join(\"/\");\n issues.push({\n severity: \"error\",\n code: \"delta.no-delta-sections\",\n subject: service,\n message: `${service}: ${rel} contains ${reqs.length} requirement(s) but no '## ADDED|MODIFIED|REMOVED Requirements' section — this delta would merge nothing`,\n });\n }\n\n const livingPath = servicePaths(docsDir, service)[axis.key];\n const living = existsSync(livingPath)\n ? context === undefined\n ? parseRequirements(await readFile(livingPath, \"utf8\"))\n : await context.readRequirements(livingPath)\n : [];\n // How the axis's living document is named in messages — spec.md keeps the\n // historical \"living spec\", the arch axis says which file it means.\n const livingDoc = axis.key === \"spec\" ? \"living spec\" : \"living arch.spec.md\";\n const livingNames = new Set(living.map((r) => r.name));\n const livingByName = new Map<string, Requirement[]>();\n const livingById = new Map<string, Requirement[]>();\n for (const requirement of living) {\n const named = livingByName.get(requirement.name) ?? [];\n named.push(requirement);\n livingByName.set(requirement.name, named);\n if (requirement.id !== undefined) {\n const identified = livingById.get(requirement.id) ?? [];\n identified.push(requirement);\n livingById.set(requirement.id, identified);\n }\n }\n for (const problem of requirementIdProblems(living)) {\n issues.push({\n severity: \"error\",\n code: \"delta.living-requirement-id-invalid\",\n subject: service,\n message:\n problem.kind === \"invalid\"\n ? `${where}: the ${livingDoc} requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}', so this delta cannot select it safely`\n : problem.kind === \"repeated\"\n ? `${where}: the ${livingDoc} requirement '${problem.requirement}' declares Requirement-ID more than once, so this delta cannot select it safely`\n : `${where}: the ${livingDoc} shares Requirement-ID '${problem.id}' across ${problem.requirements.map((name) => `'${name}'`).join(\", \")}, so this delta cannot select one safely`,\n });\n }\n // Two living requirements under one heading make the delta algebra\n // disagree with itself on the SAME input: MODIFIED replaces the first\n // match (findIndex) while REMOVED deletes every match (filter). So one\n // delta edits one of the twins and leaves the other, and the next one\n // deletes both — and neither outcome is what an author reading the\n // living document could have predicted. Gating rather than advisory,\n // because there is no reading of the delta that makes the merge correct;\n // the fix is in the living document, and it is one rename.\n for (const [name, twins] of livingByName) {\n if (twins.length < 2) continue;\n issues.push({\n severity: \"error\",\n code: \"delta.living-duplicate-requirement\",\n subject: service,\n message: `${where}: the ${livingDoc} declares ${twins.length} requirements named '${name}' — MODIFIED would rewrite only the first and REMOVED would delete both, so no delta applies to it predictably. Give them distinct names (or distinct Requirement-IDs and headings) first.`,\n });\n }\n\n // Lowercased living name -> its exact spelling, for the near-duplicate warning.\n const livingFolded = new Map(living.map((r) => [r.name.toLowerCase(), r.name] as const));\n\n /**\n * The pin, against the living requirement this delta actually selects.\n *\n * This is the check `delta.modified-conflict` cannot be: that one names\n * the other feature while BOTH are in flight, and the collision that\n * happens on a fleet outlives the window. A archives, leaves `features/`,\n * and stops being an active claim; B revalidates GREEN, and its MODIFIED —\n * carrying the full text it wrote against the pre-A document — replaces\n * A's requirement wholesale, scenarios and all, with `+0 ~1 -0` and exit\n * 0. Nothing downstream can even detect it afterwards: `unarchive A`\n * refuses `snapshot-stale`, and `--force` takes B out with it.\n *\n * A digest of what the author read closes that by not depending on\n * timing at all. Stale is an ERROR and gates, because there is no reading\n * of a stale delta under which the merge is what its author meant.\n * Missing is a warning and does not: a delta adopted from OpenSpec never\n * had the line, and refusing to archive an entire migrated corpus is not\n * a safety property, it is an outage.\n */\n const checkBaseline = (r: Requirement, selected: Requirement): void => {\n // A pin the document pass already refused is not also stale: that would\n // send its author to `loam rebase` for a problem rebase does not fix.\n const declared = basedOnDeclarations(r);\n if (declared.length > 1) return;\n if (r.basedOn !== undefined && !REQUIREMENT_DIGEST_RE.test(r.basedOn)) return;\n const current = requirementDigest(selected);\n if (r.basedOn === undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.baseline-missing\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' carries no Based-On, so nothing can say whether the living text moved since this delta was written. Run \\`loam rebase ${featureId}\\` to pin it (Based-On: ${current}).`,\n });\n return;\n }\n if (r.basedOn === current) return;\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-stale\",\n subject: service,\n message:\n `${where}: ${r.kind} requirement '${r.name}' was written against living version ${r.basedOn}, but the ${livingDoc} now holds ${current} — someone landed a change to it in between. ` +\n (r.kind === \"MODIFIED\"\n ? `This MODIFIED carries its FULL new text, so merging it would replace theirs outright.`\n : `Merging this REMOVED would delete what they landed.`) +\n ` Re-read the living requirement, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n };\n\n for (const r of reqs) {\n // BASE in a delta file means the requirement is under no delta section, so\n // the merge skips it. Reported rather than merged: deciding that `## Behavior`\n // means ADDED would be archive guessing at intent, and it would guess wrong\n // for every prose section that quotes a requirement as documentation. A\n // requirement above every heading has no heading to name, and no claim to be\n // written in the delta grammar at all — same judgement as `## Requirements`.\n //\n // A warning that GATES archive — the one check where the two axes\n // diverge. Severity says whether the document is valid: this shape is\n // legal OpenSpec, so an error would fail `loam validate` on every\n // adopted repo whose active deltas still use it. Gating says whether\n // the merge is safe: it is not — the merge would silently drop authored\n // content, the exact loss this module exists to prevent — so archive\n // refuses, and `--approve` remains the way to say the loss is meant.\n if (r.kind === \"BASE\") {\n if (r.section !== undefined && !isRequirementsHeading(r.section)) {\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"delta.requirement-not-merged\",\n subject: service,\n message: `${where}: requirement '${r.name}' sits under '${r.section}', which is not a delta section — archive will NOT merge it. Move it under '## ADDED Requirements' (or MODIFIED/REMOVED), or drop it if it is documentation.`,\n });\n }\n continue;\n }\n\n if (r.kind === \"ADDED\") {\n const idMatches = r.id === undefined ? [] : (livingById.get(r.id) ?? []);\n const nameMatches = livingByName.get(r.name) ?? [];\n const nameSelectsOther = r.id !== undefined && nameMatches.some((candidate) => candidate.id !== r.id);\n if (nameSelectsOther) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' carries Requirement-ID '${r.id}', but that heading already identifies a different living requirement — ID and name cannot select different identities`,\n });\n continue;\n }\n if ((r.id === undefined && livingNames.has(r.name)) || idMatches.length > 0) {\n issues.push({\n severity: \"error\",\n code: \"delta.added-duplicate\",\n subject: service,\n message: r.id === undefined\n ? `${where}: ADDED requirement '${r.name}' already exists in the ${livingDoc} — the merge would REPLACE it, scenarios and all. Use MODIFIED, or rename.`\n : `${where}: ADDED requirement '${r.name}' uses Requirement-ID '${r.id}', which already exists in the ${livingDoc} — use MODIFIED to change that identity`,\n });\n continue;\n }\n // Merge identity is exact string equality (applyRequirementDelta), so a\n // name that differs only in case slips past the duplicate check above and\n // lands as a SECOND requirement next to the living one. A warning, not an\n // error: the author may genuinely mean a distinct requirement, but with\n // LLM authors the case drift is the likely story. Never fires together\n // with added-duplicate — an exact match continues before this runs.\n const near = livingFolded.get(r.name.toLowerCase());\n if (near !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-near-duplicate\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' differs only in case from living requirement '${near}' — the merge matches names exactly, so both would coexist. Match the living spelling and use MODIFIED, or pick a distinct name.`,\n });\n }\n const other = await addedElsewhere(service, axis, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-conflict\",\n subject: service,\n message: `${where}: requirement '${r.name}' is also added by ${other} — whichever archives first lands it, and the second archive is refused (delta.added-duplicate) unless --approve`,\n });\n }\n continue;\n }\n\n // MODIFIED vs MODIFIED on ONE living requirement. The ADDED case has\n // been caught since delta.added-conflict: two features adding the same\n // name collide loudly, because the second archive is refused. Two\n // features CHANGING the same requirement collide in total silence —\n // both deltas apply cleanly, and whichever archives second replaces\n // the first's text, scenarios and all, with a version written against\n // a document that no longer exists. A warning, not a gate: the second\n // author may well be rewriting on purpose, and only they can say.\n const alsoChanged = await claimedElsewhere(\"changed\", service, axis, r);\n if (alsoChanged !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.modified-conflict\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' is also changed by ${alsoChanged} — both deltas apply cleanly, so whichever archives second REPLACES the other's text wholesale. Agree on one owner, or fold the two changes together.`,\n });\n }\n\n if (r.id !== undefined) {\n const idMatches = livingById.get(r.id) ?? [];\n const nameMatches = livingByName.get(r.name) ?? [];\n const nameSelectsOther = nameMatches.some((candidate) => candidate.id !== r.id);\n if (nameSelectsOther || (idMatches.length === 0 && nameMatches.length > 0)) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' carries Requirement-ID '${r.id}', but its ID and heading select different living requirements — fix the ID or heading; archive will not guess`,\n });\n continue;\n }\n // A differing heading is the explicit loam rename mechanism: the\n // stable ID selects the old requirement, MODIFIED supplies its new name.\n if (idMatches.length === 1) {\n checkBaseline(r, idMatches[0]!);\n continue;\n }\n } else if (livingNames.has(r.name)) {\n // Twins under one heading are already refused\n // (`delta.living-duplicate-requirement`), and applyRequirementDelta\n // takes the first either way — so the pin is compared against the\n // same requirement the merge would rewrite.\n checkBaseline(r, livingByName.get(r.name)![0]!);\n continue;\n }\n\n const other = await addedElsewhere(service, axis, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-pending\" : \"delta.removed-pending\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' is not in the ${livingDoc} yet — ${other} introduces it. Archive ${other} first.`,\n });\n continue;\n }\n issues.push({\n severity: \"error\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-unknown\" : \"delta.removed-unknown\",\n subject: service,\n message:\n r.kind === \"MODIFIED\"\n ? `${where}: MODIFIED requirement '${r.name}' does not exist in the ${livingDoc} — the merge would create it. Did you mean ADDED?`\n : `${where}: REMOVED requirement '${r.name}' does not exist in the ${livingDoc} — there is nothing to remove`,\n });\n }\n }\n }\n\n return issues;\n}\n\n/**\n * What other ACTIVE features claim, per (service, axis, requirement): what they\n * ADD, and — separately — what they CHANGE or REMOVE. The two are separate\n * indexes because they mean opposite things to the reader: an addition\n * elsewhere explains why a MODIFIED target is missing today, a change elsewhere\n * warns that the target is contested. One scan builds both; scanning twice\n * would double the fleet walk for the same bytes.\n */\ninterface ActiveClaims {\n added: Map<string, string>;\n changed: Map<string, string>;\n}\n\nasync function activeClaims(\n docsDir: string,\n exclude: string,\n context?: FleetContext,\n): Promise<ActiveClaims> {\n const added = new Map<string, string>();\n const changed = new Map<string, string>();\n for (const feature of await listFeatures(docsDir, {}, context)) {\n if (feature.id === exclude) continue;\n for (const service of feature.services) {\n for (const axis of SPEC_AXES) {\n const path = featureSpecPaths(feature.dir, service)[axis.key];\n if (!existsSync(path)) continue;\n const reqs = context === undefined\n ? parseRequirements(await readFile(path, \"utf8\"))\n : await context.readRequirements(path);\n for (const r of reqs) {\n const map = r.kind === \"ADDED\" ? added : r.kind === \"MODIFIED\" || r.kind === \"REMOVED\" ? changed : null;\n if (map === null) continue;\n for (const identity of new Set([requirementKey(r), `name:${r.name}`])) {\n const k = key(service, axis, identity);\n if (!map.has(k)) map.set(k, feature.id);\n }\n }\n }\n }\n }\n return { added, changed };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/core/delta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Does the diff apply to the thing it claims to change?\n *\n * A requirement delta is a diff against a living spec, and nothing used to check\n * that it lands. Every failure mode here is silent today: MODIFIED of a\n * requirement that does not exist is merged as a creation, ADDED of one that\n * does exist REPLACES it (scenarios and all) while the author believes they are\n * adding, and a heading that nearly matches the delta grammar parses as plain\n * prose so archive merges nothing at all and says nothing about it.\n *\n * The last of those has a legal-looking cousin: a requirement under an ordinary\n * prose heading (`## Behavior`, `## Error Handling`) is BASE too, and BASE never\n * merges. Upstream OpenSpec deltas are written that way, so the answer is to name\n * what will be lost rather than to start merging prose sections.\n *\n * These run inside the archive gate, because the merge is where the damage lands.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Issue } from \"./issue.js\";\nimport {\n featureSpecPaths,\n featureSpecServices,\n listFeatures,\n servicePaths,\n SPEC_AXES,\n type SpecAxis,\n} from \"./repo.js\";\nimport {\n KIND_RE,\n REQUIREMENT_DIGEST_LENGTH,\n REQUIREMENT_DIGEST_RE,\n basedOnDeclarations,\n isRequirementsHeading,\n parseRequirements,\n requirementDigest,\n requirementIdProblems,\n sectionHeadings,\n type Requirement,\n} from \"./spec.js\";\nimport type { FleetContext } from \"./fleet-context.js\";\n\n/**\n * `## <word> Requirement(s)` — the shape an author reaches for. Anything matching\n * this but not the real grammar is a near miss: `## ADDED Requirement` (singular),\n * `## NEW Requirements`, `## ADDED Requirements:`. A bare `## Requirements` is not\n * a near miss — quoting the living state inside a delta is legal.\n */\nconst NEAR_SECTION_RE = /^##\\s+[A-Za-z]+\\s+Requirements?\\s*:?\\s*$/i;\n\n/**\n * OpenSpec's fourth delta operation. NEAR_SECTION_RE already catches it, but the\n * generic advice would mislead: a RENAMED body is FROM/TO backtick bullets, not\n * `### Requirement:` headings, so it parses to zero requirements and even the\n * whole-file check below has nothing to count — the heading is the only place to\n * stop it. Rename semantics stay unimplemented (the corpus never uses them); the\n * error says what to write instead.\n */\nconst RENAMED_SECTION_RE = /^##\\s+RENAMED\\s+Requirements?\\s*:?\\s*$/i;\n\n// The one non-delta heading whose requirements are SUPPOSED not to merge — a\n// delta quoting the living state for context — is `## Requirements`, matched by\n// the shared isRequirementsHeading (spec.ts): the same definition that fixes\n// archive's rewrite boundary, so \"legal to quote\" and \"where the merge writes\"\n// cannot drift apart. Every other heading — `## Behavior`, `## Error Handling`,\n// `## Notes` — leaves its requirements as BASE, which the merge skips, so the\n// author wrote a change that will never land.\n\n/** Key for the (service, axis, requirement) triple a feature claims. The two\n * axes are separate namespaces on purpose — an arch requirement cannot conflict\n * with a business requirement of the same name, they merge into different files. */\nfunction key(service: string, axis: SpecAxis, identity: string): string {\n return `${service}\\0${axis.file}\\0${identity}`;\n}\n\n/** Stable ID when present; exact heading for a legacy requirement. */\nfunction requirementKey(requirement: Pick<Requirement, \"id\" | \"name\">): string {\n return requirement.id === undefined ? `name:${requirement.name}` : `id:${requirement.id}`;\n}\n\nexport async function deltaShapeIssues(\n docsDir: string,\n featureDir: string,\n featureId: string,\n context?: FleetContext,\n): Promise<Issue[]> {\n const issues: Issue[] = [];\n const services = await featureSpecServices(featureDir, context);\n if (services.length === 0) return issues;\n\n // What OTHER features in flight claim. Only built when a claim has to be\n // checked — the common case never pays for the scan.\n let inFlight: ActiveClaims | null = null;\n const claimedElsewhere = async (\n which: keyof ActiveClaims,\n service: string,\n axis: SpecAxis,\n requirement: Pick<Requirement, \"id\" | \"name\">,\n ): Promise<string | undefined> => {\n inFlight ??= await activeClaims(docsDir, featureId, context);\n const map = inFlight[which];\n return map.get(key(service, axis, requirementKey(requirement)))\n ?? map.get(key(service, axis, `name:${requirement.name}`));\n };\n const addedElsewhere = (\n service: string,\n axis: SpecAxis,\n requirement: Pick<Requirement, \"id\" | \"name\">,\n ): Promise<string | undefined> => claimedElsewhere(\"added\", service, axis, requirement);\n\n // Both requirement-carrying files per service run the same checks — one code\n // path parameterized by filename, the merge's own factoring. `where` names\n // the arch file in messages so a finding cannot be chased into the wrong\n // document; spec.md keeps its historical spelling.\n for (const service of services) {\n for (const axis of SPEC_AXES) {\n const specPath = featureSpecPaths(featureDir, service)[axis.key];\n if (!existsSync(specPath)) continue;\n const raw = context === undefined\n ? await readFile(specPath, \"utf8\")\n : await context.readText(specPath);\n const where = axis.key === \"spec\" ? service : `${service} (arch.spec.md)`;\n\n for (const heading of sectionHeadings(raw)) {\n if (KIND_RE.test(heading.text) || !NEAR_SECTION_RE.test(heading.text)) continue;\n issues.push({\n severity: \"error\",\n code: \"delta.unknown-section\",\n subject: service,\n message: RENAMED_SECTION_RE.test(heading.text)\n ? `${where}: '${heading.text}' (line ${heading.line}) is OpenSpec rename syntax, which loam does not merge — carry one stable Requirement-ID through a MODIFIED requirement, or express a legacy rename as a REMOVED requirement plus an ADDED one; otherwise the rename happens to nothing`\n : `${where}: '${heading.text}' (line ${heading.line}) is not a delta section — use '## ADDED Requirements', '## MODIFIED Requirements' or '## REMOVED Requirements', or everything under it merges as nothing`,\n });\n }\n\n const reqs = context === undefined ? parseRequirements(raw) : await context.readRequirements(specPath);\n\n for (const problem of requirementIdProblems(reqs)) {\n if (problem.kind === \"invalid\") {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-invalid\",\n subject: service,\n message: `${where}: requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}' — use 1-128 characters matching [A-Za-z][A-Za-z0-9._-]*`,\n });\n } else if (problem.kind === \"repeated\") {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-repeated\",\n subject: service,\n message: `${where}: requirement '${problem.requirement}' declares Requirement-ID ${problem.values.length} times — identity must be declared exactly once`,\n });\n } else {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-id-duplicate\",\n subject: service,\n message: `${where}: Requirement-ID '${problem.id}' is shared by ${problem.requirements.map((name) => `'${name}'`).join(\", \")} — one ID may identify only one requirement`,\n });\n }\n }\n\n // The `Based-On:` pin as a DOCUMENT question, settled before anything is\n // compared against the living spec: a value that is not a digest, or two\n // of them on one requirement, is a pin nobody can evaluate — and a pin\n // that silently protects nothing is worse than no pin at all, because its\n // author believes they are covered. ADDED is refused for the same reason\n // rather than ignored: a requirement with no living version cannot have\n // been written against one, so the line is a misunderstanding today and a\n // digest-shaped lie on the day someone turns it into a MODIFIED.\n for (const r of reqs) {\n const declared = basedOnDeclarations(r);\n if (declared.length > 1) {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: requirement '${r.name}' declares Based-On ${declared.length} times — a delta is written against exactly one living version`,\n });\n continue;\n }\n const pin = declared[0];\n if (pin === undefined) continue;\n if (!REQUIREMENT_DIGEST_RE.test(pin)) {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: requirement '${r.name}' has invalid Based-On '${pin}' — expected ${REQUIREMENT_DIGEST_LENGTH} lowercase hex characters, as \\`loam rebase\\` writes them`,\n });\n } else if (r.kind === \"ADDED\") {\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-invalid\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' carries Based-On '${pin}', but an added requirement has no living version to be based on — drop the line, or make it MODIFIED`,\n });\n }\n }\n\n // The whole-file version of the near-miss check, and the one that cannot be\n // dodged: NEAR_SECTION_RE only recognizes single-word misses (`## NEW\n // Requirements`), so `## NEWLY ADDED Requirements` — or requirements under a\n // prose heading, or under no heading at all — still parses everything as BASE\n // and archive merges nothing, silently. Same failure class as the BOM incident\n // (see spec.ts). A file that MIXES delta sections with quoted BASE requirements\n // is legal authoring; a file with requirements and no delta kind anywhere is a\n // delta that would do nothing, which cannot be what its author meant.\n if (reqs.length > 0 && reqs.every((r) => r.kind === \"BASE\")) {\n const rel = relative(docsDir, specPath).split(/[\\\\/]/).join(\"/\");\n issues.push({\n severity: \"error\",\n code: \"delta.no-delta-sections\",\n subject: service,\n message: `${service}: ${rel} contains ${reqs.length} requirement(s) but no '## ADDED|MODIFIED|REMOVED Requirements' section — this delta would merge nothing`,\n });\n }\n\n const livingPath = servicePaths(docsDir, service)[axis.key];\n const living = existsSync(livingPath)\n ? context === undefined\n ? parseRequirements(await readFile(livingPath, \"utf8\"))\n : await context.readRequirements(livingPath)\n : [];\n // How the axis's living document is named in messages — spec.md keeps the\n // historical \"living spec\", the arch axis says which file it means.\n const livingDoc = axis.key === \"spec\" ? \"living spec\" : \"living arch.spec.md\";\n const livingNames = new Set(living.map((r) => r.name));\n const livingByName = new Map<string, Requirement[]>();\n const livingById = new Map<string, Requirement[]>();\n for (const requirement of living) {\n const named = livingByName.get(requirement.name) ?? [];\n named.push(requirement);\n livingByName.set(requirement.name, named);\n if (requirement.id !== undefined) {\n const identified = livingById.get(requirement.id) ?? [];\n identified.push(requirement);\n livingById.set(requirement.id, identified);\n }\n }\n for (const problem of requirementIdProblems(living)) {\n issues.push({\n severity: \"error\",\n code: \"delta.living-requirement-id-invalid\",\n subject: service,\n message:\n problem.kind === \"invalid\"\n ? `${where}: the ${livingDoc} requirement '${problem.requirement}' has invalid Requirement-ID '${problem.value}', so this delta cannot select it safely`\n : problem.kind === \"repeated\"\n ? `${where}: the ${livingDoc} requirement '${problem.requirement}' declares Requirement-ID more than once, so this delta cannot select it safely`\n : `${where}: the ${livingDoc} shares Requirement-ID '${problem.id}' across ${problem.requirements.map((name) => `'${name}'`).join(\", \")}, so this delta cannot select one safely`,\n });\n }\n // Two living requirements under one heading make the delta algebra\n // disagree with itself on the SAME input: MODIFIED replaces the first\n // match (findIndex) while REMOVED deletes every match (filter). So one\n // delta edits one of the twins and leaves the other, and the next one\n // deletes both — and neither outcome is what an author reading the\n // living document could have predicted. Gating rather than advisory,\n // because there is no reading of the delta that makes the merge correct;\n // the fix is in the living document, and it is one rename.\n for (const [name, twins] of livingByName) {\n if (twins.length < 2) continue;\n issues.push({\n severity: \"error\",\n code: \"delta.living-duplicate-requirement\",\n subject: service,\n message: `${where}: the ${livingDoc} declares ${twins.length} requirements named '${name}' — MODIFIED would rewrite only the first and REMOVED would delete both, so no delta applies to it predictably. Give them distinct names (or distinct Requirement-IDs and headings) first.`,\n });\n }\n\n // Lowercased living name -> its exact spelling, for the near-duplicate warning.\n const livingFolded = new Map(living.map((r) => [r.name.toLowerCase(), r.name] as const));\n\n /**\n * The pin, against the living requirement this delta actually selects.\n *\n * This is the check `delta.modified-conflict` cannot be: that one names\n * the other feature while BOTH are in flight, and the collision that\n * happens on a fleet outlives the window. A archives, leaves `features/`,\n * and stops being an active claim; B revalidates GREEN, and its MODIFIED —\n * carrying the full text it wrote against the pre-A document — replaces\n * A's requirement wholesale, scenarios and all, with `+0 ~1 -0` and exit\n * 0. Nothing downstream can even detect it afterwards: `unarchive A`\n * refuses `snapshot-stale`, and `--force` takes B out with it.\n *\n * A digest of what the author read closes that by not depending on\n * timing at all. Stale is an ERROR and gates, because there is no reading\n * of a stale delta under which the merge is what its author meant.\n * Missing is a warning and does not: a delta adopted from OpenSpec never\n * had the line, and refusing to archive an entire migrated corpus is not\n * a safety property, it is an outage.\n */\n const checkBaseline = (r: Requirement, selected: Requirement): void => {\n // A pin the document pass already refused is not also stale: that would\n // send its author to `loam rebase` for a problem rebase does not fix.\n const declared = basedOnDeclarations(r);\n if (declared.length > 1) return;\n if (r.basedOn !== undefined && !REQUIREMENT_DIGEST_RE.test(r.basedOn)) return;\n const current = requirementDigest(selected);\n if (r.basedOn === undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.baseline-missing\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' carries no Based-On, so nothing can say whether the living text moved since this delta was written. Run \\`loam rebase ${featureId}\\` to pin it (Based-On: ${current}).`,\n });\n return;\n }\n if (r.basedOn === current) return;\n issues.push({\n severity: \"error\",\n code: \"delta.baseline-stale\",\n subject: service,\n message:\n `${where}: ${r.kind} requirement '${r.name}' was written against living version ${r.basedOn}, but the ${livingDoc} now holds ${current} — someone landed a change to it in between. ` +\n (r.kind === \"MODIFIED\"\n ? `This MODIFIED carries its FULL new text, so merging it would replace theirs outright.`\n : `Merging this REMOVED would delete what they landed.`) +\n ` Re-read the living requirement, fold in what you still mean, then run \\`loam rebase ${featureId}\\`.`,\n });\n };\n\n for (const r of reqs) {\n // BASE in a delta file means the requirement is under no delta section, so\n // the merge skips it. Reported rather than merged: deciding that `## Behavior`\n // means ADDED would be archive guessing at intent, and it would guess wrong\n // for every prose section that quotes a requirement as documentation. A\n // requirement above every heading has no heading to name, and no claim to be\n // written in the delta grammar at all — same judgement as `## Requirements`.\n //\n // A warning that GATES archive — the one check where the two axes\n // diverge. Severity says whether the document is valid: this shape is\n // legal OpenSpec, so an error would fail `loam validate` on every\n // adopted repo whose active deltas still use it. Gating says whether\n // the merge is safe: it is not — the merge would silently drop authored\n // content, the exact loss this module exists to prevent — so archive\n // refuses, and `--approve` remains the way to say the loss is meant.\n if (r.kind === \"BASE\") {\n if (r.section !== undefined && !isRequirementsHeading(r.section)) {\n issues.push({\n severity: \"warn\",\n gates: true,\n code: \"delta.requirement-not-merged\",\n subject: service,\n message: `${where}: requirement '${r.name}' sits under '${r.section}', which is not a delta section — archive will NOT merge it. Move it under '## ADDED Requirements' (or MODIFIED/REMOVED), or drop it if it is documentation.`,\n });\n }\n continue;\n }\n\n if (r.kind === \"ADDED\") {\n const idMatches = r.id === undefined ? [] : (livingById.get(r.id) ?? []);\n const nameMatches = livingByName.get(r.name) ?? [];\n const nameSelectsOther = r.id !== undefined && nameMatches.some((candidate) => candidate.id !== r.id);\n if (nameSelectsOther) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' carries Requirement-ID '${r.id}', but that heading already identifies a different living requirement — ID and name cannot select different identities`,\n });\n continue;\n }\n if ((r.id === undefined && livingNames.has(r.name)) || idMatches.length > 0) {\n issues.push({\n severity: \"error\",\n code: \"delta.added-duplicate\",\n subject: service,\n message: r.id === undefined\n ? `${where}: ADDED requirement '${r.name}' already exists in the ${livingDoc} — the merge would REPLACE it, scenarios and all. Use MODIFIED, or rename.`\n : `${where}: ADDED requirement '${r.name}' uses Requirement-ID '${r.id}', which already exists in the ${livingDoc} — use MODIFIED to change that identity`,\n });\n continue;\n }\n // Merge identity is exact string equality (applyRequirementDelta), so a\n // name that differs only in case slips past the duplicate check above and\n // lands as a SECOND requirement next to the living one. A warning, not an\n // error: the author may genuinely mean a distinct requirement, but with\n // LLM authors the case drift is the likely story. Never fires together\n // with added-duplicate — an exact match continues before this runs.\n const near = livingFolded.get(r.name.toLowerCase());\n if (near !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-near-duplicate\",\n subject: service,\n message: `${where}: ADDED requirement '${r.name}' differs only in case from living requirement '${near}' — the merge matches names exactly, so both would coexist. Match the living spelling and use MODIFIED, or pick a distinct name.`,\n });\n }\n const other = await addedElsewhere(service, axis, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.added-conflict\",\n subject: service,\n message: `${where}: requirement '${r.name}' is also added by ${other} — whichever archives first lands it, and the second archive is refused (delta.added-duplicate) unless --approve`,\n });\n }\n continue;\n }\n\n // MODIFIED vs MODIFIED on ONE living requirement. The ADDED case has\n // been caught since delta.added-conflict: two features adding the same\n // name collide loudly, because the second archive is refused. Two\n // features CHANGING the same requirement collide in total silence —\n // both deltas apply cleanly, and whichever archives second replaces\n // the first's text, scenarios and all, with a version written against\n // a document that no longer exists. A warning, not a gate: the second\n // author may well be rewriting on purpose, and only they can say.\n const alsoChanged = await claimedElsewhere(\"changed\", service, axis, r);\n if (alsoChanged !== undefined) {\n issues.push({\n severity: \"warn\",\n code: \"delta.modified-conflict\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' is also changed by ${alsoChanged} — both deltas apply cleanly, so whichever archives second REPLACES the other's text wholesale. Agree on one owner, or fold the two changes together.`,\n });\n }\n\n if (r.id !== undefined) {\n const idMatches = livingById.get(r.id) ?? [];\n const nameMatches = livingByName.get(r.name) ?? [];\n const nameSelectsOther = nameMatches.some((candidate) => candidate.id !== r.id);\n // The second disjunct is defensive rather than reachable: a living\n // requirement under this heading that the ID did NOT select must carry\n // some other ID, which is what `nameSelectsOther` already says. It is\n // kept because it states the rule the reader is being told — ID and\n // heading must select the same identity — without depending on that\n // derivation holding after the next edit.\n if (nameSelectsOther || (idMatches.length === 0 && nameMatches.length > 0)) {\n issues.push({\n severity: \"error\",\n code: \"delta.requirement-identity-collision\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' carries Requirement-ID '${r.id}', but its ID and heading select different living requirements — fix the ID or heading; archive will not guess`,\n });\n continue;\n }\n // A differing heading is the explicit loam rename mechanism: the\n // stable ID selects the old requirement, MODIFIED supplies its new name.\n //\n // Total in the ID, deliberately: an ID that matches SEVERAL living\n // requirements has already been refused as\n // `delta.living-requirement-id-invalid` (severity error, so archive is\n // gated either way), and the living document is where it gets fixed.\n // Falling through instead added `delta.modified-unknown` — \"does not\n // exist … Did you mean ADDED?\" — beside it, which is both false and\n // the more actionable-sounding of the two, and it sends the author to\n // edit the delta over a problem that is not in the delta. There is no\n // baseline to check against an ambiguous selection, so the pin is\n // simply not graded until the ambiguity is gone.\n if (idMatches.length > 0) {\n const selected = idMatches.length === 1 ? idMatches[0] : undefined;\n if (selected !== undefined) checkBaseline(r, selected);\n continue;\n }\n } else if (livingNames.has(r.name)) {\n // Twins under one heading are already refused\n // (`delta.living-duplicate-requirement`), and applyRequirementDelta\n // takes the first either way — so the pin is compared against the\n // same requirement the merge would rewrite.\n checkBaseline(r, livingByName.get(r.name)![0]!);\n continue;\n }\n\n const other = await addedElsewhere(service, axis, r);\n if (other !== undefined) {\n issues.push({\n severity: \"warn\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-pending\" : \"delta.removed-pending\",\n subject: service,\n message: `${where}: ${r.kind} requirement '${r.name}' is not in the ${livingDoc} yet — ${other} introduces it. Archive ${other} first.`,\n });\n continue;\n }\n issues.push({\n severity: \"error\",\n code: r.kind === \"MODIFIED\" ? \"delta.modified-unknown\" : \"delta.removed-unknown\",\n subject: service,\n message:\n r.kind === \"MODIFIED\"\n ? `${where}: MODIFIED requirement '${r.name}' does not exist in the ${livingDoc} — the merge would create it. Did you mean ADDED?`\n : `${where}: REMOVED requirement '${r.name}' does not exist in the ${livingDoc} — there is nothing to remove`,\n });\n }\n }\n }\n\n return issues;\n}\n\n/**\n * What other ACTIVE features claim, per (service, axis, requirement): what they\n * ADD, and — separately — what they CHANGE or REMOVE. The two are separate\n * indexes because they mean opposite things to the reader: an addition\n * elsewhere explains why a MODIFIED target is missing today, a change elsewhere\n * warns that the target is contested. One scan builds both; scanning twice\n * would double the fleet walk for the same bytes.\n */\ninterface ActiveClaims {\n added: Map<string, string>;\n changed: Map<string, string>;\n}\n\nasync function activeClaims(\n docsDir: string,\n exclude: string,\n context?: FleetContext,\n): Promise<ActiveClaims> {\n const added = new Map<string, string>();\n const changed = new Map<string, string>();\n for (const feature of await listFeatures(docsDir, {}, context)) {\n if (feature.id === exclude) continue;\n for (const service of feature.services) {\n for (const axis of SPEC_AXES) {\n const path = featureSpecPaths(feature.dir, service)[axis.key];\n if (!existsSync(path)) continue;\n const reqs = context === undefined\n ? parseRequirements(await readFile(path, \"utf8\"))\n : await context.readRequirements(path);\n for (const r of reqs) {\n const map = r.kind === \"ADDED\" ? added : r.kind === \"MODIFIED\" || r.kind === \"REMOVED\" ? changed : null;\n if (map === null) continue;\n for (const identity of new Set([requirementKey(r), `name:${r.name}`])) {\n const k = key(service, axis, identity);\n if (!map.has(k)) map.set(k, feature.id);\n }\n }\n }\n }\n }\n return { added, changed };\n}\n"]}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* it introduces an identity or operation the latter already refers to.
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
+
import { inOrder } from "./concurrency.js";
|
|
10
11
|
import { FleetContext } from "./fleet-context.js";
|
|
11
|
-
import {
|
|
12
|
+
import { serviceResolver } from "./likec4.js";
|
|
12
13
|
import { operations } from "./openapi.js";
|
|
13
14
|
import { SPEC_AXES, compareIds, featurePaths, featureSpecPaths, listFeatures, servicePaths, } from "./repo.js";
|
|
14
15
|
function reqKeys(requirement) {
|
|
@@ -17,15 +18,15 @@ function reqKeys(requirement) {
|
|
|
17
18
|
`name:${requirement.name}`,
|
|
18
19
|
];
|
|
19
20
|
}
|
|
20
|
-
function reqIndexKey(
|
|
21
|
-
return `${service}\0${axis}\0${identity}`;
|
|
21
|
+
function reqIndexKey(at) {
|
|
22
|
+
return `${at.service}\0${at.axis}\0${at.identity}`;
|
|
22
23
|
}
|
|
23
|
-
function opIndexKey(
|
|
24
|
-
return `${service}\0${operationId}`;
|
|
24
|
+
function opIndexKey(at) {
|
|
25
|
+
return `${at.service}\0${at.operationId}`;
|
|
25
26
|
}
|
|
26
|
-
function addOwner(index, key, feature) {
|
|
27
|
-
const owners = index.get(key) ?? new Set();
|
|
28
|
-
owners.add(feature);
|
|
27
|
+
function addOwner(index, key, at, feature) {
|
|
28
|
+
const owners = index.get(key) ?? { at, features: new Set() };
|
|
29
|
+
owners.features.add(feature);
|
|
29
30
|
index.set(key, owners);
|
|
30
31
|
}
|
|
31
32
|
async function readFacts(docsDir, feature, context) {
|
|
@@ -105,9 +106,10 @@ async function readFacts(docsDir, feature, context) {
|
|
|
105
106
|
// flight — so the graph disagreeing with the validator about the same pair of
|
|
106
107
|
// features was the graph being wrong, not quiet.
|
|
107
108
|
//
|
|
108
|
-
// Endpoints resolve through `
|
|
109
|
-
// binding, title as fallback — the same join every
|
|
110
|
-
// renaming a box in a diagram cannot silently unhook it
|
|
109
|
+
// Endpoints resolve through `serviceResolver` — the element's
|
|
110
|
+
// `metadata { service }` binding, title as fallback — the same join every
|
|
111
|
+
// other check uses, so renaming a box in a diagram cannot silently unhook it
|
|
112
|
+
// here either.
|
|
111
113
|
const deltaPath = featurePaths(feature.dir).delta;
|
|
112
114
|
if (existsSync(deltaPath)) {
|
|
113
115
|
const doc = await context.loadLikeC4(deltaPath);
|
|
@@ -115,10 +117,13 @@ async function readFacts(docsDir, feature, context) {
|
|
|
115
117
|
// `loam validate` owns that diagnosis (`delta.invalid`); inventing edges out
|
|
116
118
|
// of a half-read document would be worse than the silence.
|
|
117
119
|
if (doc.errors.length === 0) {
|
|
120
|
+
// One resolver for the whole delta: `serviceOf` rebuilds its id map on
|
|
121
|
+
// every call, and this loop asks once per edge.
|
|
122
|
+
const svcOf = serviceResolver(doc.elements);
|
|
118
123
|
for (const rel of doc.relationships) {
|
|
119
124
|
if (rel.op === undefined)
|
|
120
125
|
continue;
|
|
121
|
-
const service =
|
|
126
|
+
const service = svcOf(rel.target);
|
|
122
127
|
if ((await living(service)).has(rel.op))
|
|
123
128
|
continue;
|
|
124
129
|
requiredOperations.push({ service, operationId: rel.op });
|
|
@@ -229,7 +234,14 @@ function dependencyFirstOrder(ids, edges) {
|
|
|
229
234
|
/** Derive the complete active-feature graph, or one feature and its transitive prerequisites. */
|
|
230
235
|
export async function analyzeDependencies(docsDir, featureId, context = new FleetContext()) {
|
|
231
236
|
const features = await listFeatures(docsDir, {}, context);
|
|
232
|
-
|
|
237
|
+
// Bounded, not `Promise.all`: `readFacts` loads the feature's delta.likec4,
|
|
238
|
+
// and each of those is a whole Langium workspace held open until the read
|
|
239
|
+
// finishes — the same resource `validate --all` rations for the same measured
|
|
240
|
+
// reason (see core/concurrency.ts). This module fanned out over every active
|
|
241
|
+
// feature at once, and it is not only `loam dependencies` that pays: the fleet
|
|
242
|
+
// form of `loam status` calls in here too. `inOrder` returns results in input
|
|
243
|
+
// order, so the graph, the conflict list and their ordering are unchanged.
|
|
244
|
+
const facts = await inOrder(features, (feature) => readFacts(docsDir, feature, context));
|
|
233
245
|
const requirementOwners = new Map();
|
|
234
246
|
// The second index over the SAME identity: who MODIFIES or REMOVES it. Only
|
|
235
247
|
// the first index existed, so the graph could see "two features add the same
|
|
@@ -241,39 +253,33 @@ export async function analyzeDependencies(docsDir, featureId, context = new Flee
|
|
|
241
253
|
for (const fact of facts) {
|
|
242
254
|
for (const added of fact.addedRequirements) {
|
|
243
255
|
for (const identity of reqKeys(added.requirement)) {
|
|
244
|
-
|
|
256
|
+
const at = { service: added.service, axis: added.axis, identity };
|
|
257
|
+
addOwner(requirementOwners, reqIndexKey(at), at, fact.feature.id);
|
|
245
258
|
}
|
|
246
259
|
}
|
|
247
260
|
for (const changed of fact.changedRequirements) {
|
|
248
261
|
for (const identity of reqKeys(changed.requirement)) {
|
|
249
|
-
|
|
262
|
+
const at = { service: changed.service, axis: changed.axis, identity };
|
|
263
|
+
addOwner(changedOwners, reqIndexKey(at), at, fact.feature.id);
|
|
250
264
|
}
|
|
251
265
|
}
|
|
252
266
|
for (const operation of fact.introducedOperations) {
|
|
253
|
-
addOwner(operationOwners, opIndexKey(operation
|
|
267
|
+
addOwner(operationOwners, opIndexKey(operation), operation, fact.feature.id);
|
|
254
268
|
}
|
|
255
269
|
}
|
|
256
270
|
const edgeMap = new Map();
|
|
257
271
|
for (const fact of facts) {
|
|
258
272
|
for (const changed of fact.changedRequirements) {
|
|
259
273
|
for (const identity of reqKeys(changed.requirement)) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
service: changed.service,
|
|
264
|
-
axis: changed.axis,
|
|
265
|
-
identity,
|
|
266
|
-
});
|
|
274
|
+
const at = { service: changed.service, axis: changed.axis, identity };
|
|
275
|
+
for (const owner of requirementOwners.get(reqIndexKey(at))?.features ?? []) {
|
|
276
|
+
appendReason(edgeMap, fact.feature.id, owner, { kind: "requirement", ...at });
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
}
|
|
270
280
|
for (const required of fact.requiredOperations) {
|
|
271
|
-
for (const owner of operationOwners.get(opIndexKey(required
|
|
272
|
-
appendReason(edgeMap, fact.feature.id, owner, {
|
|
273
|
-
kind: "operation",
|
|
274
|
-
service: required.service,
|
|
275
|
-
operationId: required.operationId,
|
|
276
|
-
});
|
|
281
|
+
for (const owner of operationOwners.get(opIndexKey(required))?.features ?? []) {
|
|
282
|
+
appendReason(edgeMap, fact.feature.id, owner, { kind: "operation", ...required });
|
|
277
283
|
}
|
|
278
284
|
}
|
|
279
285
|
}
|
|
@@ -285,32 +291,30 @@ export async function analyzeDependencies(docsDir, featureId, context = new Flee
|
|
|
285
291
|
[requirementOwners, "added"],
|
|
286
292
|
[changedOwners, "changed"],
|
|
287
293
|
]) {
|
|
288
|
-
for (const
|
|
289
|
-
if (
|
|
294
|
+
for (const { at, features } of index.values()) {
|
|
295
|
+
if (features.size < 2)
|
|
290
296
|
continue;
|
|
291
|
-
const [service, axis, identity] = key.split("\0");
|
|
292
297
|
conflicts.push({
|
|
293
298
|
kind: "requirement",
|
|
294
299
|
change,
|
|
295
|
-
service,
|
|
296
|
-
axis,
|
|
297
|
-
identity,
|
|
298
|
-
features: [...
|
|
300
|
+
service: at.service,
|
|
301
|
+
axis: at.axis,
|
|
302
|
+
identity: at.identity,
|
|
303
|
+
features: [...features].sort(compareIds),
|
|
299
304
|
});
|
|
300
305
|
}
|
|
301
306
|
}
|
|
302
|
-
for (const
|
|
303
|
-
if (
|
|
307
|
+
for (const { at, features } of operationOwners.values()) {
|
|
308
|
+
if (features.size < 2)
|
|
304
309
|
continue;
|
|
305
|
-
const [service, identity] = key.split("\0");
|
|
306
310
|
conflicts.push({
|
|
307
311
|
kind: "operation",
|
|
308
312
|
// Two features DEFINING the same operationId — the only way an operation
|
|
309
313
|
// collides, since removals address a living slot rather than claim one.
|
|
310
314
|
change: "added",
|
|
311
|
-
service,
|
|
312
|
-
identity,
|
|
313
|
-
features: [...
|
|
315
|
+
service: at.service,
|
|
316
|
+
identity: at.operationId,
|
|
317
|
+
features: [...features].sort(compareIds),
|
|
314
318
|
});
|
|
315
319
|
}
|
|
316
320
|
conflicts.sort((a, b) => compareIds(a.kind, b.kind)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/core/dependencies.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A read-only dependency/conflict index for active features.\n *\n * This is deliberately derived from parsed artifacts rather than validation\n * messages: issue prose is for people and is not an API. The graph answers a\n * narrower question — which in-flight change must land before another because\n * it introduces an identity or operation the latter already refers to.\n */\nimport { existsSync } from \"node:fs\";\nimport { FleetContext } from \"./fleet-context.js\";\nimport { serviceOf } from \"./likec4.js\";\nimport { operations } from \"./openapi.js\";\nimport {\n SPEC_AXES,\n compareIds,\n featurePaths,\n featureSpecPaths,\n listFeatures,\n servicePaths,\n type FeatureEntry,\n type SpecAxis,\n} from \"./repo.js\";\nimport type { Requirement } from \"./spec.js\";\n\nexport type DependencyReason =\n | {\n kind: \"requirement\";\n service: string;\n axis: SpecAxis[\"file\"];\n identity: string;\n }\n | { kind: \"operation\"; service: string; operationId: string };\n\nexport interface DependencyEdge {\n /** The feature that consumes/modifies something. */\n from: string;\n /** The feature that introduces it and therefore has to land first. */\n to: string;\n reasons: DependencyReason[];\n}\n\nexport interface FeatureConflict {\n kind: \"requirement\" | \"operation\";\n /**\n * How the owners collide.\n *\n * `added` — two features ADD the same identity (or define the same\n * operationId): whichever archives second replaces the first outright.\n *\n * `changed` — two features MODIFY or REMOVE the same living requirement.\n * That collision was invisible until now, and it is the more common one on a\n * fleet: nobody adds \"Cancel an order\" twice, but two teams editing its text\n * in the same week is a Tuesday. A MODIFIED requirement carries its FULL new\n * text, so the second archive does not merge the first one's wording — it\n * overwrites it, silently and completely.\n */\n change: \"added\" | \"changed\";\n service: string;\n axis?: SpecAxis[\"file\"];\n identity: string;\n features: string[];\n}\n\nexport interface DependencyNode {\n id: string;\n dirName: string;\n services: string[];\n dependsOn: string[];\n}\n\nexport interface DependencyGraph {\n nodes: DependencyNode[];\n edges: DependencyEdge[];\n conflicts: FeatureConflict[];\n /** Dependencies precede their consumers; cyclic members remain deterministic. */\n order: string[];\n /** Strongly connected components with more than one node (or a self-loop). */\n cycles: string[][];\n}\n\ninterface FeatureFacts {\n feature: FeatureEntry;\n addedRequirements: Array<{\n service: string;\n axis: SpecAxis[\"file\"];\n requirement: Requirement;\n }>;\n changedRequirements: Array<{\n service: string;\n axis: SpecAxis[\"file\"];\n requirement: Requirement;\n }>;\n introducedOperations: Array<{ service: string; operationId: string }>;\n requiredOperations: Array<{ service: string; operationId: string }>;\n}\n\nfunction reqKeys(requirement: Pick<Requirement, \"id\" | \"name\">): string[] {\n return [\n ...(requirement.id === undefined ? [] : [`id:${requirement.id}`]),\n `name:${requirement.name}`,\n ];\n}\n\nfunction reqIndexKey(service: string, axis: string, identity: string): string {\n return `${service}\\0${axis}\\0${identity}`;\n}\n\nfunction opIndexKey(service: string, operationId: string): string {\n return `${service}\\0${operationId}`;\n}\n\nfunction addOwner(index: Map<string, Set<string>>, key: string, feature: string): void {\n const owners = index.get(key) ?? new Set<string>();\n owners.add(feature);\n index.set(key, owners);\n}\n\nasync function readFacts(\n docsDir: string,\n feature: FeatureEntry,\n context: FleetContext,\n): Promise<FeatureFacts> {\n const addedRequirements: FeatureFacts[\"addedRequirements\"] = [];\n const changedRequirements: FeatureFacts[\"changedRequirements\"] = [];\n const introducedOperations: FeatureFacts[\"introducedOperations\"] = [];\n const requiredOperations: FeatureFacts[\"requiredOperations\"] = [];\n\n /**\n * The operationIds a service ALREADY has, per service.\n *\n * This is the whole difference between a dependency graph and a rumour mill.\n * A feature's `openapi.yaml` is a COMPLETE document, not a patch — authors\n * restate the living API around the slot they are changing — so every feature\n * that so much as mentions `authorizePayment` used to be recorded as\n * INTRODUCING it, and every other feature governing it as REQUIRING it. Two\n * features quoting the same living operation therefore depended on each other\n * in both directions: an invented cycle, an invented conflict, and an ordering\n * for work that had none. `loam validate`'s coherence pass already subtracts\n * the living contract this way (core/coherence.ts); this is the same\n * subtraction, so the two cannot disagree about what \"new\" means.\n *\n * Cached per service inside the call and, through the FleetContext, across\n * features: a fleet-wide graph asks about the same ten services N times.\n */\n const livingByService = new Map<string, Set<string>>();\n const living = async (service: string): Promise<Set<string>> => {\n let ids = livingByService.get(service);\n if (ids === undefined) {\n ids = new Set(\n (await operations(servicePaths(docsDir, service).openapi, context))\n .filter((op) => !op.remove)\n .map((op) => op.id),\n );\n livingByService.set(service, ids);\n }\n return ids;\n };\n\n for (const service of feature.services) {\n const paths = featureSpecPaths(feature.dir, service);\n const livingIds = await living(service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n const requirements = await context.readRequirements(path);\n for (const requirement of requirements) {\n const fact = { service, axis: axis.file, requirement };\n if (requirement.kind === \"ADDED\") addedRequirements.push(fact);\n if (requirement.kind === \"MODIFIED\" || requirement.kind === \"REMOVED\") {\n changedRequirements.push(fact);\n }\n for (const operationId of requirement.operations) {\n // Governing an operation the service already provides is not a\n // dependency on anybody: the contract it needs is already merged.\n if (livingIds.has(operationId)) continue;\n requiredOperations.push({ service, operationId });\n }\n }\n }\n\n if (existsSync(paths.openapi)) {\n const featureOperations = await operations(paths.openapi, context);\n for (const operation of featureOperations) {\n if (livingIds.has(operation.id)) continue;\n if (operation.remove) requiredOperations.push({ service, operationId: operation.id });\n else introducedOperations.push({ service, operationId: operation.id });\n }\n }\n }\n\n // The C4 delta is the FOURTH place a feature names an operation, and it was\n // the one the graph could not see: an edge carrying `metadata { op 'x' }` is\n // a call this feature builds on `x`, which is exactly the \"must land first\"\n // relation this module exists to compute. Validate already says so — it\n // reports `c4-api.op-pending` when the op is defined by another feature in\n // flight — so the graph disagreeing with the validator about the same pair of\n // features was the graph being wrong, not quiet.\n //\n // Endpoints resolve through `serviceOf` — the element's `metadata { service }`\n // binding, title as fallback — the same join every other check uses, so\n // renaming a box in a diagram cannot silently unhook it here either.\n const deltaPath = featurePaths(feature.dir).delta;\n if (existsSync(deltaPath)) {\n const doc = await context.loadLikeC4(deltaPath);\n // A delta that does not parse proves nothing about who depends on whom.\n // `loam validate` owns that diagnosis (`delta.invalid`); inventing edges out\n // of a half-read document would be worse than the silence.\n if (doc.errors.length === 0) {\n for (const rel of doc.relationships) {\n if (rel.op === undefined) continue;\n const service = serviceOf(doc.elements, rel.target);\n if ((await living(service)).has(rel.op)) continue;\n requiredOperations.push({ service, operationId: rel.op });\n }\n }\n }\n\n return {\n feature,\n addedRequirements,\n changedRequirements,\n introducedOperations,\n requiredOperations,\n };\n}\n\nfunction compareReasons(a: DependencyReason, b: DependencyReason): number {\n const ak = a.kind === \"requirement\"\n ? `${a.kind}\\0${a.service}\\0${a.axis}\\0${a.identity}`\n : `${a.kind}\\0${a.service}\\0${a.operationId}`;\n const bk = b.kind === \"requirement\"\n ? `${b.kind}\\0${b.service}\\0${b.axis}\\0${b.identity}`\n : `${b.kind}\\0${b.service}\\0${b.operationId}`;\n return compareIds(ak, bk);\n}\n\nfunction edgeKey(from: string, to: string): string {\n return `${from}\\0${to}`;\n}\n\nfunction appendReason(\n edges: Map<string, DependencyEdge>,\n from: string,\n to: string,\n reason: DependencyReason,\n): void {\n if (from === to) return;\n const key = edgeKey(from, to);\n const edge = edges.get(key) ?? { from, to, reasons: [] };\n const encoded = JSON.stringify(reason);\n if (!edge.reasons.some((item) => JSON.stringify(item) === encoded)) edge.reasons.push(reason);\n edges.set(key, edge);\n}\n\nfunction stronglyConnected(ids: string[], edges: DependencyEdge[]): string[][] {\n const adjacency = new Map(ids.map((id) => [id, [] as string[]]));\n for (const edge of edges) adjacency.get(edge.from)?.push(edge.to);\n for (const targets of adjacency.values()) targets.sort(compareIds);\n\n let nextIndex = 0;\n const indices = new Map<string, number>();\n const low = new Map<string, number>();\n const stack: string[] = [];\n const onStack = new Set<string>();\n const components: string[][] = [];\n\n const visit = (id: string): void => {\n indices.set(id, nextIndex);\n low.set(id, nextIndex);\n nextIndex += 1;\n stack.push(id);\n onStack.add(id);\n\n for (const target of adjacency.get(id) ?? []) {\n if (!indices.has(target)) {\n visit(target);\n low.set(id, Math.min(low.get(id)!, low.get(target)!));\n } else if (onStack.has(target)) {\n low.set(id, Math.min(low.get(id)!, indices.get(target)!));\n }\n }\n if (low.get(id) !== indices.get(id)) return;\n const component: string[] = [];\n while (stack.length > 0) {\n const member = stack.pop()!;\n onStack.delete(member);\n component.push(member);\n if (member === id) break;\n }\n component.sort(compareIds);\n const selfLoop = component.length === 1\n && (adjacency.get(component[0]!) ?? []).includes(component[0]!);\n if (component.length > 1 || selfLoop) components.push(component);\n };\n\n for (const id of [...ids].sort(compareIds)) if (!indices.has(id)) visit(id);\n return components.sort((a, b) => compareIds(a[0]!, b[0]!));\n}\n\nfunction dependencyFirstOrder(ids: string[], edges: DependencyEdge[]): string[] {\n const deps = new Map(ids.map((id) => [id, [] as string[]]));\n for (const edge of edges) deps.get(edge.from)?.push(edge.to);\n for (const values of deps.values()) values.sort(compareIds);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const order: string[] = [];\n const visit = (id: string): void => {\n if (visited.has(id) || visiting.has(id)) return;\n visiting.add(id);\n for (const dependency of deps.get(id) ?? []) visit(dependency);\n visiting.delete(id);\n visited.add(id);\n order.push(id);\n };\n for (const id of [...ids].sort(compareIds)) visit(id);\n return order;\n}\n\n/** Derive the complete active-feature graph, or one feature and its transitive prerequisites. */\nexport async function analyzeDependencies(\n docsDir: string,\n featureId?: string,\n context = new FleetContext(),\n): Promise<DependencyGraph> {\n const features = await listFeatures(docsDir, {}, context);\n const facts = await Promise.all(features.map((feature) => readFacts(docsDir, feature, context)));\n const requirementOwners = new Map<string, Set<string>>();\n // The second index over the SAME identity: who MODIFIES or REMOVES it. Only\n // the first index existed, so the graph could see \"two features add the same\n // requirement\" and was blind to \"two features rewrite the same requirement\" —\n // the collision that actually happens on a fleet, and the one whose loser\n // loses their whole authored text without a word from any command.\n const changedOwners = new Map<string, Set<string>>();\n const operationOwners = new Map<string, Set<string>>();\n\n for (const fact of facts) {\n for (const added of fact.addedRequirements) {\n for (const identity of reqKeys(added.requirement)) {\n addOwner(requirementOwners, reqIndexKey(added.service, added.axis, identity), fact.feature.id);\n }\n }\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n addOwner(changedOwners, reqIndexKey(changed.service, changed.axis, identity), fact.feature.id);\n }\n }\n for (const operation of fact.introducedOperations) {\n addOwner(operationOwners, opIndexKey(operation.service, operation.operationId), fact.feature.id);\n }\n }\n\n const edgeMap = new Map<string, DependencyEdge>();\n for (const fact of facts) {\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n for (const owner of requirementOwners.get(reqIndexKey(changed.service, changed.axis, identity)) ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, {\n kind: \"requirement\",\n service: changed.service,\n axis: changed.axis,\n identity,\n });\n }\n }\n }\n for (const required of fact.requiredOperations) {\n for (const owner of operationOwners.get(opIndexKey(required.service, required.operationId)) ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, {\n kind: \"operation\",\n service: required.service,\n operationId: required.operationId,\n });\n }\n }\n }\n\n let edges = [...edgeMap.values()]\n .map((edge) => ({ ...edge, reasons: edge.reasons.sort(compareReasons) }))\n .sort((a, b) => compareIds(a.from, b.from) || compareIds(a.to, b.to));\n\n const conflicts: FeatureConflict[] = [];\n for (const [index, change] of [\n [requirementOwners, \"added\"],\n [changedOwners, \"changed\"],\n ] as const) {\n for (const [key, owners] of index) {\n if (owners.size < 2) continue;\n const [service, axis, identity] = key.split(\"\\0\") as [string, SpecAxis[\"file\"], string];\n conflicts.push({\n kind: \"requirement\",\n change,\n service,\n axis,\n identity,\n features: [...owners].sort(compareIds),\n });\n }\n }\n for (const [key, owners] of operationOwners) {\n if (owners.size < 2) continue;\n const [service, identity] = key.split(\"\\0\") as [string, string];\n conflicts.push({\n kind: \"operation\",\n // Two features DEFINING the same operationId — the only way an operation\n // collides, since removals address a living slot rather than claim one.\n change: \"added\",\n service,\n identity,\n features: [...owners].sort(compareIds),\n });\n }\n conflicts.sort((a, b) =>\n compareIds(a.kind, b.kind)\n || compareIds(a.change, b.change)\n || compareIds(a.service, b.service)\n || compareIds(a.axis ?? \"\", b.axis ?? \"\")\n || compareIds(a.identity, b.identity));\n\n let selected = new Set(features.map((feature) => feature.id));\n if (featureId !== undefined) {\n selected = new Set<string>([featureId]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const edge of edges) {\n if (selected.has(edge.from) && !selected.has(edge.to)) {\n selected.add(edge.to);\n changed = true;\n }\n }\n }\n edges = edges.filter((edge) => selected.has(edge.from) && selected.has(edge.to));\n }\n\n const selectedFeatures = features.filter((feature) => selected.has(feature.id));\n const ids = selectedFeatures.map((feature) => feature.id);\n const nodes = selectedFeatures.map((feature): DependencyNode => ({\n id: feature.id,\n dirName: feature.dirName,\n services: [...feature.services],\n dependsOn: edges.filter((edge) => edge.from === feature.id).map((edge) => edge.to).sort(compareIds),\n }));\n\n return {\n nodes,\n edges,\n conflicts: conflicts.filter((conflict) => conflict.features.some((id) => selected.has(id))),\n order: dependencyFirstOrder(ids, edges),\n cycles: stronglyConnected(ids, edges),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/core/dependencies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,YAAY,GAGb,MAAM,WAAW,CAAC;AA2EnB,SAAS,OAAO,CAAC,WAA6C;IAC5D,OAAO;QACL,GAAG,CAAC,WAAW,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;QACjE,QAAQ,WAAW,CAAC,IAAI,EAAE;KAC3B,CAAC;AACJ,CAAC;AA6BD,SAAS,WAAW,CAAC,EAAS;IAC5B,OAAO,GAAG,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,UAAU,CAAC,EAAQ;IAC1B,OAAO,GAAG,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC;AAC5C,CAAC;AAED,SAAS,QAAQ,CAAI,KAA6B,EAAE,GAAW,EAAE,EAAK,EAAE,OAAe;IACrF,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAU,EAAE,CAAC;IACrE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACzB,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,OAAe,EACf,OAAqB,EACrB,OAAqB;IAErB,MAAM,iBAAiB,GAAsC,EAAE,CAAC;IAChE,MAAM,mBAAmB,GAAwC,EAAE,CAAC;IACpE,MAAM,oBAAoB,GAAyC,EAAE,CAAC;IACtE,MAAM,kBAAkB,GAAuC,EAAE,CAAC;IAElE;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,eAAe,GAAG,IAAI,GAAG,EAAuB,CAAC;IACvD,MAAM,MAAM,GAAG,KAAK,EAAE,OAAe,EAAwB,EAAE;QAC7D,IAAI,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,GAAG,GAAG,IAAI,GAAG,CACX,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;iBAChE,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;iBAC1B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CACtB,CAAC;YACF,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChC,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC1D,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;gBACvD,IAAI,WAAW,CAAC,IAAI,KAAK,OAAO;oBAAE,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC/D,IAAI,WAAW,CAAC,IAAI,KAAK,UAAU,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBACtE,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACjC,CAAC;gBACD,KAAK,MAAM,WAAW,IAAI,WAAW,CAAC,UAAU,EAAE,CAAC;oBACjD,+DAA+D;oBAC/D,kEAAkE;oBAClE,IAAI,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC;wBAAE,SAAS;oBACzC,kBAAkB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;gBACpD,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,MAAM,iBAAiB,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnE,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;gBAC1C,IAAI,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC1C,IAAI,SAAS,CAAC,MAAM;oBAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;;oBACjF,oBAAoB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,2EAA2E;IAC3E,8EAA8E;IAC9E,iDAAiD;IACjD,EAAE;IACF,8DAA8D;IAC9D,0EAA0E;IAC1E,6EAA6E;IAC7E,eAAe;IACf,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;IAClD,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAChD,wEAAwE;QACxE,6EAA6E;QAC7E,2DAA2D;QAC3D,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,uEAAuE;YACvE,gDAAgD;YAChD,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC5C,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;gBACpC,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS;oBAAE,SAAS;gBACnC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAClC,IAAI,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAClD,kBAAkB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO;QACP,iBAAiB;QACjB,mBAAmB;QACnB,oBAAoB;QACpB,kBAAkB;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,CAAmB,EAAE,CAAmB;IAC9D,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,aAAa;QACjC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,EAAE;QACrD,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,aAAa;QACjC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,EAAE;QACrD,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IAChD,OAAO,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,EAAU;IACvC,OAAO,GAAG,IAAI,KAAK,EAAE,EAAE,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CACnB,KAAkC,EAClC,IAAY,EACZ,EAAU,EACV,MAAwB;IAExB,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO;IACxB,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9F,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAa,EAAE,KAAuB;IAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAc,CAAC,CAAC,CAAC,CAAC;IACjE,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEnE,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,UAAU,GAAe,EAAE,CAAC;IAElC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAQ,EAAE;QACjC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC3B,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QACvB,SAAS,IAAI,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzB,KAAK,CAAC,MAAM,CAAC,CAAC;gBACd,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,CAAC,CAAC;YACxD,CAAC;iBAAM,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAE,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO;QAC5C,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACvB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvB,IAAI,MAAM,KAAK,EAAE;gBAAE,MAAM;QAC3B,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC;eAClC,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC;QAClE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ;YAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnE,CAAC,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5E,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAa,EAAE,KAAuB;IAClE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAc,CAAC,CAAC,CAAC,CAAC;IAC5D,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,EAAU,EAAQ,EAAE;QACjC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO;QAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iGAAiG;AACjG,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAe,EACf,SAAkB,EAClB,OAAO,GAAG,IAAI,YAAY,EAAE;IAE5B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1D,4EAA4E;IAC5E,0EAA0E;IAC1E,8EAA8E;IAC9E,6EAA6E;IAC7E,+EAA+E;IAC/E,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACzF,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC3D,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,mEAAmE;IACnE,MAAM,aAAa,GAAG,IAAI,GAAG,EAAyB,CAAC;IACvD,MAAM,eAAe,GAAG,IAAI,GAAG,EAAwB,CAAC;IAExD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;gBAClD,MAAM,EAAE,GAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;gBACzE,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACpD,MAAM,EAAE,GAAU,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAC7E,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAClD,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACpD,MAAM,EAAE,GAAU,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAC7E,KAAK,MAAM,KAAK,IAAI,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;oBAC3E,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;gBAChF,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC/C,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;gBAC9E,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;SAC9B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;SACxE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAExE,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI;QAC5B,CAAC,iBAAiB,EAAE,OAAO,CAAC;QAC5B,CAAC,aAAa,EAAE,SAAS,CAAC;KAClB,EAAE,CAAC;QACX,KAAK,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC;gBAAE,SAAS;YAChC,SAAS,CAAC,IAAI,CAAC;gBACb,IAAI,EAAE,aAAa;gBACnB,MAAM;gBACN,OAAO,EAAE,EAAE,CAAC,OAAO;gBACnB,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,QAAQ,EAAE,EAAE,CAAC,QAAQ;gBACrB,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;aACzC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;QACxD,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC;YAAE,SAAS;QAChC,SAAS,CAAC,IAAI,CAAC;YACb,IAAI,EAAE,WAAW;YACjB,yEAAyE;YACzE,wEAAwE;YACxE,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,EAAE,CAAC,OAAO;YACnB,QAAQ,EAAE,EAAE,CAAC,WAAW;YACxB,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;SACzC,CAAC,CAAC;IACL,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACtB,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;WACvB,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;WAC9B,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC;WAChC,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;WACtC,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEzC,IAAI,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QACxC,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,OAAO,OAAO,EAAE,CAAC;YACf,OAAO,GAAG,KAAK,CAAC;YAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;oBACtD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACtB,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAkB,EAAE,CAAC,CAAC;QAC/D,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC/B,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;KACpG,CAAC,CAAC,CAAC;IAEJ,OAAO;QACL,KAAK;QACL,KAAK;QACL,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,KAAK,EAAE,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC;QACvC,MAAM,EAAE,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC;KACtC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * A read-only dependency/conflict index for active features.\n *\n * This is deliberately derived from parsed artifacts rather than validation\n * messages: issue prose is for people and is not an API. The graph answers a\n * narrower question — which in-flight change must land before another because\n * it introduces an identity or operation the latter already refers to.\n */\nimport { existsSync } from \"node:fs\";\nimport { inOrder } from \"./concurrency.js\";\nimport { FleetContext } from \"./fleet-context.js\";\nimport { serviceResolver } from \"./likec4.js\";\nimport { operations } from \"./openapi.js\";\nimport {\n SPEC_AXES,\n compareIds,\n featurePaths,\n featureSpecPaths,\n listFeatures,\n servicePaths,\n type FeatureEntry,\n type SpecAxis,\n} from \"./repo.js\";\nimport type { Requirement } from \"./spec.js\";\n\nexport type DependencyReason =\n | {\n kind: \"requirement\";\n service: string;\n axis: SpecAxis[\"file\"];\n identity: string;\n }\n | { kind: \"operation\"; service: string; operationId: string };\n\nexport interface DependencyEdge {\n /** The feature that consumes/modifies something. */\n from: string;\n /** The feature that introduces it and therefore has to land first. */\n to: string;\n reasons: DependencyReason[];\n}\n\nexport interface FeatureConflict {\n kind: \"requirement\" | \"operation\";\n /**\n * How the owners collide.\n *\n * `added` — two features ADD the same identity (or define the same\n * operationId): whichever archives second replaces the first outright.\n *\n * `changed` — two features MODIFY or REMOVE the same living requirement.\n * That collision was invisible until now, and it is the more common one on a\n * fleet: nobody adds \"Cancel an order\" twice, but two teams editing its text\n * in the same week is a Tuesday. A MODIFIED requirement carries its FULL new\n * text, so the second archive does not merge the first one's wording — it\n * overwrites it, silently and completely.\n */\n change: \"added\" | \"changed\";\n service: string;\n axis?: SpecAxis[\"file\"];\n identity: string;\n features: string[];\n}\n\nexport interface DependencyNode {\n id: string;\n dirName: string;\n services: string[];\n dependsOn: string[];\n}\n\nexport interface DependencyGraph {\n nodes: DependencyNode[];\n edges: DependencyEdge[];\n conflicts: FeatureConflict[];\n /** Dependencies precede their consumers; cyclic members remain deterministic. */\n order: string[];\n /** Strongly connected components with more than one node (or a self-loop). */\n cycles: string[][];\n}\n\ninterface FeatureFacts {\n feature: FeatureEntry;\n addedRequirements: Array<{\n service: string;\n axis: SpecAxis[\"file\"];\n requirement: Requirement;\n }>;\n changedRequirements: Array<{\n service: string;\n axis: SpecAxis[\"file\"];\n requirement: Requirement;\n }>;\n introducedOperations: Array<{ service: string; operationId: string }>;\n requiredOperations: Array<{ service: string; operationId: string }>;\n}\n\nfunction reqKeys(requirement: Pick<Requirement, \"id\" | \"name\">): string[] {\n return [\n ...(requirement.id === undefined ? [] : [`id:${requirement.id}`]),\n `name:${requirement.name}`,\n ];\n}\n\n/**\n * WHERE a collision happens, kept as fields rather than as a string.\n *\n * An index needs one scalar per entry to hash on, so the fields get NUL-joined\n * into a key — but the conflict records want those fields back, and splitting\n * the key apart again means asserting the pieces back into their types. That\n * assertion is a claim about data loam does not own: a service directory or a\n * requirement name containing a NUL would hand `axis` a fragment of an identity\n * with nothing to notice. Carrying the structured value beside the owner set\n * means the key is only ever a key, and the record is read, not reconstructed.\n */\ninterface ReqAt {\n service: string;\n axis: SpecAxis[\"file\"];\n identity: string;\n}\n\ninterface OpAt {\n service: string;\n operationId: string;\n}\n\ninterface Owners<T> {\n readonly at: T;\n readonly features: Set<string>;\n}\n\nfunction reqIndexKey(at: ReqAt): string {\n return `${at.service}\\0${at.axis}\\0${at.identity}`;\n}\n\nfunction opIndexKey(at: OpAt): string {\n return `${at.service}\\0${at.operationId}`;\n}\n\nfunction addOwner<T>(index: Map<string, Owners<T>>, key: string, at: T, feature: string): void {\n const owners = index.get(key) ?? { at, features: new Set<string>() };\n owners.features.add(feature);\n index.set(key, owners);\n}\n\nasync function readFacts(\n docsDir: string,\n feature: FeatureEntry,\n context: FleetContext,\n): Promise<FeatureFacts> {\n const addedRequirements: FeatureFacts[\"addedRequirements\"] = [];\n const changedRequirements: FeatureFacts[\"changedRequirements\"] = [];\n const introducedOperations: FeatureFacts[\"introducedOperations\"] = [];\n const requiredOperations: FeatureFacts[\"requiredOperations\"] = [];\n\n /**\n * The operationIds a service ALREADY has, per service.\n *\n * This is the whole difference between a dependency graph and a rumour mill.\n * A feature's `openapi.yaml` is a COMPLETE document, not a patch — authors\n * restate the living API around the slot they are changing — so every feature\n * that so much as mentions `authorizePayment` used to be recorded as\n * INTRODUCING it, and every other feature governing it as REQUIRING it. Two\n * features quoting the same living operation therefore depended on each other\n * in both directions: an invented cycle, an invented conflict, and an ordering\n * for work that had none. `loam validate`'s coherence pass already subtracts\n * the living contract this way (core/coherence.ts); this is the same\n * subtraction, so the two cannot disagree about what \"new\" means.\n *\n * Cached per service inside the call and, through the FleetContext, across\n * features: a fleet-wide graph asks about the same ten services N times.\n */\n const livingByService = new Map<string, Set<string>>();\n const living = async (service: string): Promise<Set<string>> => {\n let ids = livingByService.get(service);\n if (ids === undefined) {\n ids = new Set(\n (await operations(servicePaths(docsDir, service).openapi, context))\n .filter((op) => !op.remove)\n .map((op) => op.id),\n );\n livingByService.set(service, ids);\n }\n return ids;\n };\n\n for (const service of feature.services) {\n const paths = featureSpecPaths(feature.dir, service);\n const livingIds = await living(service);\n for (const axis of SPEC_AXES) {\n const path = paths[axis.key];\n if (!existsSync(path)) continue;\n const requirements = await context.readRequirements(path);\n for (const requirement of requirements) {\n const fact = { service, axis: axis.file, requirement };\n if (requirement.kind === \"ADDED\") addedRequirements.push(fact);\n if (requirement.kind === \"MODIFIED\" || requirement.kind === \"REMOVED\") {\n changedRequirements.push(fact);\n }\n for (const operationId of requirement.operations) {\n // Governing an operation the service already provides is not a\n // dependency on anybody: the contract it needs is already merged.\n if (livingIds.has(operationId)) continue;\n requiredOperations.push({ service, operationId });\n }\n }\n }\n\n if (existsSync(paths.openapi)) {\n const featureOperations = await operations(paths.openapi, context);\n for (const operation of featureOperations) {\n if (livingIds.has(operation.id)) continue;\n if (operation.remove) requiredOperations.push({ service, operationId: operation.id });\n else introducedOperations.push({ service, operationId: operation.id });\n }\n }\n }\n\n // The C4 delta is the FOURTH place a feature names an operation, and it was\n // the one the graph could not see: an edge carrying `metadata { op 'x' }` is\n // a call this feature builds on `x`, which is exactly the \"must land first\"\n // relation this module exists to compute. Validate already says so — it\n // reports `c4-api.op-pending` when the op is defined by another feature in\n // flight — so the graph disagreeing with the validator about the same pair of\n // features was the graph being wrong, not quiet.\n //\n // Endpoints resolve through `serviceResolver` — the element's\n // `metadata { service }` binding, title as fallback — the same join every\n // other check uses, so renaming a box in a diagram cannot silently unhook it\n // here either.\n const deltaPath = featurePaths(feature.dir).delta;\n if (existsSync(deltaPath)) {\n const doc = await context.loadLikeC4(deltaPath);\n // A delta that does not parse proves nothing about who depends on whom.\n // `loam validate` owns that diagnosis (`delta.invalid`); inventing edges out\n // of a half-read document would be worse than the silence.\n if (doc.errors.length === 0) {\n // One resolver for the whole delta: `serviceOf` rebuilds its id map on\n // every call, and this loop asks once per edge.\n const svcOf = serviceResolver(doc.elements);\n for (const rel of doc.relationships) {\n if (rel.op === undefined) continue;\n const service = svcOf(rel.target);\n if ((await living(service)).has(rel.op)) continue;\n requiredOperations.push({ service, operationId: rel.op });\n }\n }\n }\n\n return {\n feature,\n addedRequirements,\n changedRequirements,\n introducedOperations,\n requiredOperations,\n };\n}\n\nfunction compareReasons(a: DependencyReason, b: DependencyReason): number {\n const ak = a.kind === \"requirement\"\n ? `${a.kind}\\0${a.service}\\0${a.axis}\\0${a.identity}`\n : `${a.kind}\\0${a.service}\\0${a.operationId}`;\n const bk = b.kind === \"requirement\"\n ? `${b.kind}\\0${b.service}\\0${b.axis}\\0${b.identity}`\n : `${b.kind}\\0${b.service}\\0${b.operationId}`;\n return compareIds(ak, bk);\n}\n\nfunction edgeKey(from: string, to: string): string {\n return `${from}\\0${to}`;\n}\n\nfunction appendReason(\n edges: Map<string, DependencyEdge>,\n from: string,\n to: string,\n reason: DependencyReason,\n): void {\n if (from === to) return;\n const key = edgeKey(from, to);\n const edge = edges.get(key) ?? { from, to, reasons: [] };\n const encoded = JSON.stringify(reason);\n if (!edge.reasons.some((item) => JSON.stringify(item) === encoded)) edge.reasons.push(reason);\n edges.set(key, edge);\n}\n\nfunction stronglyConnected(ids: string[], edges: DependencyEdge[]): string[][] {\n const adjacency = new Map(ids.map((id) => [id, [] as string[]]));\n for (const edge of edges) adjacency.get(edge.from)?.push(edge.to);\n for (const targets of adjacency.values()) targets.sort(compareIds);\n\n let nextIndex = 0;\n const indices = new Map<string, number>();\n const low = new Map<string, number>();\n const stack: string[] = [];\n const onStack = new Set<string>();\n const components: string[][] = [];\n\n const visit = (id: string): void => {\n indices.set(id, nextIndex);\n low.set(id, nextIndex);\n nextIndex += 1;\n stack.push(id);\n onStack.add(id);\n\n for (const target of adjacency.get(id) ?? []) {\n if (!indices.has(target)) {\n visit(target);\n low.set(id, Math.min(low.get(id)!, low.get(target)!));\n } else if (onStack.has(target)) {\n low.set(id, Math.min(low.get(id)!, indices.get(target)!));\n }\n }\n if (low.get(id) !== indices.get(id)) return;\n const component: string[] = [];\n while (stack.length > 0) {\n const member = stack.pop()!;\n onStack.delete(member);\n component.push(member);\n if (member === id) break;\n }\n component.sort(compareIds);\n const selfLoop = component.length === 1\n && (adjacency.get(component[0]!) ?? []).includes(component[0]!);\n if (component.length > 1 || selfLoop) components.push(component);\n };\n\n for (const id of [...ids].sort(compareIds)) if (!indices.has(id)) visit(id);\n return components.sort((a, b) => compareIds(a[0]!, b[0]!));\n}\n\nfunction dependencyFirstOrder(ids: string[], edges: DependencyEdge[]): string[] {\n const deps = new Map(ids.map((id) => [id, [] as string[]]));\n for (const edge of edges) deps.get(edge.from)?.push(edge.to);\n for (const values of deps.values()) values.sort(compareIds);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n const order: string[] = [];\n const visit = (id: string): void => {\n if (visited.has(id) || visiting.has(id)) return;\n visiting.add(id);\n for (const dependency of deps.get(id) ?? []) visit(dependency);\n visiting.delete(id);\n visited.add(id);\n order.push(id);\n };\n for (const id of [...ids].sort(compareIds)) visit(id);\n return order;\n}\n\n/** Derive the complete active-feature graph, or one feature and its transitive prerequisites. */\nexport async function analyzeDependencies(\n docsDir: string,\n featureId?: string,\n context = new FleetContext(),\n): Promise<DependencyGraph> {\n const features = await listFeatures(docsDir, {}, context);\n // Bounded, not `Promise.all`: `readFacts` loads the feature's delta.likec4,\n // and each of those is a whole Langium workspace held open until the read\n // finishes — the same resource `validate --all` rations for the same measured\n // reason (see core/concurrency.ts). This module fanned out over every active\n // feature at once, and it is not only `loam dependencies` that pays: the fleet\n // form of `loam status` calls in here too. `inOrder` returns results in input\n // order, so the graph, the conflict list and their ordering are unchanged.\n const facts = await inOrder(features, (feature) => readFacts(docsDir, feature, context));\n const requirementOwners = new Map<string, Owners<ReqAt>>();\n // The second index over the SAME identity: who MODIFIES or REMOVES it. Only\n // the first index existed, so the graph could see \"two features add the same\n // requirement\" and was blind to \"two features rewrite the same requirement\" —\n // the collision that actually happens on a fleet, and the one whose loser\n // loses their whole authored text without a word from any command.\n const changedOwners = new Map<string, Owners<ReqAt>>();\n const operationOwners = new Map<string, Owners<OpAt>>();\n\n for (const fact of facts) {\n for (const added of fact.addedRequirements) {\n for (const identity of reqKeys(added.requirement)) {\n const at: ReqAt = { service: added.service, axis: added.axis, identity };\n addOwner(requirementOwners, reqIndexKey(at), at, fact.feature.id);\n }\n }\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n const at: ReqAt = { service: changed.service, axis: changed.axis, identity };\n addOwner(changedOwners, reqIndexKey(at), at, fact.feature.id);\n }\n }\n for (const operation of fact.introducedOperations) {\n addOwner(operationOwners, opIndexKey(operation), operation, fact.feature.id);\n }\n }\n\n const edgeMap = new Map<string, DependencyEdge>();\n for (const fact of facts) {\n for (const changed of fact.changedRequirements) {\n for (const identity of reqKeys(changed.requirement)) {\n const at: ReqAt = { service: changed.service, axis: changed.axis, identity };\n for (const owner of requirementOwners.get(reqIndexKey(at))?.features ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, { kind: \"requirement\", ...at });\n }\n }\n }\n for (const required of fact.requiredOperations) {\n for (const owner of operationOwners.get(opIndexKey(required))?.features ?? []) {\n appendReason(edgeMap, fact.feature.id, owner, { kind: \"operation\", ...required });\n }\n }\n }\n\n let edges = [...edgeMap.values()]\n .map((edge) => ({ ...edge, reasons: edge.reasons.sort(compareReasons) }))\n .sort((a, b) => compareIds(a.from, b.from) || compareIds(a.to, b.to));\n\n const conflicts: FeatureConflict[] = [];\n for (const [index, change] of [\n [requirementOwners, \"added\"],\n [changedOwners, \"changed\"],\n ] as const) {\n for (const { at, features } of index.values()) {\n if (features.size < 2) continue;\n conflicts.push({\n kind: \"requirement\",\n change,\n service: at.service,\n axis: at.axis,\n identity: at.identity,\n features: [...features].sort(compareIds),\n });\n }\n }\n for (const { at, features } of operationOwners.values()) {\n if (features.size < 2) continue;\n conflicts.push({\n kind: \"operation\",\n // Two features DEFINING the same operationId — the only way an operation\n // collides, since removals address a living slot rather than claim one.\n change: \"added\",\n service: at.service,\n identity: at.operationId,\n features: [...features].sort(compareIds),\n });\n }\n conflicts.sort((a, b) =>\n compareIds(a.kind, b.kind)\n || compareIds(a.change, b.change)\n || compareIds(a.service, b.service)\n || compareIds(a.axis ?? \"\", b.axis ?? \"\")\n || compareIds(a.identity, b.identity));\n\n let selected = new Set(features.map((feature) => feature.id));\n if (featureId !== undefined) {\n selected = new Set<string>([featureId]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const edge of edges) {\n if (selected.has(edge.from) && !selected.has(edge.to)) {\n selected.add(edge.to);\n changed = true;\n }\n }\n }\n edges = edges.filter((edge) => selected.has(edge.from) && selected.has(edge.to));\n }\n\n const selectedFeatures = features.filter((feature) => selected.has(feature.id));\n const ids = selectedFeatures.map((feature) => feature.id);\n const nodes = selectedFeatures.map((feature): DependencyNode => ({\n id: feature.id,\n dirName: feature.dirName,\n services: [...feature.services],\n dependsOn: edges.filter((edge) => edge.from === feature.id).map((edge) => edge.to).sort(compareIds),\n }));\n\n return {\n nodes,\n edges,\n conflicts: conflicts.filter((conflict) => conflict.features.some((id) => selected.has(id))),\n order: dependencyFirstOrder(ids, edges),\n cycles: stronglyConnected(ids, edges),\n };\n}\n"]}
|
package/dist/core/docs.js
CHANGED
|
@@ -12,6 +12,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { join, resolve } from "node:path";
|
|
14
14
|
import { AGENTS_MD } from "./agent.js";
|
|
15
|
+
import { AGENTS_FILENAME } from "./repo.js";
|
|
15
16
|
/**
|
|
16
17
|
* Top-level layout of the shared docs repo, and part of its identity rather
|
|
17
18
|
* than a convenience: `services/` is what makes a directory a docs repo —
|
|
@@ -127,7 +128,7 @@ export function docsRepoFiles(opts = {}) {
|
|
|
127
128
|
return [
|
|
128
129
|
// The process contract lives with the docs it describes, so an agent handed
|
|
129
130
|
// only the docs repo still knows the cycle.
|
|
130
|
-
[
|
|
131
|
+
[AGENTS_FILENAME, AGENTS_MD],
|
|
131
132
|
[join("architecture", "landscape.likec4"), `${preamble}${LANDSCAPE_STUB}`],
|
|
132
133
|
["loam.json", DOCS_SELF_CONFIG],
|
|
133
134
|
];
|
package/dist/core/docs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/core/docs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/core/docs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scaffolding for the shared docs repo.\n *\n * There is deliberately no manifest. `init` used to write a `loam.docs.json`\n * listing the repo's services; nothing ever read it — `repo.ts` enumerates from\n * the filesystem, because files are the source of truth — and nothing ever\n * updated it, so it named an empty fleet forever. A second list of services is\n * exactly the drift `loam validate` now cross-checks the landscape for; the\n * cheapest way to keep it honest is not to have it.\n */\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { AGENTS_MD } from \"./agent.js\";\nimport { AGENTS_FILENAME } from \"./repo.js\";\n\n/**\n * Top-level layout of the shared docs repo, and part of its identity rather\n * than a convenience: `services/` is what makes a directory a docs repo —\n * `init` will not join one without it, and every enumerating command refuses a\n * `docsDir` that has none. So it is laid down even when nothing is in it yet.\n */\nexport const DOCS_SUBDIRS = [\"architecture\", \"services\", \"features\"] as const;\n\n/**\n * The fleet map, empty but valid.\n *\n * Nobody writes this file automatically — `adopt` deliberately does not touch\n * it, because who calls whom is a human judgement and a generated landscape\n * would be a guess presented as the map. But \"nobody writes it\" used to mean\n * \"it is simply absent\", and an absent landscape is the one artifact whose\n * absence silences the fleet-wide checks entirely: every cross-service breach\n * `validate` exists to find is invisible on a repo with no landscape.\n *\n * So the scaffold lays down the empty map instead: the four element kinds a\n * fleet actually uses are declared, the model is empty, and the comments say\n * what to add and why. The first `loam adopt` then has somewhere to be drawn.\n *\n * No `views` block, deliberately. loam reads elements and relationships and\n * never a view, so the scaffolded `view index { include * }` was a diagram loam\n * planted on every repo and then never drew — and views are the expensive half\n * of LikeC4: computing them is superlinear in edge count (120 services / 300\n * calls does not finish in three minutes; parsing the same file takes 11 ms).\n * loam stopped computing views, so the block is free for loam itself now, but\n * scaffolding one still hands the user a fleet-sized bill for the first tool\n * that does compute it. Teams who want diagrams declare their own.\n */\nconst LANDSCAPE_STUB = `// The fleet map: every service in services/ appears here, and every call\n// between two of them is an edge. This file is written by hand — loam never\n// guesses it, because \"who calls whom\" is the one fact no generator can read\n// off a repository.\n//\n// After \\`loam adopt --service <id>\\`, add the service here:\n//\n// paymentService = softwareSystem 'payment-service' {\n// description 'Owns payment authorization/capture'\n// metadata { service 'payment-service' } // binds the box to services/<id>/\n// }\n//\n// and give each call the operationId it uses, so requirements, C4 and OpenAPI\n// can be cross-checked:\n//\n// checkoutWeb -> paymentService 'Authorizes' {\n// metadata { op 'authorizePayment' }\n// }\n//\n// There is no \\`views\\` block, on purpose: loam reads the model and never a\n// view, so it would draw nothing. Add views here if you want diagrams, and\n// render them with LikeC4's own tooling (\\`npx likec4 start\\`) — but scope them,\n// because computing a view is superlinear in the number of edges and an\n// \\`include *\\` over a whole fleet takes minutes.\n\nspecification {\n element person\n element softwareSystem\n element container\n element database\n}\n\nmodel {\n}\n`;\n\n/**\n * The docs repo's own loam.json. It makes the docs repo self-describing: a\n * command run from inside it (or from any directory under it) finds this file\n * first and resolves the fleet to the repo it is standing in, instead of\n * walking out to whatever service repo happens to be above.\n *\n * `\".\"` and not an absolute path for the same reason every other docsDir is\n * stored as written: this file is committed and cloned to machines whose\n * directory layout nobody here can predict.\n */\nconst DOCS_SELF_CONFIG = `${JSON.stringify({ docsDir: \".\" }, null, 2)}\\n`;\n\nexport interface ScaffoldResult {\n root: string;\n created: string[];\n}\n\n/** Idempotently create the docs-repo skeleton. Existing files/dirs are left untouched. */\nexport async function scaffoldDocs(docsDir: string): Promise<ScaffoldResult> {\n const root = resolve(docsDir);\n const created: string[] = [];\n\n await mkdir(root, { recursive: true });\n\n for (const dir of DOCS_SUBDIRS) {\n const p = join(root, dir);\n if (!existsSync(p)) {\n await mkdir(p, { recursive: true });\n created.push(p);\n }\n }\n\n // Never overwritten, all three of them — a team's own house rules, their own\n // map and their own config outrank the template. The order here IS the order\n // `init` probes for what it will skip; keep the two lists in step.\n for (const [rel, content] of docsRepoFiles()) {\n const path = join(root, rel);\n if (!existsSync(path)) {\n await writeFile(path, content, \"utf8\");\n created.push(path);\n }\n }\n\n return { root, created };\n}\n\nexport interface DocsRepoFileOptions {\n /**\n * Comment lines prepended to the landscape stub, for a scaffold with a reason\n * of its own to hand over an empty map — `migrate-openspec` stages a docs repo\n * out of a corpus that HAS no topology, and says so where the migrator reads.\n *\n * A parameter and not a second template on purpose. The rest of the stub —\n * how to declare a service, how to bind an edge to an operationId, why there\n * is no `views` block — is the same advice however the repo came to exist, and\n * the migration used to carry its own shorter copy of it: two files that were\n * both correct on the day they were written and could only diverge after.\n */\n landscapePreamble?: string;\n}\n\n/**\n * The files every docs repo starts with, relative to its root, in creation\n * order. `init` scaffolds them directly; `migrate-openspec` stages the same\n * bytes into its target, because the whole point of migrating into a docs repo\n * is that it be the docs repo `loam init` makes.\n *\n * Exported as paths only (via `plannedDocsFiles`) where `init` reports what it\n * will skip — a duplicate list is exactly the drift that made `created +\n * skipped` disagree with reality.\n */\nexport function docsRepoFiles(opts: DocsRepoFileOptions = {}): Array<[string, string]> {\n const preamble = opts.landscapePreamble === undefined ? \"\" : `${opts.landscapePreamble.trimEnd()}\\n//\\n`;\n return [\n // The process contract lives with the docs it describes, so an agent handed\n // only the docs repo still knows the cycle.\n [AGENTS_FILENAME, AGENTS_MD],\n [join(\"architecture\", \"landscape.likec4\"), `${preamble}${LANDSCAPE_STUB}`],\n [\"loam.json\", DOCS_SELF_CONFIG],\n ];\n}\n\n/** Everything `scaffoldDocs(docsDir)` would create, in the order it creates it. */\nexport function plannedDocsFiles(docsDir: string): string[] {\n const root = resolve(docsDir);\n return [\n ...DOCS_SUBDIRS.map((d) => join(root, d)),\n ...docsRepoFiles().map(([rel]) => join(root, rel)),\n ];\n}\n"]}
|