@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,103 @@
|
|
|
1
|
+
// adr_supersedes edge builder — emits one row per (superseding, superseded)
|
|
2
|
+
// ADR pair where either side declares the relationship:
|
|
3
|
+
// - the superseding ADR's `Supersedes:` text references a known ADR id, or
|
|
4
|
+
// - the superseded ADR's `Superseded By:` text references a known ADR id
|
|
5
|
+
// (the ADR 0174 §4.5 lazy-migration case: distributed reference ADRs are
|
|
6
|
+
// adopter-agnostic and cannot name adopter ADR numbers, so only the
|
|
7
|
+
// superseded local copy carries the link — including the cross-repo
|
|
8
|
+
// `<repo>#9000` form, whose number resolves locally once the bundle that
|
|
9
|
+
// ships ADR 9000 is pinned).
|
|
10
|
+
//
|
|
11
|
+
// Only rows where BOTH endpoints resolve to known ADR ids are emitted —
|
|
12
|
+
// the canonical schema's FKs on this edge are required+internal.
|
|
13
|
+
//
|
|
14
|
+
// Also computes the inverse (`superseded_by_id`) backfill values that the
|
|
15
|
+
// orchestrator stitches into adrs.csv after this builder runs.
|
|
16
|
+
const ADR_REF_RE = /\bADR[\s\-_]?(\d{3,4})\b/gi;
|
|
17
|
+
// `Superseded By:` additionally accepts the ADR 0140 cross-repo reference
|
|
18
|
+
// form `<repo>#9000` — the lazy-migration case, where the superseding
|
|
19
|
+
// record lives in a distributed bundle. Procedural citations of the
|
|
20
|
+
// migration rule itself ("per sdd-method#0174 §4.5") are stripped first
|
|
21
|
+
// so they never read as supersession targets.
|
|
22
|
+
const ADR_REF_WITH_HASH_RE = /(?:\bADR[\s\-_]?|#)(\d{3,4})\b/gi;
|
|
23
|
+
const PROCEDURAL_CITATION_RE = /\bper\s+[\w./-]*#\d{3,4}(?:\s*§[\d.]+)?/gi;
|
|
24
|
+
export function buildAdrSupersedes(inputs) {
|
|
25
|
+
const knownIds = new Set(inputs.adrs.map((a) => a.id));
|
|
26
|
+
const rows = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const supersededByIndex = new Map();
|
|
29
|
+
for (const adr of inputs.adrs) {
|
|
30
|
+
if (adr.supersedesRaw === "")
|
|
31
|
+
continue;
|
|
32
|
+
const refs = extractAdrRefs(adr.supersedesRaw);
|
|
33
|
+
for (const supersededId of refs) {
|
|
34
|
+
if (supersededId === adr.id)
|
|
35
|
+
continue; // self-supersession is nonsense
|
|
36
|
+
if (!knownIds.has(supersededId)) {
|
|
37
|
+
inputs.findings.add("orphan", `${adr.docPath}: Supersedes references "${supersededId}" but no such ADR exists`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const id = `${adr.id}__${supersededId}`;
|
|
41
|
+
if (seen.has(id))
|
|
42
|
+
continue;
|
|
43
|
+
seen.add(id);
|
|
44
|
+
rows.push({
|
|
45
|
+
id,
|
|
46
|
+
superseding_adr_id: adr.id,
|
|
47
|
+
superseded_adr_id: supersededId,
|
|
48
|
+
});
|
|
49
|
+
// Last writer wins on the inverse index — multiple superseders for
|
|
50
|
+
// the same ADR is rare; if it happens we surface a finding.
|
|
51
|
+
const prior = supersededByIndex.get(supersededId);
|
|
52
|
+
if (prior !== undefined && prior !== adr.id) {
|
|
53
|
+
inputs.findings.add("divergence", `ADR ${supersededId} is superseded by both ${prior} and ${adr.id}`);
|
|
54
|
+
}
|
|
55
|
+
supersededByIndex.set(supersededId, adr.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Second pass: edges declared from the superseded side (`Superseded By:`).
|
|
59
|
+
for (const adr of inputs.adrs) {
|
|
60
|
+
if (adr.supersededByRaw === "" || /^none$/i.test(adr.supersededByRaw.trim()))
|
|
61
|
+
continue;
|
|
62
|
+
const refs = extractAdrRefs(adr.supersededByRaw.replace(PROCEDURAL_CITATION_RE, " "), ADR_REF_WITH_HASH_RE);
|
|
63
|
+
for (const supersedingId of refs) {
|
|
64
|
+
if (supersedingId === adr.id)
|
|
65
|
+
continue;
|
|
66
|
+
if (!knownIds.has(supersedingId)) {
|
|
67
|
+
inputs.findings.add("orphan", `${adr.docPath}: Superseded By references "${supersedingId}" but no such ADR exists locally — edge skipped (pin the bundle that ships it, or fix the reference)`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const id = `${supersedingId}__${adr.id}`;
|
|
71
|
+
if (seen.has(id))
|
|
72
|
+
continue; // already declared from the Supersedes side
|
|
73
|
+
seen.add(id);
|
|
74
|
+
rows.push({
|
|
75
|
+
id,
|
|
76
|
+
superseding_adr_id: supersedingId,
|
|
77
|
+
superseded_adr_id: adr.id,
|
|
78
|
+
});
|
|
79
|
+
const prior = supersededByIndex.get(adr.id);
|
|
80
|
+
if (prior !== undefined && prior !== supersedingId) {
|
|
81
|
+
inputs.findings.add("divergence", `ADR ${adr.id} is superseded by both ${prior} and ${supersedingId}`);
|
|
82
|
+
}
|
|
83
|
+
supersededByIndex.set(adr.id, supersedingId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { rows, supersededByIndex };
|
|
87
|
+
}
|
|
88
|
+
function extractAdrRefs(raw, re = ADR_REF_RE) {
|
|
89
|
+
const out = [];
|
|
90
|
+
let m;
|
|
91
|
+
// Reset regex state between calls.
|
|
92
|
+
re.lastIndex = 0;
|
|
93
|
+
while ((m = re.exec(raw)) !== null) {
|
|
94
|
+
if (m[1] === undefined)
|
|
95
|
+
continue;
|
|
96
|
+
const num = parseInt(m[1], 10);
|
|
97
|
+
if (!Number.isFinite(num))
|
|
98
|
+
continue;
|
|
99
|
+
out.push(`ADR-${String(num).padStart(4, "0")}`);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=adr-supersedes.js.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// ADR discoverer — emits `adrs.csv` rows from every `adr/*.md` file
|
|
2
|
+
// under `docs/`.
|
|
3
|
+
//
|
|
4
|
+
// Source paths discovered:
|
|
5
|
+
// - docs/platform/adr/ADR XXXX: Title.md (platform-scoped)
|
|
6
|
+
// - docs/method/adr/ADR XXXX: Title.md (method-scoped → domain_id EMPTY, schema 3.10.0)
|
|
7
|
+
// - docs/method/platform-adr/ADR XXXX: Title.md (method-scoped; the frozen
|
|
8
|
+
// platform-foundational 0001-0099 band per M-ADR 0140)
|
|
9
|
+
// - docs/domains/<domain>/architecture/adr/ADR XXXX: Title.md
|
|
10
|
+
//
|
|
11
|
+
// Per ADR 0136 the method-scope ADRs are owned by the platform-root in
|
|
12
|
+
// the federation chain (the highest-up SDD that holds them via the seed-
|
|
13
|
+
// file distribution). App and integration profile SDDs skip emission of
|
|
14
|
+
// method-scope ADRs so the aggregated catalogue carries each method ADR
|
|
15
|
+
// exactly once with an unambiguous source_sdd_id.
|
|
16
|
+
//
|
|
17
|
+
// File-content extraction (best-effort, no failure on missing fields):
|
|
18
|
+
// - **Status:** value
|
|
19
|
+
// - **Date:** YYYY-MM-DD
|
|
20
|
+
// - **Supersedes:** raw text (captured into supersedes_raw for the
|
|
21
|
+
// adr_supersedes builder to parse)
|
|
22
|
+
//
|
|
23
|
+
// ID canonicalisation: filename stem `ADR XXXX[: Title]` → `ADR-{0-padded}`.
|
|
24
|
+
// Title = the portion after the colon when present, else the file stem
|
|
25
|
+
// minus the prefix.
|
|
26
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
27
|
+
import { join, relative, sep } from "node:path";
|
|
28
|
+
import { ORIGIN_PREFIX_SRC } from "../../adr-origin.js";
|
|
29
|
+
const PROFILES_THAT_OWN_METHOD_SCOPE = new Set([
|
|
30
|
+
"platform-profile",
|
|
31
|
+
]);
|
|
32
|
+
const CATEGORIES_THAT_DO_NOT_OWN_METHOD_SCOPE = new Set([
|
|
33
|
+
"platform-subsystem",
|
|
34
|
+
]);
|
|
35
|
+
// Dual-resolution (M-ADR 0140 §4.1 / M-ADR 0186): the durable identity is the
|
|
36
|
+
// NUMBER, not the origin prefix. A file named `ADR NNNN` (bare/legacy), or any
|
|
37
|
+
// canonical prefix — `M-ADR`, `RA-ADR`, `P-ADR`, `D-ADR` — all resolve to the
|
|
38
|
+
// same `ADR-NNNN` id. The prefix set is composed from the shared source so a new
|
|
39
|
+
// tier is a one-line change in adr-origin.ts.
|
|
40
|
+
const FILENAME_RE = new RegExp(`^${ORIGIN_PREFIX_SRC}ADR[\\s-]?(\\d{3,4})(?:[:\\s-]+(.*))?$`, "i");
|
|
41
|
+
export async function buildAdrs(inputs) {
|
|
42
|
+
const docsDir = join(inputs.sddPath, "docs");
|
|
43
|
+
const files = await collectAdrFiles(docsDir);
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const parsed = [];
|
|
46
|
+
const ownsMethodScope = PROFILES_THAT_OWN_METHOD_SCOPE.has(inputs.profile) &&
|
|
47
|
+
!CATEGORIES_THAT_DO_NOT_OWN_METHOD_SCOPE.has(inputs.category ?? "");
|
|
48
|
+
let methodScopeSkipCount = 0;
|
|
49
|
+
for (const file of files.sort()) {
|
|
50
|
+
const stem = baseStem(file);
|
|
51
|
+
const m = FILENAME_RE.exec(stem);
|
|
52
|
+
if (!m || m[1] === undefined) {
|
|
53
|
+
// Template file (e.g. `ADR {0000}: ...`) or non-ADR markdown — skip silently.
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const num = parseInt(m[1], 10);
|
|
57
|
+
if (!Number.isFinite(num))
|
|
58
|
+
continue;
|
|
59
|
+
const adrId = `ADR-${String(num).padStart(4, "0")}`;
|
|
60
|
+
if (seen.has(adrId)) {
|
|
61
|
+
inputs.findings.add("divergence", `${relative(inputs.sddPath, file)}: ADR id ${adrId} already emitted by an earlier file`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const relPath = relative(inputs.sddPath, file);
|
|
65
|
+
const { domainId, scope } = scopeFromPath(relPath);
|
|
66
|
+
if ((scope === "method" || scope === "platform") && !ownsMethodScope) {
|
|
67
|
+
// Per ADR 0136: method- and platform-scope ADRs are emitted only by
|
|
68
|
+
// the SDD that owns the platform scope (platform-profile in
|
|
69
|
+
// adopting ecosystems). App and integration SDDs hold seed-file
|
|
70
|
+
// copies on disk but do not contribute them to the federated
|
|
71
|
+
// catalogue.
|
|
72
|
+
methodScopeSkipCount++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
seen.add(adrId);
|
|
76
|
+
const titleFromFilename = (m[2] ?? "").trim() || stem;
|
|
77
|
+
const text = await readFile(file, "utf-8");
|
|
78
|
+
const title = extractH1Title(text) || titleFromFilename;
|
|
79
|
+
const status = extractField(text, "Status") || "Accepted";
|
|
80
|
+
const decisionDate = extractField(text, "Date") || "";
|
|
81
|
+
const supersedesRaw = extractField(text, "Supersedes") || "";
|
|
82
|
+
const supersededByRaw = extractField(text, "Superseded By") || "";
|
|
83
|
+
if (scope === "domain" && !inputs.knownDomainIds.has(domainId)) {
|
|
84
|
+
inputs.findings.add("orphan", `${relPath}: ADR is in domain "${domainId}" but no docs/domains/${domainId}/ exists`);
|
|
85
|
+
}
|
|
86
|
+
// Method-scoped ADRs are cross-cutting governance and belong to no
|
|
87
|
+
// domain: domain_id stays EMPTY (schema 3.10.0, M-ADR 0136 §4.3
|
|
88
|
+
// amendment). The former pin to `platform` resolved only where the
|
|
89
|
+
// root happened to own a domain of that name and dangled everywhere
|
|
90
|
+
// else — every method ADR row in a packaged-SaaS root failed FK.
|
|
91
|
+
parsed.push({
|
|
92
|
+
id: adrId,
|
|
93
|
+
title,
|
|
94
|
+
status,
|
|
95
|
+
domainId,
|
|
96
|
+
decisionDate,
|
|
97
|
+
docPath: relPath,
|
|
98
|
+
supersedesRaw,
|
|
99
|
+
supersededByRaw,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (methodScopeSkipCount > 0) {
|
|
103
|
+
inputs.findings.add("coverage", `${methodScopeSkipCount} method/platform-scope ADR(s) skipped — emitted by ecosystem root only per ADR 0136`);
|
|
104
|
+
}
|
|
105
|
+
const rows = parsed.map((a) => ({
|
|
106
|
+
id: a.id,
|
|
107
|
+
title: a.title,
|
|
108
|
+
status: a.status,
|
|
109
|
+
domain_id: a.domainId,
|
|
110
|
+
decision_date: a.decisionDate,
|
|
111
|
+
doc_path: a.docPath,
|
|
112
|
+
superseded_by_id: "", // populated in a backfill pass after adr_supersedes builds
|
|
113
|
+
inference_confidence: "",
|
|
114
|
+
supersedes_raw: a.supersedesRaw,
|
|
115
|
+
source_sdd_id: "",
|
|
116
|
+
}));
|
|
117
|
+
return { rows, parsed };
|
|
118
|
+
}
|
|
119
|
+
function baseStem(absPath) {
|
|
120
|
+
const base = absPath.split(sep).slice(-1)[0] ?? "";
|
|
121
|
+
return base.replace(/\.md$/i, "");
|
|
122
|
+
}
|
|
123
|
+
function scopeFromPath(relPath) {
|
|
124
|
+
const parts = relPath.split(sep);
|
|
125
|
+
const domainsIdx = parts.indexOf("domains");
|
|
126
|
+
if (domainsIdx !== -1 && domainsIdx + 1 < parts.length) {
|
|
127
|
+
return { domainId: parts[domainsIdx + 1] ?? "", scope: "domain" };
|
|
128
|
+
}
|
|
129
|
+
if (parts.includes("method")) {
|
|
130
|
+
return { domainId: "", scope: "method" };
|
|
131
|
+
}
|
|
132
|
+
return { domainId: "platform", scope: "platform" };
|
|
133
|
+
}
|
|
134
|
+
function extractH1Title(text) {
|
|
135
|
+
for (const line of text.split(/\r?\n/)) {
|
|
136
|
+
const t = line.trim();
|
|
137
|
+
if (t === "")
|
|
138
|
+
continue;
|
|
139
|
+
if (!t.startsWith("#"))
|
|
140
|
+
return "";
|
|
141
|
+
// `# ADR 0001: Title` → `Title`; otherwise return the heading text.
|
|
142
|
+
// Accepts any canonical origin prefix (M-/RA-/P-/D-) — dual-resolution.
|
|
143
|
+
const stripped = t.replace(/^#+\s*/, "").trim();
|
|
144
|
+
const adrPrefix = new RegExp(`^${ORIGIN_PREFIX_SRC}ADR[\\s-]?\\d+(?:[:\\s-]+(.*))?$`, "i").exec(stripped);
|
|
145
|
+
if (adrPrefix) {
|
|
146
|
+
return (adrPrefix[1] ?? "").trim();
|
|
147
|
+
}
|
|
148
|
+
return stripped;
|
|
149
|
+
}
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Extract a value from a metadata bullet of the form:
|
|
154
|
+
* * **Field:** value
|
|
155
|
+
* - **Field:** value
|
|
156
|
+
* **Field:** value
|
|
157
|
+
* Returns the trimmed value, or empty string if not found.
|
|
158
|
+
*/
|
|
159
|
+
function extractField(text, field) {
|
|
160
|
+
const re = new RegExp(`^\\s*[*\\-]?\\s*\\*\\*${escapeRegex(field)}:\\*\\*\\s*(.*?)\\s*$`, "im");
|
|
161
|
+
const m = re.exec(text);
|
|
162
|
+
if (!m || m[1] === undefined)
|
|
163
|
+
return "";
|
|
164
|
+
return m[1].trim();
|
|
165
|
+
}
|
|
166
|
+
function escapeRegex(s) {
|
|
167
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Directory names that hold ADR files. An EXPLICIT SET, not a pattern.
|
|
171
|
+
*
|
|
172
|
+
* `platform-adr` was invisible until 2026-08-06 because this walk tested for a
|
|
173
|
+
* directory named exactly "adr" — so `docs/method/platform-adr/` was recursed
|
|
174
|
+
* into and its files were never collected. Measured at discovery: 14 files
|
|
175
|
+
* present, 0 rows, in EVERY SDD of both reference estates (~1,414 files), while
|
|
176
|
+
* the directory is bundle payload and its ADRs are cited, superseded and scored
|
|
177
|
+
* like any other. One estate held 13 assessments of them, which read as orphaned
|
|
178
|
+
* scores and were nearly withdrawn as ADRs that no longer existed.
|
|
179
|
+
*
|
|
180
|
+
* Named rather than matched (`endsWith("adr")`, `/adr$/`) on purpose: a pattern
|
|
181
|
+
* here would also swallow any future directory an adopter happens to name
|
|
182
|
+
* `<something>-adr`, and this file has just paid for one loose match.
|
|
183
|
+
*/
|
|
184
|
+
const ADR_DIR_NAMES = new Set(["adr", "platform-adr"]);
|
|
185
|
+
async function collectAdrFiles(docsDir) {
|
|
186
|
+
const out = [];
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = await readdir(docsDir, { withFileTypes: true });
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
const code = err.code;
|
|
193
|
+
if (code === "ENOENT")
|
|
194
|
+
return [];
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
for (const entry of entries) {
|
|
198
|
+
const full = join(docsDir, entry.name);
|
|
199
|
+
if (entry.isDirectory()) {
|
|
200
|
+
if (ADR_DIR_NAMES.has(entry.name)) {
|
|
201
|
+
// Walk all files in the adr/ directory (no nesting expected).
|
|
202
|
+
const inner = await readdir(full, { withFileTypes: true });
|
|
203
|
+
for (const f of inner) {
|
|
204
|
+
if (f.isFile() && f.name.endsWith(".md") && !isTemplate(f.name)) {
|
|
205
|
+
out.push(join(full, f.name));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
out.push(...(await collectAdrFiles(full)));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
function isTemplate(filename) {
|
|
217
|
+
// Excludes template files like `ADR {0000}: {Short, Descriptive Title}.md`.
|
|
218
|
+
return /[{}]/.test(filename);
|
|
219
|
+
}
|
|
220
|
+
/** Drop-in stat used during testing — unused at runtime but kept exported for symmetry. */
|
|
221
|
+
export const _statForTest = stat;
|
|
222
|
+
//# sourceMappingURL=adrs.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// application_conformance_coverage builder (M-ADR 0191 §4.3 overlay, WS4).
|
|
2
|
+
//
|
|
3
|
+
// Emits one row per conformance tag the product declares about its archetype's
|
|
4
|
+
// conformance expectations: `status=covered` (its suite exercises the tag) or
|
|
5
|
+
// `status=deviated` (deliberately not covered, with a rationale). Built per-SDD
|
|
6
|
+
// from the manifest's `conformance` block so the AGGREGATION-tier conformance
|
|
7
|
+
// gate can read it — the gate itself runs at aggregation, where the reference-
|
|
8
|
+
// architecture bundle's per-archetype `conformance` tags resolve.
|
|
9
|
+
export function buildApplicationConformanceCoverage(inputs) {
|
|
10
|
+
const { manifest, findings } = inputs;
|
|
11
|
+
const rows = [];
|
|
12
|
+
const conformance = manifest.conformance;
|
|
13
|
+
if (conformance === undefined)
|
|
14
|
+
return rows;
|
|
15
|
+
if (manifest.profile !== "application-product") {
|
|
16
|
+
if ((conformance.coverage?.length ?? 0) > 0 || (conformance.deviations?.length ?? 0) > 0) {
|
|
17
|
+
findings.add("convention", `application_conformance_coverage: conformance declared on non-application-product SDD "${manifest.sddId}" (profile=${manifest.profile}) — ignored (M-ADR 0191 §4.3 is application-product only)`);
|
|
18
|
+
}
|
|
19
|
+
return rows;
|
|
20
|
+
}
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
const emit = (tag, status, rationale) => {
|
|
23
|
+
const t = tag.trim();
|
|
24
|
+
if (t === "")
|
|
25
|
+
return;
|
|
26
|
+
const id = `${manifest.sddId}__${t}`;
|
|
27
|
+
if (seen.has(id)) {
|
|
28
|
+
findings.add("divergence", `application_conformance_coverage: duplicate conformance tag id="${id}" — same (product_id, tag) already emitted; second dropped`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
seen.add(id);
|
|
32
|
+
rows.push({
|
|
33
|
+
id,
|
|
34
|
+
product_id: manifest.sddId,
|
|
35
|
+
tag: t,
|
|
36
|
+
status,
|
|
37
|
+
rationale,
|
|
38
|
+
source_sdd_id: "",
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
for (const tag of conformance.coverage ?? [])
|
|
42
|
+
emit(tag, "covered", "");
|
|
43
|
+
for (const d of conformance.deviations ?? [])
|
|
44
|
+
emit(d.tag, "deviated", d.rationale);
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=application-conformance-coverage.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// application_data_plane_bindings builder — emits one row per declared
|
|
2
|
+
// data-plane binding (M-ADR 0193).
|
|
3
|
+
//
|
|
4
|
+
// The data-plane axis models an application-product as a COMPOSITION of typed
|
|
5
|
+
// data bindings, orthogonal to `application_archetype` (0191). Each binding is
|
|
6
|
+
// one `{pattern, locality, transport, runtime, target, notes}` tuple declared
|
|
7
|
+
// in the manifest's `data_plane.bindings` block. The manifest loader has
|
|
8
|
+
// already validated the closed enums and the runtime/locality rules (malformed
|
|
9
|
+
// bindings FAIL at load), so this builder only shapes valid bindings into rows.
|
|
10
|
+
//
|
|
11
|
+
// The `pattern` vocabulary splits by DIRECTION (§4.7): an INBOUND data-sourcing
|
|
12
|
+
// sub-set (retrieve/own-store/stream-ingest/project) and an OUTPUT-side sub-set
|
|
13
|
+
// (generate/publish). Each row carries a derived `direction` column so consumers
|
|
14
|
+
// can branch on the sub-set the ADR declares instead of re-deriving the split.
|
|
15
|
+
//
|
|
16
|
+
// An INBOUND binding whose target is a platform capability/service reconciles
|
|
17
|
+
// with `product_platform_dependencies` (a retrieve@internal binding is a
|
|
18
|
+
// synchronous platform dependency; a stream-ingest@internal binding is the
|
|
19
|
+
// M-ADR 0192 contract_type:event async dependency). OUTPUT-side rows do NOT
|
|
20
|
+
// participate in that reconciliation (§4.7 rule 1) — `publish` is a contract the
|
|
21
|
+
// service PROVIDES, so it has no inbound counterpart to cross-check.
|
|
22
|
+
import { dataPlaneDirection } from "../manifest-loader.js";
|
|
23
|
+
// Recommended transport values (open string per the schema; a value outside the
|
|
24
|
+
// set is a convention finding, never a hard fail — the M-ADR 0156/0192 pattern).
|
|
25
|
+
const RECOMMENDED_TRANSPORTS = new Set([
|
|
26
|
+
"rest",
|
|
27
|
+
"graphql",
|
|
28
|
+
"grpc",
|
|
29
|
+
"appsync",
|
|
30
|
+
"kinesis",
|
|
31
|
+
"kafka",
|
|
32
|
+
"eventhub",
|
|
33
|
+
"cdc",
|
|
34
|
+
]);
|
|
35
|
+
export function buildApplicationDataPlaneBindings(inputs) {
|
|
36
|
+
const { manifest, findings } = inputs;
|
|
37
|
+
const rows = [];
|
|
38
|
+
const bindings = manifest.dataPlane?.bindings;
|
|
39
|
+
if (bindings === undefined || bindings.length === 0)
|
|
40
|
+
return rows;
|
|
41
|
+
// data_plane is an application-product-only axis. A binding block on another
|
|
42
|
+
// profile is a manifest smell — report it and emit nothing.
|
|
43
|
+
if (manifest.profile !== "application-product") {
|
|
44
|
+
findings.add("convention", `application_data_plane_bindings: data_plane declared on non-application-product SDD "${manifest.sddId}" (profile=${manifest.profile}) — ignored (M-ADR 0193 is application-product only)`);
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
for (const b of bindings) {
|
|
49
|
+
const pattern = b.pattern;
|
|
50
|
+
const locality = b.locality ?? "";
|
|
51
|
+
const transport = b.transport ?? "";
|
|
52
|
+
const runtime = b.runtime ?? "";
|
|
53
|
+
const target = b.target ?? "";
|
|
54
|
+
const notes = b.notes ?? "";
|
|
55
|
+
const id = `${manifest.sddId}__${pattern}__${locality}__${target}`;
|
|
56
|
+
if (seen.has(id)) {
|
|
57
|
+
findings.add("divergence", `application_data_plane_bindings: duplicate binding id="${id}" — same (product_id, pattern, locality, target) already emitted; second dropped`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
seen.add(id);
|
|
61
|
+
if (transport !== "" && !RECOMMENDED_TRANSPORTS.has(transport.toLowerCase())) {
|
|
62
|
+
findings.add("convention", `application_data_plane_bindings: binding "${id}" declares transport="${transport}" outside the recommended set (${[...RECOMMENDED_TRANSPORTS].join(", ")})`);
|
|
63
|
+
}
|
|
64
|
+
rows.push({
|
|
65
|
+
id,
|
|
66
|
+
product_id: manifest.sddId,
|
|
67
|
+
pattern,
|
|
68
|
+
// Derived from `pattern` (M-ADR 0193 §4.7), not authored — it materialises
|
|
69
|
+
// the output-side sub-set so consumers can branch on direction (notably
|
|
70
|
+
// the inbound-only platform-dep reconciliation) without re-deriving the
|
|
71
|
+
// pattern split. Not a PK component: it adds nothing to identity.
|
|
72
|
+
direction: dataPlaneDirection(pattern),
|
|
73
|
+
locality,
|
|
74
|
+
transport,
|
|
75
|
+
runtime,
|
|
76
|
+
target,
|
|
77
|
+
notes,
|
|
78
|
+
source_sdd_id: "",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Emit `application_data_plane_deviations` rows from the manifest's
|
|
85
|
+
* `data_plane.deviations` block (M-ADR 0191 §4.3). Built per-SDD so the
|
|
86
|
+
* aggregation-tier overlay WARN can read them; the WARN itself runs at
|
|
87
|
+
* aggregation, where the reference-architecture `expected_bindings` resolve.
|
|
88
|
+
*/
|
|
89
|
+
export function buildApplicationDataPlaneDeviations(inputs) {
|
|
90
|
+
const { manifest, findings } = inputs;
|
|
91
|
+
const rows = [];
|
|
92
|
+
const deviations = manifest.dataPlane?.deviations;
|
|
93
|
+
if (deviations === undefined || deviations.length === 0)
|
|
94
|
+
return rows;
|
|
95
|
+
if (manifest.profile !== "application-product")
|
|
96
|
+
return rows;
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const d of deviations) {
|
|
99
|
+
const id = `${manifest.sddId}__${d.expectation}`;
|
|
100
|
+
if (seen.has(id)) {
|
|
101
|
+
findings.add("divergence", `application_data_plane_deviations: duplicate deviation id="${id}" — same (product_id, expectation) already emitted; second dropped`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
seen.add(id);
|
|
105
|
+
rows.push({
|
|
106
|
+
id,
|
|
107
|
+
product_id: manifest.sddId,
|
|
108
|
+
expectation: d.expectation,
|
|
109
|
+
rationale: d.rationale,
|
|
110
|
+
source_sdd_id: "",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return rows;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=application-data-plane-bindings.js.map
|