@sdd-method/author-cli 0.0.0-stage → 0.2.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.
Files changed (203) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +17 -0
  3. package/README.md +6 -2
  4. package/dist/author-bin.js +8 -0
  5. package/dist/author-program.js +99 -0
  6. package/dist/lib/adr-origin.js +59 -0
  7. package/dist/lib/agent-harness.js +37 -0
  8. package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
  9. package/dist/lib/api-surface/analyzers/library.js +95 -0
  10. package/dist/lib/api-surface/analyzers/serverless.js +336 -0
  11. package/dist/lib/api-surface/analyzers/spring.js +237 -0
  12. package/dist/lib/api-surface/analyzers/terraform.js +109 -0
  13. package/dist/lib/api-surface/detect.js +25 -0
  14. package/dist/lib/api-surface/emit.js +109 -0
  15. package/dist/lib/api-surface/file-set.js +43 -0
  16. package/dist/lib/api-surface/index.js +134 -0
  17. package/dist/lib/api-surface/types.js +17 -0
  18. package/dist/lib/briefing/index.js +13 -0
  19. package/dist/lib/briefing/render.js +132 -0
  20. package/dist/lib/briefing/variables.js +92 -0
  21. package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
  22. package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
  23. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
  24. package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
  25. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
  26. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
  27. package/dist/lib/catalogue/archetype/resolver.js +103 -0
  28. package/dist/lib/catalogue/archetype/wireframe.js +418 -0
  29. package/dist/lib/catalogue/baseline-check.js +151 -0
  30. package/dist/lib/catalogue/build.js +1502 -0
  31. package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
  32. package/dist/lib/catalogue/builders/adrs.js +222 -0
  33. package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
  34. package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
  35. package/dist/lib/catalogue/builders/c4-components.js +229 -0
  36. package/dist/lib/catalogue/builders/c4-containers.js +240 -0
  37. package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
  38. package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
  39. package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
  40. package/dist/lib/catalogue/builders/c4-systems.js +98 -0
  41. package/dist/lib/catalogue/builders/capabilities.js +488 -0
  42. package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
  43. package/dist/lib/catalogue/builders/capability-edges.js +256 -0
  44. package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
  45. package/dist/lib/catalogue/builders/capability-spec.js +348 -0
  46. package/dist/lib/catalogue/builders/container-dependency.js +183 -0
  47. package/dist/lib/catalogue/builders/contracts.js +402 -0
  48. package/dist/lib/catalogue/builders/data-items.js +344 -0
  49. package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
  50. package/dist/lib/catalogue/builders/domain-models.js +175 -0
  51. package/dist/lib/catalogue/builders/domains.js +119 -0
  52. package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
  53. package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
  54. package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
  55. package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
  56. package/dist/lib/catalogue/builders/entity-slug.js +47 -0
  57. package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
  58. package/dist/lib/catalogue/builders/event-edges.js +327 -0
  59. package/dist/lib/catalogue/builders/events.js +168 -0
  60. package/dist/lib/catalogue/builders/evidence-class.js +43 -0
  61. package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
  62. package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
  63. package/dist/lib/catalogue/builders/feature-contract.js +77 -0
  64. package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
  65. package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
  66. package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
  67. package/dist/lib/catalogue/builders/feature-groups.js +63 -0
  68. package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
  69. package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
  70. package/dist/lib/catalogue/builders/features.js +300 -0
  71. package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
  72. package/dist/lib/catalogue/builders/glossary.js +204 -0
  73. package/dist/lib/catalogue/builders/implementations.js +214 -0
  74. package/dist/lib/catalogue/builders/index.js +64 -0
  75. package/dist/lib/catalogue/builders/integration-components.js +344 -0
  76. package/dist/lib/catalogue/builders/integrations.js +181 -0
  77. package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
  78. package/dist/lib/catalogue/builders/persona-container.js +133 -0
  79. package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
  80. package/dist/lib/catalogue/builders/personas.js +152 -0
  81. package/dist/lib/catalogue/builders/product-edges.js +106 -0
  82. package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
  83. package/dist/lib/catalogue/builders/products.js +162 -0
  84. package/dist/lib/catalogue/builders/quality-scores.js +635 -0
  85. package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
  86. package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
  87. package/dist/lib/catalogue/builders/repo-edges.js +340 -0
  88. package/dist/lib/catalogue/builders/repositories.js +458 -0
  89. package/dist/lib/catalogue/builders/scaffolds.js +197 -0
  90. package/dist/lib/catalogue/builders/screen-tree.js +222 -0
  91. package/dist/lib/catalogue/builders/screens.js +396 -0
  92. package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
  93. package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
  94. package/dist/lib/catalogue/builders/service-contract.js +74 -0
  95. package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
  96. package/dist/lib/catalogue/builders/services.js +232 -0
  97. package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
  98. package/dist/lib/catalogue/builders/user-journeys.js +253 -0
  99. package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
  100. package/dist/lib/catalogue/cli-version.js +10 -0
  101. package/dist/lib/catalogue/contract-id.js +49 -0
  102. package/dist/lib/catalogue/coverage-predicates.js +603 -0
  103. package/dist/lib/catalogue/coverage-render.js +552 -0
  104. package/dist/lib/catalogue/coverage-run.js +115 -0
  105. package/dist/lib/catalogue/csv-reader.js +97 -0
  106. package/dist/lib/catalogue/csv-writer.js +96 -0
  107. package/dist/lib/catalogue/data-plane-coherence.js +77 -0
  108. package/dist/lib/catalogue/ddl-generator.js +233 -0
  109. package/dist/lib/catalogue/dedupe-keyed-tables.js +100 -0
  110. package/dist/lib/catalogue/engine-validator.js +239 -0
  111. package/dist/lib/catalogue/ext-id.js +76 -0
  112. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
  113. package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
  114. package/dist/lib/catalogue/finding-identity.js +183 -0
  115. package/dist/lib/catalogue/findings.js +46 -0
  116. package/dist/lib/catalogue/fk-validator.js +136 -0
  117. package/dist/lib/catalogue/freshness.js +70 -0
  118. package/dist/lib/catalogue/gate-checks.js +338 -0
  119. package/dist/lib/catalogue/graph-projection.js +346 -0
  120. package/dist/lib/catalogue/impact-run.js +228 -0
  121. package/dist/lib/catalogue/manifest-loader.js +435 -0
  122. package/dist/lib/catalogue/platform-scope.js +7 -0
  123. package/dist/lib/catalogue/population-checks.js +87 -0
  124. package/dist/lib/catalogue/relationship-semantics.js +66 -0
  125. package/dist/lib/catalogue/reverse-product-quality.js +99 -0
  126. package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
  127. package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
  128. package/dist/lib/catalogue/scaffold-delta.js +261 -0
  129. package/dist/lib/catalogue/schema-compat.js +52 -0
  130. package/dist/lib/catalogue/schema-loader.js +87 -0
  131. package/dist/lib/catalogue/scores-coverage.js +573 -0
  132. package/dist/lib/catalogue/screen-validation-loader.js +89 -0
  133. package/dist/lib/catalogue/service-architecture-parser.js +193 -0
  134. package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
  135. package/dist/lib/catalogue/staleness-verdicts.js +345 -0
  136. package/dist/lib/catalogue/traceability-loader.js +350 -0
  137. package/dist/lib/catalogue/validate.js +200 -0
  138. package/dist/lib/catalogue/version.js +18 -0
  139. package/dist/lib/conformance/binding.js +70 -0
  140. package/dist/lib/conformance/finding.js +69 -0
  141. package/dist/lib/conformance/index.js +578 -0
  142. package/dist/lib/context-pull/index.js +191 -0
  143. package/dist/lib/context-pull/resolve.js +70 -0
  144. package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
  145. package/dist/lib/contracts-availability/index.js +267 -0
  146. package/dist/lib/generate-briefing/index.js +77 -0
  147. package/dist/lib/list/discover.js +100 -0
  148. package/dist/lib/list/format.js +84 -0
  149. package/dist/lib/list/index.js +16 -0
  150. package/dist/lib/lock-file.js +447 -0
  151. package/dist/lib/pack/log.js +18 -0
  152. package/dist/lib/pack/schema.js +121 -0
  153. package/dist/lib/pack/seed.js +182 -0
  154. package/dist/lib/pack/validate.js +49 -0
  155. package/dist/lib/package-version.js +92 -0
  156. package/dist/lib/plan/index.js +69 -0
  157. package/dist/lib/plan/read-sdd.js +102 -0
  158. package/dist/lib/plan/render-template.js +84 -0
  159. package/dist/lib/repo-kind.js +129 -0
  160. package/dist/lib/scaffold/domain.js +234 -0
  161. package/dist/lib/scaffold/registry.js +136 -0
  162. package/dist/lib/scripts.js +7 -0
  163. package/dist/lib/sdd-index/repo-config.js +245 -0
  164. package/dist/lib/sdd-reader.js +91 -0
  165. package/dist/lib/snapshot/advance.js +454 -0
  166. package/dist/lib/snapshot/diff.js +190 -0
  167. package/dist/lib/snapshot/fold.js +208 -0
  168. package/dist/lib/snapshot/land.js +178 -0
  169. package/dist/lib/snapshot/manifest.js +206 -0
  170. package/dist/lib/snapshot/targets.js +169 -0
  171. package/dist/lib/snapshot/verify.js +91 -0
  172. package/dist/lib/sync/briefing-merge.js +106 -0
  173. package/dist/lib/sync/bundle-cache.js +109 -0
  174. package/dist/lib/sync/yaml-block-merge.js +120 -0
  175. package/dist/lib/validate/aggregate.js +51 -0
  176. package/dist/lib/validate/bundle-coherence.js +74 -0
  177. package/dist/lib/validate/dispatch.js +243 -0
  178. package/dist/lib/validate/index.js +110 -0
  179. package/dist/lib/validate/plan.js +57 -0
  180. package/dist/lib/validate/runner.js +85 -0
  181. package/dist/lib/validate/yaml-parse.js +112 -0
  182. package/dist/maintain-version.json +4 -0
  183. package/dist/verbs/api-surface.js +71 -0
  184. package/dist/verbs/catalogue-check.js +28 -0
  185. package/dist/verbs/catalogue-verify.js +114 -0
  186. package/dist/verbs/conformance.js +34 -0
  187. package/dist/verbs/context.js +39 -0
  188. package/dist/verbs/contracts.js +34 -0
  189. package/dist/verbs/generate-briefing.js +29 -0
  190. package/dist/verbs/list.js +45 -0
  191. package/dist/verbs/pack-validate.js +17 -0
  192. package/dist/verbs/path-option.js +25 -0
  193. package/dist/verbs/plan.js +42 -0
  194. package/dist/verbs/render.js +87 -0
  195. package/dist/verbs/scaffold.js +83 -0
  196. package/dist/verbs/snapshot.js +134 -0
  197. package/dist/verbs/validate.js +53 -0
  198. package/dist/verify-program.js +122 -0
  199. package/dist/verify-version.json +4 -0
  200. package/package.json +28 -4
  201. package/scripts/validate-app-dependencies.sh +664 -0
  202. package/scripts/validate-app-docs.sh +199 -0
  203. package/scripts/validate-integration-docs.sh +505 -0
