@sdd-method/author-cli 0.0.0-stage → 0.1.0
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/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/author-bin.js +8 -0
- package/dist/author-program.js +99 -0
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +37 -0
- package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
- package/dist/lib/api-surface/analyzers/library.js +95 -0
- package/dist/lib/api-surface/analyzers/serverless.js +336 -0
- package/dist/lib/api-surface/analyzers/spring.js +237 -0
- package/dist/lib/api-surface/analyzers/terraform.js +109 -0
- package/dist/lib/api-surface/detect.js +25 -0
- package/dist/lib/api-surface/emit.js +109 -0
- package/dist/lib/api-surface/file-set.js +43 -0
- package/dist/lib/api-surface/index.js +134 -0
- package/dist/lib/api-surface/types.js +17 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +132 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +70 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/conformance/finding.js +69 -0
- package/dist/lib/conformance/index.js +578 -0
- package/dist/lib/context-pull/index.js +191 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
- package/dist/lib/contracts-availability/index.js +267 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/plan/index.js +69 -0
- package/dist/lib/plan/read-sdd.js +102 -0
- package/dist/lib/plan/render-template.js +84 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scaffold/domain.js +234 -0
- package/dist/lib/scaffold/registry.js +136 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/sdd-index/repo-config.js +245 -0
- package/dist/lib/sdd-reader.js +91 -0
- package/dist/lib/snapshot/advance.js +454 -0
- package/dist/lib/snapshot/diff.js +190 -0
- package/dist/lib/snapshot/fold.js +208 -0
- package/dist/lib/snapshot/land.js +178 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/snapshot/targets.js +169 -0
- package/dist/lib/snapshot/verify.js +91 -0
- package/dist/lib/sync/briefing-merge.js +106 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/api-surface.js +71 -0
- package/dist/verbs/catalogue-check.js +28 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/conformance.js +34 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/contracts.js +34 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/plan.js +42 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/scaffold.js +83 -0
- package/dist/verbs/snapshot.js +134 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// C4 external software systems discoverer — emits `c4_external_systems.csv`
|
|
2
|
+
// rows (per ADR 0155). External systems are C4 L1 peers the ecosystem
|
|
3
|
+
// integrates with but does not own; they are rendered `System_Ext(...)` on
|
|
4
|
+
// L1/L2 diagrams (and occasionally `Container_Ext(...)` for an external
|
|
5
|
+
// system's container). They are NOT c4_containers (not inside your system)
|
|
6
|
+
// and NOT c4_systems (no owning Product).
|
|
7
|
+
//
|
|
8
|
+
// We scan the same L1 + L2 files the c4-systems / c4-containers builders
|
|
9
|
+
// read, and pull every `System_Ext(id, "name", "desc")` and
|
|
10
|
+
// `Container_Ext(id, "name", "tech", "desc")` declaration. The row id
|
|
11
|
+
// derives from the DECLARED diagram entry id — `ext-{slug(entry-id)}`
|
|
12
|
+
// (schema 3.0.0, M-ADR 0203 §4.2) — so identity is the author's declared
|
|
13
|
+
// assertion, the same identity the aggregator adjudicates (M-ADR 0189
|
|
14
|
+
// §4.2). The same declared identity across SDDs collapses to one row at
|
|
15
|
+
// aggregation (the dedup-by-PK exception to ADR 0134). Two entries whose
|
|
16
|
+
// DISPLAY NAMES agree but whose entry ids differ are two rows — the
|
|
17
|
+
// same-entity-two-labels case made visible instead of silently merged;
|
|
18
|
+
// the 2.x name-slug ids map to these via the ext-id migration map.
|
|
19
|
+
//
|
|
20
|
+
// The returned `idByEntryId` map (diagram entry-id → row id) lets the
|
|
21
|
+
// system_external_dependency builder resolve `Rel(...)` endpoints.
|
|
22
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
23
|
+
import { join, sep } from "node:path";
|
|
24
|
+
import { C4_L1_CANDIDATES, SYNDICATED_PLATFORM_L1, findDomainScopedL1s, } from "./c4-l1-candidates.js";
|
|
25
|
+
import { extIdFromEntryId } from "../ext-id.js";
|
|
26
|
+
// `System_Ext(id, "name", "desc")` — no technology argument.
|
|
27
|
+
const SYSTEM_EXT_RE = /System_Ext\s*\(\s*(\w+)\s*,\s*"([^"]+)"\s*(?:,\s*"([^"]*)")?\s*\)/;
|
|
28
|
+
// `Container_Ext(id, "name", "tech", "desc")` — external container.
|
|
29
|
+
const CONTAINER_EXT_RE = /Container_Ext\s*\(\s*(\w+)\s*,\s*"([^"]+)"\s*(?:,\s*"([^"]*)")?\s*(?:,\s*"([^"]*)")?\s*\)/;
|
|
30
|
+
// Shares the candidate list with c4-systems / persona-c4system for DRY (one
|
|
31
|
+
// source of truth — see c4-l1-candidates.ts). NOTE: unlike those two, this
|
|
32
|
+
// builder does NOT pick one file — it UNIONS every existing L1 file (own +
|
|
33
|
+
// domain-scoped + syndicated) plus the L2 files and dedups externals by slug
|
|
34
|
+
// at aggregation, so the candidate ORDER is irrelevant here. The syndicated
|
|
35
|
+
// copy deliberately stays in the union (unlike identity sourcing, M-ADR 0189
|
|
36
|
+
// §4.5): a sibling's own diagram Rel(...) lines may reference entries the
|
|
37
|
+
// syndicated file declares, and dropping it would silently orphan those
|
|
38
|
+
// edges. Its dedup-by-slug makes the extra harvest idempotent at aggregation.
|
|
39
|
+
const L1_CANDIDATES = C4_L1_CANDIDATES;
|
|
40
|
+
const L2_BASENAME = "c4-l2-container.mermaid.md";
|
|
41
|
+
export async function buildC4ExternalSystems(inputs) {
|
|
42
|
+
const files = await collectC4DiagramFiles(inputs.sddPath);
|
|
43
|
+
const rows = [];
|
|
44
|
+
const idByEntryId = new Map();
|
|
45
|
+
const nameByEntryId = new Map();
|
|
46
|
+
const technologyByEntryId = new Map();
|
|
47
|
+
const descriptionByEntryId = new Map();
|
|
48
|
+
const seen = new Map();
|
|
49
|
+
for (const { abs, scope } of files) {
|
|
50
|
+
const content = await readFile(abs, "utf-8");
|
|
51
|
+
for (const ext of parseExternals(content)) {
|
|
52
|
+
// Identity is the declared entry id (schema 3.0.0). An entry id that
|
|
53
|
+
// normalises to nothing cannot key a row — announced, never silent
|
|
54
|
+
// (M-ADR 0133 §4.2c).
|
|
55
|
+
const id = extIdFromEntryId(ext.entryId);
|
|
56
|
+
if (id === "") {
|
|
57
|
+
inputs.findings.add("convention", `c4_external_systems: entry id "${ext.entryId}" ("${ext.name}") normalises to an empty slug — no row emitted; rename the diagram entry id`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
idByEntryId.set(ext.entryId, id);
|
|
61
|
+
if (!nameByEntryId.has(ext.entryId))
|
|
62
|
+
nameByEntryId.set(ext.entryId, ext.name);
|
|
63
|
+
if (!technologyByEntryId.has(ext.entryId) && ext.technology !== "") {
|
|
64
|
+
technologyByEntryId.set(ext.entryId, ext.technology);
|
|
65
|
+
}
|
|
66
|
+
if (!descriptionByEntryId.has(ext.entryId) && ext.description !== "") {
|
|
67
|
+
descriptionByEntryId.set(ext.entryId, ext.description);
|
|
68
|
+
}
|
|
69
|
+
const existing = seen.get(id);
|
|
70
|
+
if (existing) {
|
|
71
|
+
// Same declared identity referenced again — first row wins; flag
|
|
72
|
+
// attribute conflicts so authors can reconcile. Name joins the
|
|
73
|
+
// checked set at 3.0.0: with the id keyed on the entry id, one
|
|
74
|
+
// identity under two display names is exactly this conflict.
|
|
75
|
+
if ((ext.name !== "" &&
|
|
76
|
+
existing["name"] !== "" &&
|
|
77
|
+
existing["name"] !== ext.name) ||
|
|
78
|
+
(ext.technology !== "" &&
|
|
79
|
+
existing["technology"] !== "" &&
|
|
80
|
+
existing["technology"] !== ext.technology) ||
|
|
81
|
+
(ext.description !== "" &&
|
|
82
|
+
existing["description"] !== "" &&
|
|
83
|
+
existing["description"] !== ext.description)) {
|
|
84
|
+
inputs.findings.add("convention", `c4_external_systems: id "${id}" declared with conflicting attributes within ${inputs.manifest.sddId} — first-seen kept`);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const row = {
|
|
89
|
+
id,
|
|
90
|
+
name: ext.name,
|
|
91
|
+
technology: ext.technology,
|
|
92
|
+
description: ext.description,
|
|
93
|
+
// Adjudication provenance (M-ADR 0189 §4.1/§4.2, schema 2.30.0):
|
|
94
|
+
// the declared entry id is the identity the aggregator resolves;
|
|
95
|
+
// the scope records whether the first-seen declaration was
|
|
96
|
+
// ecosystem-context (l1) or scope-relative (l2).
|
|
97
|
+
declared_id: ext.entryId,
|
|
98
|
+
source_scope: scope,
|
|
99
|
+
source_sdd_id: "",
|
|
100
|
+
};
|
|
101
|
+
seen.set(id, row);
|
|
102
|
+
rows.push(row);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
rows.sort((a, b) => String(a["id"]).localeCompare(String(b["id"])));
|
|
106
|
+
return { rows, idByEntryId, nameByEntryId, technologyByEntryId, descriptionByEntryId };
|
|
107
|
+
}
|
|
108
|
+
function parseExternals(content) {
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
111
|
+
const line = raw.trim();
|
|
112
|
+
const se = SYSTEM_EXT_RE.exec(line);
|
|
113
|
+
if (se) {
|
|
114
|
+
out.push({
|
|
115
|
+
entryId: se[1] ?? "",
|
|
116
|
+
name: se[2] ?? "",
|
|
117
|
+
technology: "",
|
|
118
|
+
description: se[3] ?? "",
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const ce = CONTAINER_EXT_RE.exec(line);
|
|
123
|
+
if (ce) {
|
|
124
|
+
out.push({
|
|
125
|
+
entryId: ce[1] ?? "",
|
|
126
|
+
name: ce[2] ?? "",
|
|
127
|
+
technology: ce[3] ?? "",
|
|
128
|
+
description: ce[4] ?? "",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every C4 diagram file an SDD can carry (all L1 candidates incl.
|
|
136
|
+
* domain-scoped, plus every L2), scope-tagged. Shared with
|
|
137
|
+
* system-external-dependency (F11c: that builder kept a private,
|
|
138
|
+
* pre-0.30.1-ordered copy MISSING the domain-scoped path — the fourth
|
|
139
|
+
* divergent copy of this list, and a silent drop inside adjudication:
|
|
140
|
+
* a Rel in a domain-scoped L1 was never harvested, so a converted
|
|
141
|
+
* candidate's relationship was recorded nowhere).
|
|
142
|
+
*/
|
|
143
|
+
export async function collectC4DiagramFiles(sddPath) {
|
|
144
|
+
const out = [];
|
|
145
|
+
for (const rel of L1_CANDIDATES) {
|
|
146
|
+
const abs = join(sddPath, rel);
|
|
147
|
+
if (await isFile(abs)) {
|
|
148
|
+
out.push({ abs, scope: "l1", syndicated: rel === SYNDICATED_PLATFORM_L1 });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Domain-scoped L1s (M-ADR 0189 §4.5) — union, like every other candidate.
|
|
152
|
+
for (const rel of await findDomainScopedL1s(sddPath)) {
|
|
153
|
+
out.push({ abs: join(sddPath, rel), scope: "l1", syndicated: false });
|
|
154
|
+
}
|
|
155
|
+
// Per-domain L2 files.
|
|
156
|
+
const domainsDir = join(sddPath, "docs", "domains");
|
|
157
|
+
try {
|
|
158
|
+
const entries = (await readdir(domainsDir, { withFileTypes: true }))
|
|
159
|
+
.filter((e) => e.isDirectory())
|
|
160
|
+
.map((e) => e.name)
|
|
161
|
+
.sort((a, b) => a.localeCompare(b));
|
|
162
|
+
for (const name of entries) {
|
|
163
|
+
const abs = join(sddPath, "docs", "domains", name, "architecture", "diagrams", L2_BASENAME);
|
|
164
|
+
if (await isFile(abs))
|
|
165
|
+
out.push({ abs, scope: "l2", syndicated: false });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (err.code !== "ENOENT")
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
// Platform-wide + per-product L2.
|
|
173
|
+
for (const rel of [
|
|
174
|
+
join("docs", "platform", "diagrams", L2_BASENAME),
|
|
175
|
+
join("docs", L2_BASENAME),
|
|
176
|
+
]) {
|
|
177
|
+
const abs = join(sddPath, rel);
|
|
178
|
+
if (await isFile(abs))
|
|
179
|
+
out.push({ abs, scope: "l2", syndicated: false });
|
|
180
|
+
}
|
|
181
|
+
void sep;
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
async function isFile(path) {
|
|
185
|
+
try {
|
|
186
|
+
return (await stat(path)).isFile();
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
if (err.code === "ENOENT")
|
|
190
|
+
return false;
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=c4-external-systems.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Shared L1 context-diagram path resolution for the L1-reading C4 builders
|
|
2
|
+
// (c4-systems, persona-c4system, c4-external-systems).
|
|
3
|
+
//
|
|
4
|
+
// **Own-L1-first, syndicated-for-platform-root-only** (M-ADR 0189 §4.5).
|
|
5
|
+
// Under federation a sibling SDD typically carries a syndicated copy of the
|
|
6
|
+
// platform-root's L1 at `docs/platform/diagrams/c4-l1-context.mermaid.md`
|
|
7
|
+
// for cross-reference, AND may author its own L1. The sibling's own L1 must
|
|
8
|
+
// outrank the syndicated copy — and, per F7/WS8, the syndicated copy must
|
|
9
|
+
// not source a sibling's identity AT ALL: on two measured estates roughly
|
|
10
|
+
// half of all systems carried the platform's name and description because
|
|
11
|
+
// the fallback fired for every sibling that authored no own L1. A sibling
|
|
12
|
+
// with no own L1 resolves to "" — the caller derives the name from the
|
|
13
|
+
// manifest and emits a coverage finding (the M-ADR 0133 §4.2c rule, applied
|
|
14
|
+
// to columns instead of rows).
|
|
15
|
+
//
|
|
16
|
+
// This module is shared so the L1-reading builders cannot keep divergent
|
|
17
|
+
// copies. They drifted before: the c4-systems L1-priority fix (sdd-cli
|
|
18
|
+
// 0.30.1) reversed only c4-systems' private copy; persona-c4system kept the
|
|
19
|
+
// old syndicated-first order, so `persona_c4system` matched the platform's
|
|
20
|
+
// personas instead of the sibling's. c4-external-systems UNIONS every
|
|
21
|
+
// existing L1 file rather than picking one, so candidate order is irrelevant
|
|
22
|
+
// there; it shares these paths only to avoid a divergent copy.
|
|
23
|
+
// See sdd-method-gov/coordination/persona-c4system-l1-priority-bug-coordination.md.
|
|
24
|
+
import { readdir, stat } from "node:fs/promises";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
/** Own-L1 candidates usable by ANY SDD, in priority order. */
|
|
27
|
+
export const OWN_L1_CANDIDATES = [
|
|
28
|
+
"docs/c4-l1.md", // sibling's own L1 — highest priority
|
|
29
|
+
"docs/application/c4-l1.md",
|
|
30
|
+
"docs/diagrams/c4-l1-context.mermaid.md",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* The syndicated platform L1. For the platform-root SDD this file IS its own
|
|
34
|
+
* L1 (lowest-priority own candidate). For every other SDD it may not source
|
|
35
|
+
* identity (M-ADR 0189 §4.5).
|
|
36
|
+
*/
|
|
37
|
+
export const SYNDICATED_PLATFORM_L1 = "docs/platform/diagrams/c4-l1-context.mermaid.md";
|
|
38
|
+
/**
|
|
39
|
+
* Full candidate list, retained for union-style consumers
|
|
40
|
+
* (c4-external-systems) where order and ownership are irrelevant.
|
|
41
|
+
*/
|
|
42
|
+
export const C4_L1_CANDIDATES = [
|
|
43
|
+
...OWN_L1_CANDIDATES,
|
|
44
|
+
SYNDICATED_PLATFORM_L1,
|
|
45
|
+
];
|
|
46
|
+
const DOMAIN_L1_REL = ["architecture", "diagrams", "c4-l1-context.mermaid.md"];
|
|
47
|
+
/**
|
|
48
|
+
* Domain-scoped L1s: `docs/domains/<slug>/architecture/diagrams/
|
|
49
|
+
* c4-l1-context.mermaid.md` — the natural home for a promoted subsystem with
|
|
50
|
+
* one eponymous domain (M-ADR 0189 §4.5). Returned as repo-relative paths,
|
|
51
|
+
* sorted by domain slug.
|
|
52
|
+
*/
|
|
53
|
+
export async function findDomainScopedL1s(sddPath) {
|
|
54
|
+
const domainsDir = join(sddPath, "docs", "domains");
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = (await readdir(domainsDir, { withFileTypes: true }))
|
|
58
|
+
.filter((e) => e.isDirectory())
|
|
59
|
+
.map((e) => e.name)
|
|
60
|
+
.sort((a, b) => a.localeCompare(b));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err.code === "ENOENT")
|
|
64
|
+
return [];
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const name of entries) {
|
|
69
|
+
const rel = join("docs", "domains", name, ...DOMAIN_L1_REL);
|
|
70
|
+
if (await isFile(join(sddPath, rel)))
|
|
71
|
+
out.push(rel);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the L1 diagram that may source THIS SDD's identity (name,
|
|
77
|
+
* description, personas): own candidates in priority order, then a unique
|
|
78
|
+
* domain-scoped L1, then — for the platform root only — the syndicated
|
|
79
|
+
* platform path (which is its own L1).
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveIdentityL1(sddPath, isPlatformRoot) {
|
|
82
|
+
for (const rel of OWN_L1_CANDIDATES) {
|
|
83
|
+
if (await isFile(join(sddPath, rel))) {
|
|
84
|
+
return { path: rel, ambiguousDomainL1Count: 0 };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const domainL1s = await findDomainScopedL1s(sddPath);
|
|
88
|
+
if (domainL1s.length === 1) {
|
|
89
|
+
return { path: domainL1s[0], ambiguousDomainL1Count: 0 };
|
|
90
|
+
}
|
|
91
|
+
const ambiguous = domainL1s.length > 1 ? domainL1s.length : 0;
|
|
92
|
+
if (isPlatformRoot && (await isFile(join(sddPath, SYNDICATED_PLATFORM_L1)))) {
|
|
93
|
+
return { path: SYNDICATED_PLATFORM_L1, ambiguousDomainL1Count: ambiguous };
|
|
94
|
+
}
|
|
95
|
+
return { path: "", ambiguousDomainL1Count: ambiguous };
|
|
96
|
+
}
|
|
97
|
+
async function isFile(path) {
|
|
98
|
+
try {
|
|
99
|
+
return (await stat(path)).isFile();
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.code === "ENOENT")
|
|
103
|
+
return false;
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=c4-l1-candidates.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// C4 L1 systems discoverer — emits at most one `c4_systems.csv` row.
|
|
2
|
+
//
|
|
3
|
+
// Per ADR 0134's federation model, each SDD emits its OWN c4_systems
|
|
4
|
+
// row when its manifest category is architectural. Categories map to
|
|
5
|
+
// the c4_system_kind enum (canonical-schema.yaml):
|
|
6
|
+
// platform-root → platform
|
|
7
|
+
// platform-subsystem → subsystem (M-ADR 0170 §4.2 promotion; schema 2.28.0)
|
|
8
|
+
// application-product → application
|
|
9
|
+
// integration-provider → integration
|
|
10
|
+
// Non-architectural categories (integration-umbrella, support-repo) emit
|
|
11
|
+
// zero rows — with a coverage finding, never silently (M-ADR 0133 §4.2c).
|
|
12
|
+
//
|
|
13
|
+
// The id is `{sddId}-system` to match the products row's product_id FK.
|
|
14
|
+
// Description is parsed from the L1 mermaid `System(...)` line when an
|
|
15
|
+
// identity-sourcing L1 exists (M-ADR 0189 §4.5 — own L1, or a unique
|
|
16
|
+
// domain-scoped L1, or the syndicated platform path for the platform root
|
|
17
|
+
// only), otherwise left empty (visible coverage gap) with the name derived
|
|
18
|
+
// from the manifest.
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { resolveIdentityL1 } from "./c4-l1-candidates.js";
|
|
22
|
+
const KIND_BY_CATEGORY = {
|
|
23
|
+
"platform-root": "platform",
|
|
24
|
+
"platform-subsystem": "subsystem",
|
|
25
|
+
"application-product": "application",
|
|
26
|
+
"integration-provider": "integration",
|
|
27
|
+
};
|
|
28
|
+
// Match `System(id, "name", "description")` — the L1 root-system entry.
|
|
29
|
+
// Permissive about whitespace and the optional description argument.
|
|
30
|
+
const SYSTEM_RE = /^System\s*\(\s*\w+\s*,\s*"([^"]+)"\s*(?:,\s*"([^"]*)")?\s*\)/;
|
|
31
|
+
export async function buildC4Systems(inputs) {
|
|
32
|
+
const kind = KIND_BY_CATEGORY[inputs.manifest.category];
|
|
33
|
+
if (kind === undefined) {
|
|
34
|
+
// Non-architectural (or unmapped) — no system row, but never silently:
|
|
35
|
+
// a builder returning [] without a finding is indistinguishable from
|
|
36
|
+
// one that found nothing to return (M-ADR 0133 §4.2c no-silent-drop).
|
|
37
|
+
inputs.findings.add("coverage", `c4_systems: category '${inputs.manifest.category}' has no system kind; no row emitted`);
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const { diagramPath, name, description, ambiguousDomainL1Count } = await findL1(inputs.sddPath, inputs.manifest.category === "platform-root");
|
|
41
|
+
if (ambiguousDomainL1Count > 0) {
|
|
42
|
+
inputs.findings.add("convention", `c4_systems: ${ambiguousDomainL1Count} domain-scoped L1 diagrams found for ${inputs.manifest.sddId} — ambiguous, none used as the SDD's L1`);
|
|
43
|
+
}
|
|
44
|
+
if (diagramPath === "") {
|
|
45
|
+
inputs.findings.add("coverage", `c4_systems: no L1 diagram found for ${inputs.manifest.sddId} — diagram_path empty`);
|
|
46
|
+
}
|
|
47
|
+
const sddId = inputs.manifest.sddId;
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
id: inputs.manifest.c4SystemId ?? `${sddId}-system`,
|
|
51
|
+
name: name !== "" ? name : titleCase(sddId),
|
|
52
|
+
product_id: sddId,
|
|
53
|
+
kind,
|
|
54
|
+
diagram_path: diagramPath,
|
|
55
|
+
description,
|
|
56
|
+
source_sdd_id: "",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
async function findL1(sddPath, isPlatformRoot) {
|
|
61
|
+
const resolved = await resolveIdentityL1(sddPath, isPlatformRoot);
|
|
62
|
+
if (resolved.path === "") {
|
|
63
|
+
return {
|
|
64
|
+
diagramPath: "",
|
|
65
|
+
name: "",
|
|
66
|
+
description: "",
|
|
67
|
+
ambiguousDomainL1Count: resolved.ambiguousDomainL1Count,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const content = await readFile(join(sddPath, resolved.path), "utf-8");
|
|
71
|
+
const parsed = parseSystemLine(content);
|
|
72
|
+
return {
|
|
73
|
+
diagramPath: resolved.path,
|
|
74
|
+
name: parsed?.name ?? "",
|
|
75
|
+
description: parsed?.description ?? "",
|
|
76
|
+
ambiguousDomainL1Count: resolved.ambiguousDomainL1Count,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function parseSystemLine(content) {
|
|
80
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
81
|
+
const line = raw.trim();
|
|
82
|
+
if (line.startsWith("System_Ext") || line.startsWith("System_Boundary")) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const m = SYSTEM_RE.exec(line);
|
|
86
|
+
if (m) {
|
|
87
|
+
return { name: m[1] ?? "", description: m[2] ?? "" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function titleCase(slug) {
|
|
93
|
+
return slug
|
|
94
|
+
.split("-")
|
|
95
|
+
.map((p) => (p.length === 0 ? p : p[0].toUpperCase() + p.slice(1)))
|
|
96
|
+
.join(" ");
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=c4-systems.js.map
|