@@ -0,0 +1,87 @@
1
+ const norm = (s) => s.toLowerCase().replace(/^ext-/, "").replace(/[^a-z0-9]/g, "");
2
+ const slug = (s) => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3
+ /** Does a services[] id name a declared C4 container — by id, by the system-anchored id, or by the slug of its name? */
4
+ export function matchesContainer(sid, containers) {
5
+ const target = sid.toLowerCase();
6
+ return containers.some((c) => {
7
+ const id = c.id.toLowerCase();
8
+ if (id === target || slug(c.name) === target)
9
+ return true;
10
+ const sys = (c.system_id ?? "").toLowerCase();
11
+ return sys !== "" && id === `${sys}-${target}`;
12
+ });
13
+ }
14
+ /**
15
+ * A near-miss is an external-system id that the unresolved id contains or is contained by after
16
+ * normalisation (a separator changed, a prefix or suffix added — the three measured shapes), with a
17
+ * length floor so a short common token does not fire on everything. It is NAMED, never applied.
18
+ */
19
+ export function nearMiss(id, externalIds) {
20
+ const n = norm(id);
21
+ let best;
22
+ for (const ext of externalIds) {
23
+ const e = norm(ext);
24
+ if (ext === id || e === "" || Math.min(n.length, e.length) < 6)
25
+ continue;
26
+ if (n === e || n.includes(e) || e.includes(n)) {
27
+ if (best === undefined || e.length > norm(best).length)
28
+ best = ext; // prefer the most specific
29
+ }
30
+ }
31
+ return best;
32
+ }
33
+ // @rule population-capability-service
34
+ export function checkCapabilityServicePopulation(inputs) {
35
+ let declared = 0, unresolved = 0, deduplicated = 0, capabilityUnknown = 0, crossSdd = 0;
36
+ let externalSystem = 0, declaredContainer = 0, nowhere = 0, expectedRows = 0;
37
+ const seen = new Set();
38
+ for (const m of inputs.manifests) {
39
+ const capId = m.capability.id;
40
+ for (const entry of m.traceability.services ?? []) {
41
+ declared++;
42
+ if (!inputs.knownCapabilityIds.has(capId)) {
43
+ capabilityUnknown++;
44
+ continue;
45
+ }
46
+ const sid = entry.id;
47
+ // Resolution order (M-ADR 0211 §4.3): owner_sdd FIRST — a declared cross-SDD
48
+ // reference is resolved by declaration, not by the local services catalogue.
49
+ const resolved = entry.owner_sdd !== undefined || inputs.knownServiceIds.has(sid);
50
+ if (entry.owner_sdd !== undefined)
51
+ crossSdd++;
52
+ if (!resolved) {
53
+ unresolved++;
54
+ if (inputs.knownExternalSystemIds.has(sid)) {
55
+ externalSystem++;
56
+ }
57
+ else if (matchesContainer(sid, inputs.knownContainers)) {
58
+ declaredContainer++;
59
+ inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves as a DECLARED C4 CONTAINER, not a service — capability_service has no locus for it; the id is correct and there is nothing to fix here (M-ADR 0211 §4.3, class 2: a dependency on a container that is not a service, filed as its own item)`);
60
+ }
61
+ else {
62
+ nowhere++;
63
+ const cand = nearMiss(sid, inputs.knownExternalSystemIds);
64
+ if (cand !== undefined) {
65
+ inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves nowhere; nearest external-system id is "${cand}" — a CANDIDATE, not applied (M-ADR 0211 §4.3, class 3)`);
66
+ }
67
+ }
68
+ continue;
69
+ }
70
+ const key = `${capId}__${sid}__${(entry.role ?? "") !== "" ? entry.role : "related"}`;
71
+ if (seen.has(key)) {
72
+ deduplicated++;
73
+ continue;
74
+ }
75
+ seen.add(key);
76
+ expectedRows++;
77
+ }
78
+ }
79
+ const rows = inputs.capabilityServiceRows.length;
80
+ const balances = declared === rows + unresolved + deduplicated + capabilityUnknown;
81
+ inputs.findings.add("metric", `capability_service: declared=${declared} rows=${rows} unresolved=${unresolved} deduplicated=${deduplicated} capability-unknown=${capabilityUnknown} (cross-SDD via owner_sdd=${crossSdd}); unresolved by class: external-system=${externalSystem} declared-container=${declaredContainer} nowhere=${nowhere}`);
82
+ if (!balances) {
83
+ inputs.findings.add("coverage", `capability_service: the population does not balance — declared ${declared} ≠ rows ${rows} + unresolved ${unresolved} + deduplicated ${deduplicated} + capability-unknown ${capabilityUnknown} (expected rows from declarations: ${expectedRows}). Two instruments disagree; adjudicate, do not smooth (M-ADR 0211 §4.3)`);
84
+ }
85
+ return { declared, rows, unresolved, deduplicated, capabilityUnknown, crossSdd, externalSystem, declaredContainer, nowhere, balances };
86
+ }
87
+ //# sourceMappingURL=population-checks.js.map
@@ -0,0 +1,66 @@
1
+ // Relationship semantics and catalogue kinds, read from the vendored
2
+ // rubric-coverage-map.yaml — READ-ONLY. Split out of `graph-run.ts`, which also
3
+ // holds `runCatalogueGraph`, the verb that writes `<catalogues-dir>-graph.jsonl`
4
+ // by default. `catalogue impact` needs these loaders and nothing that writes,
5
+ // and the verify-only CLI ships `impact` (M-ADR 0212 §4.3b): while they lived in
6
+ // graph-run the writer came along with them, and the package build caught it.
7
+ // `graph-run.ts` re-exports everything here, so existing imports are unchanged.
8
+ import { readFile } from "node:fs/promises";
9
+ import { fileURLToPath } from "node:url";
10
+ import { join } from "node:path";
11
+ import { parse as parseYaml } from "yaml";
12
+ /**
13
+ * Vendored copy of sdd-method's orchestration/rubric-coverage-map.yaml —
14
+ * byte-identical to the canonical (rubric-coverage-map-drift.test.ts guards
15
+ * it, same one-way contract as canonical-schema.yaml). The build copies it
16
+ * alongside the compiled module via scripts/copy-yaml-assets.mjs.
17
+ */
18
+ export const VENDORED_COVERAGE_MAP_PATH = join(fileURLToPath(new URL(".", import.meta.url)), "rubric-coverage-map.yaml");
19
+ function toDecl(where, e) {
20
+ if (e.class === undefined) {
21
+ throw new Error(`rubric-coverage-map: ${where} has no class declaration (M-ADR 0206)`);
22
+ }
23
+ if ((e.impact_flow === undefined) === (e.impact_flow_by === undefined)) {
24
+ throw new Error(`rubric-coverage-map: ${where} must declare exactly one of impact_flow / impact_flow_by`);
25
+ }
26
+ return {
27
+ class: e.class,
28
+ ...(e.impact_flow !== undefined
29
+ ? { impactFlow: e.impact_flow }
30
+ : {}),
31
+ ...(e.impact_flow_by !== undefined
32
+ ? {
33
+ impactFlowBy: {
34
+ column: e.impact_flow_by.column,
35
+ values: e.impact_flow_by.values,
36
+ },
37
+ }
38
+ : {}),
39
+ };
40
+ }
41
+ export async function loadRelationshipSemantics(mapPath = VENDORED_COVERAGE_MAP_PATH) {
42
+ const raw = parseYaml(await readFile(mapPath, "utf-8"));
43
+ const kinds = new Map();
44
+ const edges = new Map();
45
+ const entityFks = new Map();
46
+ for (const [name, entry] of Object.entries(raw.coverage)) {
47
+ kinds.set(name, entry.kind);
48
+ if (entry.kind === "edge" || entry.kind === "annotation") {
49
+ // an annotation classifies at top level exactly as an edge does (schema 3.9.0)
50
+ edges.set(name, toDecl(`edge ${name}`, entry));
51
+ }
52
+ for (const [col, decl] of Object.entries(entry.relationships ?? {})) {
53
+ entityFks.set(`${name}.${col}`, toDecl(`entity FK ${name}.${col}`, decl));
54
+ }
55
+ }
56
+ return { kinds, edges, entityFks };
57
+ }
58
+ export async function loadCatalogueKinds(mapPath = VENDORED_COVERAGE_MAP_PATH) {
59
+ const raw = parseYaml(await readFile(mapPath, "utf-8"));
60
+ const kinds = new Map();
61
+ for (const [name, entry] of Object.entries(raw.coverage)) {
62
+ kinds.set(name, entry.kind);
63
+ }
64
+ return kinds;
65
+ }
66
+ //# sourceMappingURL=relationship-semantics.js.map
@@ -0,0 +1,99 @@
1
+ // Reverse product-quality scores → the `products` maturity columns.
2
+ //
3
+ // WHY THIS EXISTS. `products` carried two maturity mechanisms and BOTH were inert:
4
+ // - `status` (documented `scaffolded | populated | reviewed`) was hardcoded to
5
+ // `"active"`, a value outside its own vocabulary — removed at schema 3.0.0;
6
+ // - `synthesis_quality` / `synthesis_band` / `last_assessed`, designed for exactly
7
+ // this data, were populated on 0 of 208 rows across two estates.
8
+ //
9
+ // Meanwhile the reverse estate holds 131 gate-registry score files, of which
10
+ // `reverse-product-quality-scores.yaml` is the family at PRODUCT granularity — the
11
+ // only one whose unit is a product row. So the fix is to fill the richer column from
12
+ // the data that already exists, rather than populate the coarser one from a weaker
13
+ // signal and leave this empty beside it.
14
+ //
15
+ // SCOPE, measured rather than assumed: 45 such files exist, but 24 are empty shells
16
+ // and several units describe *covered* sub-products rather than the SDD's own. Using
17
+ // the self-product rule below, 15 units qualify and 12 carry a score. So this
18
+ // populates ~12 rows today — a real but modest yield, and additive: it only fills
19
+ // empty nullable cells, so no existing row changes value.
20
+ //
21
+ // THE SELF-PRODUCT RULE. A file may list several units (one estate file lists nine).
22
+ // The SDD's own product is the unit whose `product_path` is `.` AND whose key equals
23
+ // the SDD's `sdd_id`. Both conditions are required: `product_path` alone admits
24
+ // synthetic gate fixtures (three exist under one platform SDD with keys like
25
+ // `beta/product-pass`), and key-matching alone admits covered sub-products.
26
+ import { readFile } from "node:fs/promises";
27
+ import { join } from "node:path";
28
+ import { parse as parseYaml } from "yaml";
29
+ export const REVERSE_PRODUCT_QUALITY_REL = "orchestration/gate-registry/reverse-product-quality-scores.yaml";
30
+ /** The closed band vocabulary (catalogue-schema `products.synthesis_band`). */
31
+ export const SYNTHESIS_BANDS = ["developing", "working", "strong"];
32
+ /**
33
+ * Read this SDD's own product-quality score, if the reverse gate registry has one.
34
+ *
35
+ * Returns an empty object when the file is absent, unparseable, has no units, or has
36
+ * no self-product unit — all normal states, not defects. A forward-authored estate has
37
+ * no reverse scores at all, and the columns stay empty there by design (the schema
38
+ * documents them as reverse-method only).
39
+ */
40
+ export async function loadReverseProductQuality(sddPath, sddId, findings) {
41
+ let text;
42
+ try {
43
+ text = await readFile(join(sddPath, REVERSE_PRODUCT_QUALITY_REL), "utf-8");
44
+ }
45
+ catch (err) {
46
+ if (err.code === "ENOENT")
47
+ return {};
48
+ throw err;
49
+ }
50
+ let raw;
51
+ try {
52
+ raw = parseYaml(text);
53
+ }
54
+ catch (err) {
55
+ // A malformed registry must not fail the build: the catalogue is still correct
56
+ // without the score. Say so rather than silently emitting empty columns.
57
+ findings.add("convention", `${REVERSE_PRODUCT_QUALITY_REL}: failed to parse YAML — product maturity columns left empty (${err.message})`);
58
+ return {};
59
+ }
60
+ const doc = (raw ?? {});
61
+ const units = doc.units;
62
+ if (typeof units !== "object" || units === null)
63
+ return {};
64
+ // The self-product rule: `product_path: .` AND key === sdd_id. See the header.
65
+ const unit = units[sddId];
66
+ if (!unit || typeof unit !== "object")
67
+ return {};
68
+ const productPath = String(unit.product_path ?? "").trim();
69
+ if (productPath !== ".")
70
+ return {};
71
+ const q = unit.product_synthesis_quality;
72
+ if (!q || typeof q !== "object")
73
+ return {};
74
+ const out = {};
75
+ if (typeof q.total === "number" && Number.isFinite(q.total)) {
76
+ if (q.total < 0 || q.total > 10) {
77
+ findings.add("convention", `${REVERSE_PRODUCT_QUALITY_REL}: units.${sddId} total ${q.total} is outside the rubric range 0-10 — left empty`);
78
+ }
79
+ else {
80
+ out.quality = q.total;
81
+ }
82
+ }
83
+ const band = String(q.band ?? "").trim();
84
+ if (band !== "") {
85
+ if (SYNTHESIS_BANDS.includes(band)) {
86
+ out.band = band;
87
+ }
88
+ else {
89
+ // A band outside the closed vocabulary is reported, not passed through: the
90
+ // column would otherwise carry a value no consumer can interpret.
91
+ findings.add("convention", `${REVERSE_PRODUCT_QUALITY_REL}: units.${sddId} band "${band}" is not in {${SYNTHESIS_BANDS.join(" | ")}} — left empty`);
92
+ }
93
+ }
94
+ const assessedAt = String(doc.assessed_at ?? "").trim();
95
+ if (/^\d{4}-\d{2}-\d{2}$/.test(assessedAt))
96
+ out.assessedAt = assessedAt;
97
+ return out;
98
+ }
99
+ //# sourceMappingURL=reverse-product-quality.js.map