@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,418 @@
|
|
|
1
|
+
// Deterministic structural-wireframe renderer (ADR 0169 §4.4).
|
|
2
|
+
//
|
|
3
|
+
// Lowers a screen's openui-lang block to an SVG wireframe: parse the block into
|
|
4
|
+
// a composition tree, resolve each node to a visual archetype, and lay it out
|
|
5
|
+
// driven by the archetype's render hints — orientation, shape (bar / chips /
|
|
6
|
+
// grid / card / floating / paged / leaf shapes), slots, overlay, active_first,
|
|
7
|
+
// header_row. Each node is drawn as its archetype's visual SHAPE (an app-bar is
|
|
8
|
+
// a band, a tab-strip is a row of chips, a paged-panes shows the active pane and
|
|
9
|
+
// collapses the rest, a table has a header row, a modal floats), labelled with
|
|
10
|
+
// the component name + resolved archetype and shaded by resolution provenance so
|
|
11
|
+
// low-confidence inferences stay visible. A structural wireframe, not a pixel
|
|
12
|
+
// design; parameterised entirely by the archetype map (adopter-agnostic — no
|
|
13
|
+
// component vocabulary, no adopter knowledge).
|
|
14
|
+
//
|
|
15
|
+
// Deterministic companion to the LLM-driven render-screen-wireframe skill.
|
|
16
|
+
import { buildCompositionTree, stripBlockComments } from "../builders/screen-tree.js";
|
|
17
|
+
import { archetypeInfo, isKnownPlatform, mapStripPrefixes, resolveArchetype, } from "./resolver.js";
|
|
18
|
+
// --- geometry constants ----------------------------------------------------
|
|
19
|
+
const PAD = 8;
|
|
20
|
+
const GAP = 6;
|
|
21
|
+
const LABEL_H = 16; // container label strip
|
|
22
|
+
const TITLE_H = 26;
|
|
23
|
+
const TABLE_HDR_H = 20;
|
|
24
|
+
const COLLAPSE_H = 22; // collapsed (inactive) paged pane row
|
|
25
|
+
const SHADOW = 4; // floating/card drop-shadow offset
|
|
26
|
+
const GRID_COLS = 2;
|
|
27
|
+
/** Render an openui-lang block to a structural wireframe SVG string. */
|
|
28
|
+
export function renderWireframe(input) {
|
|
29
|
+
const tree = buildCompositionTree(stripBlockComments(input.block));
|
|
30
|
+
if (tree === null) {
|
|
31
|
+
return emptySvg(input.title, "empty composition — no statements");
|
|
32
|
+
}
|
|
33
|
+
const platform = isKnownPlatform(input.platform) ? input.platform : undefined;
|
|
34
|
+
const prefixes = [...(input.overlay?.strip_prefixes ?? []), ...mapStripPrefixes(platform)];
|
|
35
|
+
const root = measure(shape(tree, platform, input.overlay, prefixes));
|
|
36
|
+
const titleOffset = input.title ? TITLE_H : 0;
|
|
37
|
+
const width = root.w + PAD * 2;
|
|
38
|
+
const height = root.h + PAD * 2 + titleOffset;
|
|
39
|
+
const body = [];
|
|
40
|
+
if (input.title)
|
|
41
|
+
body.push(text(PAD, 17, escapeXml(input.title), "title"));
|
|
42
|
+
emit(root, PAD, PAD + titleOffset, body);
|
|
43
|
+
return svg(width, height, body.join("\n"));
|
|
44
|
+
}
|
|
45
|
+
// --- shape: resolve archetype + classify a render mode per node ------------
|
|
46
|
+
const LEAF_SHAPES = new Set(["button", "chip", "text", "image", "circle", "row"]);
|
|
47
|
+
function shape(node, platform, overlay, prefixes) {
|
|
48
|
+
const children = node.children.map((c) => shape(c, platform, overlay, prefixes));
|
|
49
|
+
const isContainer = children.length > 0;
|
|
50
|
+
linkPagedTabs(children, prefixes);
|
|
51
|
+
if (node.component === "" || platform === undefined) {
|
|
52
|
+
return base("", "none", isContainer ? "stack" : "leaf", "box");
|
|
53
|
+
}
|
|
54
|
+
const { archetype, provenance } = resolveArchetype(node.component, platform, overlay);
|
|
55
|
+
const r = archetypeInfo(archetype).render;
|
|
56
|
+
const sh = r.shape ?? "";
|
|
57
|
+
if (!isContainer) {
|
|
58
|
+
const leafShape = LEAF_SHAPES.has(sh) ? sh : "box";
|
|
59
|
+
return base(archetype, provenance, "leaf", leafShape);
|
|
60
|
+
}
|
|
61
|
+
let mode;
|
|
62
|
+
if (sh === "bar")
|
|
63
|
+
mode = "bar";
|
|
64
|
+
else if (sh === "chips")
|
|
65
|
+
mode = "chips";
|
|
66
|
+
else if (sh === "grid")
|
|
67
|
+
mode = r.header_row ? "table" : "grid";
|
|
68
|
+
else if (sh === "paged")
|
|
69
|
+
mode = "paged";
|
|
70
|
+
else if (sh === "card")
|
|
71
|
+
mode = "card";
|
|
72
|
+
else if (sh === "floating")
|
|
73
|
+
mode = "floating";
|
|
74
|
+
else
|
|
75
|
+
mode = r.orientation === "horizontal" ? "row" : "stack";
|
|
76
|
+
return base(archetype, provenance, mode, "box");
|
|
77
|
+
function base(archetype, provenance, mode, leafShape) {
|
|
78
|
+
return { node, archetype, provenance, mode, leafShape, children, stripPrefixes: prefixes, derivedLabel: "", w: 0, h: 0 };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Derive tab labels from windowed content (deterministic, spec-faithful): when a
|
|
83
|
+
* sibling group holds a tab-strip and a paged-panes with the SAME pane count, and
|
|
84
|
+
* the tabs carry no authored label, label each tab from its corresponding pane's
|
|
85
|
+
* content component name. Reads only names already in the spec — no fabrication.
|
|
86
|
+
*/
|
|
87
|
+
function linkPagedTabs(siblings, prefixes) {
|
|
88
|
+
const strip = siblings.find((s) => s.archetype === "tab-strip");
|
|
89
|
+
const paged = siblings.find((s) => s.archetype === "paged-panes");
|
|
90
|
+
if (!strip || !paged)
|
|
91
|
+
return;
|
|
92
|
+
if (strip.children.length === 0 || strip.children.length !== paged.children.length)
|
|
93
|
+
return;
|
|
94
|
+
strip.children.forEach((tab, i) => {
|
|
95
|
+
if (tab.node.args.length > 0 || tab.derivedLabel)
|
|
96
|
+
return; // authored label wins
|
|
97
|
+
const pane = paged.children[i];
|
|
98
|
+
const contentName = pane.children[0]?.node.component || pane.node.component;
|
|
99
|
+
if (contentName)
|
|
100
|
+
tab.derivedLabel = humaniseName(contentName, prefixes);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// --- measure: size every node bottom-up ------------------------------------
|
|
104
|
+
function leafSize(shapeName, lbl) {
|
|
105
|
+
switch (shapeName) {
|
|
106
|
+
case "button": return { w: Math.max(84, lbl.length * 6 + 16), h: 26 };
|
|
107
|
+
case "chip": return { w: Math.max(56, lbl.length * 6 + 14), h: 22 };
|
|
108
|
+
case "text": return { w: Math.max(120, lbl.length * 6 + 12), h: 28 };
|
|
109
|
+
case "image": return { w: 72, h: 54 };
|
|
110
|
+
case "circle": return { w: 44, h: 44 };
|
|
111
|
+
case "row": return { w: Math.max(200, lbl.length * 6 + 24), h: 26 };
|
|
112
|
+
default: return { w: Math.max(120, lbl.length * 6.2 + 12), h: 40 };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function measure(s) {
|
|
116
|
+
if (s.mode === "leaf") {
|
|
117
|
+
const { w, h } = leafSize(s.leafShape, labelPlain(s));
|
|
118
|
+
s.w = w;
|
|
119
|
+
s.h = h;
|
|
120
|
+
return s;
|
|
121
|
+
}
|
|
122
|
+
s.children.forEach(measure);
|
|
123
|
+
const cs = s.children;
|
|
124
|
+
let innerW;
|
|
125
|
+
let innerH;
|
|
126
|
+
switch (s.mode) {
|
|
127
|
+
case "row":
|
|
128
|
+
case "bar":
|
|
129
|
+
case "chips": {
|
|
130
|
+
innerW = cs.reduce((a, c) => a + c.w, 0) + GAP * (cs.length - 1);
|
|
131
|
+
innerH = Math.max(...cs.map((c) => c.h));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "grid": {
|
|
135
|
+
const colW = Math.max(...cs.map((c) => c.w));
|
|
136
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
137
|
+
const rows = Math.ceil(cs.length / GRID_COLS);
|
|
138
|
+
const cols = Math.min(GRID_COLS, cs.length);
|
|
139
|
+
innerW = cols * colW + GAP * (cols - 1);
|
|
140
|
+
innerH = rows * rowH + GAP * (rows - 1);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "table": {
|
|
144
|
+
innerW = Math.max(180, ...cs.map((c) => c.w));
|
|
145
|
+
innerH = TABLE_HDR_H + GAP + cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "paged": {
|
|
149
|
+
const active = cs[0];
|
|
150
|
+
const restN = cs.length - 1;
|
|
151
|
+
innerW = Math.max(160, active.w);
|
|
152
|
+
innerH = active.h + (restN > 0 ? GAP + restN * COLLAPSE_H + GAP * (restN - 1) : 0);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
// stack / card / floating
|
|
157
|
+
innerW = Math.max(...cs.map((c) => c.w));
|
|
158
|
+
innerH = cs.reduce((a, c) => a + c.h, 0) + GAP * (cs.length - 1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
s.w = innerW + PAD * 2;
|
|
162
|
+
s.h = LABEL_H + innerH + PAD * 2;
|
|
163
|
+
return s;
|
|
164
|
+
}
|
|
165
|
+
// --- emit: place + draw top-down -------------------------------------------
|
|
166
|
+
function emit(s, x, y, out) {
|
|
167
|
+
if (s.mode === "leaf") {
|
|
168
|
+
drawLeaf(s, x, y, out);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
drawContainer(s, x, y, out);
|
|
172
|
+
const cx0 = x + PAD;
|
|
173
|
+
const cy0 = y + LABEL_H + PAD;
|
|
174
|
+
const cs = s.children;
|
|
175
|
+
switch (s.mode) {
|
|
176
|
+
case "row":
|
|
177
|
+
case "bar":
|
|
178
|
+
case "chips": {
|
|
179
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
180
|
+
let cx = cx0;
|
|
181
|
+
for (const c of cs) {
|
|
182
|
+
emit(c, cx, cy0 + (rowH - c.h) / 2, out);
|
|
183
|
+
cx += c.w + GAP;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "grid": {
|
|
188
|
+
const colW = Math.max(...cs.map((c) => c.w));
|
|
189
|
+
const rowH = Math.max(...cs.map((c) => c.h));
|
|
190
|
+
cs.forEach((c, i) => {
|
|
191
|
+
const col = i % GRID_COLS;
|
|
192
|
+
const row = Math.floor(i / GRID_COLS);
|
|
193
|
+
emit(c, cx0 + col * (colW + GAP), cy0 + row * (rowH + GAP), out);
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case "table": {
|
|
198
|
+
const innerW = s.w - PAD * 2;
|
|
199
|
+
out.push(rect(cx0, cy0, innerW, TABLE_HDR_H, "none", true, "#e2e8f0"));
|
|
200
|
+
for (let i = 1; i < 4; i++) {
|
|
201
|
+
const lx = cx0 + (innerW * i) / 4;
|
|
202
|
+
out.push(line(lx, cy0, lx, cy0 + TABLE_HDR_H));
|
|
203
|
+
}
|
|
204
|
+
let ry = cy0 + TABLE_HDR_H + GAP;
|
|
205
|
+
for (const c of cs) {
|
|
206
|
+
emit(c, cx0, ry, out);
|
|
207
|
+
ry += c.h + GAP;
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "paged": {
|
|
212
|
+
const active = cs[0];
|
|
213
|
+
emit(active, cx0, cy0, out);
|
|
214
|
+
let ry = cy0 + active.h + GAP;
|
|
215
|
+
for (const c of cs.slice(1)) {
|
|
216
|
+
out.push(rect(cx0, ry, s.w - PAD * 2, COLLAPSE_H, c.provenance, false, "#f1f5f9"));
|
|
217
|
+
out.push(text(cx0 + 6, ry + 15, "▸ " + collapsedPaneLabel(c), "dim"));
|
|
218
|
+
ry += COLLAPSE_H + GAP;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
default: {
|
|
223
|
+
let cy = cy0;
|
|
224
|
+
for (const c of cs) {
|
|
225
|
+
emit(c, cx0, cy, out);
|
|
226
|
+
cy += c.h + GAP;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// --- drawing primitives ----------------------------------------------------
|
|
232
|
+
const DARK_BARS = new Set(["app-bar", "nav-bar"]);
|
|
233
|
+
function drawContainer(s, x, y, out) {
|
|
234
|
+
const dark = DARK_BARS.has(s.archetype);
|
|
235
|
+
let fill = "#f8fafc";
|
|
236
|
+
if (s.archetype === "banner")
|
|
237
|
+
fill = "#fef3c7";
|
|
238
|
+
else if (s.archetype === "tab-bar")
|
|
239
|
+
fill = "#eef2ff";
|
|
240
|
+
else if (s.mode === "card" || s.mode === "floating")
|
|
241
|
+
fill = "#ffffff";
|
|
242
|
+
else if (dark)
|
|
243
|
+
fill = "#1f2937";
|
|
244
|
+
if (s.mode === "floating" || s.mode === "card") {
|
|
245
|
+
out.push(`<rect x="${x + SHADOW}" y="${y + SHADOW}" width="${s.w}" height="${s.h}" rx="4" fill="#0f172a" opacity="0.12"/>`);
|
|
246
|
+
}
|
|
247
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, true, fill));
|
|
248
|
+
out.push(text(x + PAD, y + 12, labelMarkup(s), dark ? "lblw" : "lbl"));
|
|
249
|
+
}
|
|
250
|
+
function drawLeaf(s, x, y, out) {
|
|
251
|
+
const lbl = labelMarkup(s);
|
|
252
|
+
switch (s.leafShape) {
|
|
253
|
+
case "button":
|
|
254
|
+
out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="13" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
|
|
255
|
+
out.push(text(x + 10, y + s.h / 2 + 4, lbl, "btn"));
|
|
256
|
+
break;
|
|
257
|
+
case "chip":
|
|
258
|
+
out.push(`<rect x="${x}" y="${y}" width="${s.w}" height="${s.h}" rx="11" fill="#e2e8f0" stroke="#94a3b8"${dash(s.provenance)}/>`);
|
|
259
|
+
out.push(text(x + 9, y + s.h / 2 + 4, lbl, "lbl"));
|
|
260
|
+
break;
|
|
261
|
+
case "circle": {
|
|
262
|
+
const r = s.w / 2;
|
|
263
|
+
out.push(`<circle cx="${x + r}" cy="${y + r}" r="${r}" fill="#dbeafe" stroke="#3b82f6"${dash(s.provenance)}/>`);
|
|
264
|
+
out.push(text(x + 6, y + r + 4, lbl, "dim"));
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case "image":
|
|
268
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#f1f5f9"));
|
|
269
|
+
out.push(line(x, y, x + s.w, y + s.h));
|
|
270
|
+
out.push(line(x + s.w, y, x, y + s.h));
|
|
271
|
+
out.push(text(x + 4, y + s.h - 4, lbl, "dim"));
|
|
272
|
+
break;
|
|
273
|
+
case "text":
|
|
274
|
+
out.push(text(x, y + 12, lbl, "lbl"));
|
|
275
|
+
out.push(`<rect x="${x}" y="${y + 16}" width="${s.w}" height="5" rx="2" fill="#cbd5e1"/>`);
|
|
276
|
+
out.push(`<rect x="${x}" y="${y + 24}" width="${s.w * 0.66}" height="5" rx="2" fill="#e2e8f0"/>`);
|
|
277
|
+
break;
|
|
278
|
+
case "row":
|
|
279
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#ffffff"));
|
|
280
|
+
out.push(`<circle cx="${x + 11}" cy="${y + s.h / 2}" r="5" fill="#cbd5e1"/>`);
|
|
281
|
+
out.push(text(x + 24, y + s.h / 2 + 4, lbl, "lbl"));
|
|
282
|
+
break;
|
|
283
|
+
default:
|
|
284
|
+
out.push(rect(x, y, s.w, s.h, s.provenance, false, "#fdfdfe"));
|
|
285
|
+
out.push(text(x + 6, y + s.h / 2 + 4, lbl, "lbl"));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// --- label derivation ------------------------------------------------------
|
|
289
|
+
/** The primary human label for a node, derived only from tokens in the spec. */
|
|
290
|
+
function primaryLabel(s) {
|
|
291
|
+
if (s.node.args.length > 0)
|
|
292
|
+
return s.node.args[0]; // authored positional label
|
|
293
|
+
if (s.mode === "leaf" && s.node.actions.length > 0 && (s.archetype === "button" || s.archetype === "fab"))
|
|
294
|
+
return humaniseAction(s.node.actions[0]); // CTA from action=
|
|
295
|
+
if (s.derivedLabel)
|
|
296
|
+
return s.derivedLabel; // windowed-pane tab label
|
|
297
|
+
const comp = s.node.component;
|
|
298
|
+
if (!comp)
|
|
299
|
+
return s.node.id;
|
|
300
|
+
// A `default` resolution is a recognised standard widget — its short vendor name
|
|
301
|
+
// reads fine as-is; bespoke names (override / heuristic / fallback) humanise better.
|
|
302
|
+
return s.provenance === "default" || s.provenance === "none" ? comp : humaniseName(comp, s.stripPrefixes);
|
|
303
|
+
}
|
|
304
|
+
function archMark(prov) {
|
|
305
|
+
return prov === "fallback" ? "?" : prov === "heuristic" ? "~" : "";
|
|
306
|
+
}
|
|
307
|
+
/** Plain text label (for width measurement): primary + archetype annotation. */
|
|
308
|
+
function labelPlain(s) {
|
|
309
|
+
const primary = primaryLabel(s);
|
|
310
|
+
if (s.archetype === "" || s.provenance === "none")
|
|
311
|
+
return primary;
|
|
312
|
+
return `${primary} :${s.archetype}${archMark(s.provenance)}`;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Collapsed paged-pane label: a wrapper pane often has a generic name (its
|
|
316
|
+
* content carries the identity), so prefer the content component's label when
|
|
317
|
+
* the pane itself has no authored label of its own. Spec-faithful — the content
|
|
318
|
+
* name is already in the block.
|
|
319
|
+
*/
|
|
320
|
+
function collapsedPaneLabel(s) {
|
|
321
|
+
const content = s.children[0];
|
|
322
|
+
if (s.node.args.length === 0 && !s.derivedLabel && content && content.node.component) {
|
|
323
|
+
const lbl = escapeXml(primaryLabel(content));
|
|
324
|
+
return s.archetype === "" ? lbl : `${lbl} <tspan class="arch">:${escapeXml(s.archetype)}</tspan>`;
|
|
325
|
+
}
|
|
326
|
+
return labelMarkup(s);
|
|
327
|
+
}
|
|
328
|
+
/** SVG markup label (for emission): primary text + a small dim `:archetype` tspan. */
|
|
329
|
+
function labelMarkup(s) {
|
|
330
|
+
const primary = escapeXml(primaryLabel(s));
|
|
331
|
+
if (s.archetype === "" || s.provenance === "none")
|
|
332
|
+
return primary;
|
|
333
|
+
return `${primary} <tspan class="arch">:${escapeXml(s.archetype)}${archMark(s.provenance)}</tspan>`;
|
|
334
|
+
}
|
|
335
|
+
// --- humanisation (deterministic, adopter-agnostic) ------------------------
|
|
336
|
+
const HUMANISE_SUFFIXES = [
|
|
337
|
+
"Component", "Container", "Controller", "Activity", "Fragment", "Section",
|
|
338
|
+
"Layout", "Panel", "Popup", "Dialog", "Modal", "Widget", "View", "Screen",
|
|
339
|
+
"Page", "Table", "List", "Card", "Item",
|
|
340
|
+
];
|
|
341
|
+
/** Strip a vendor prefix + a trailing archetype-ish suffix, split camelCase, Title Case. */
|
|
342
|
+
function humaniseName(name, prefixes) {
|
|
343
|
+
let s = name;
|
|
344
|
+
for (const p of prefixes) {
|
|
345
|
+
// Strip only at a PascalCase boundary (next char uppercase) so a short prefix
|
|
346
|
+
// like "St" peels "StLayout" → "Layout" but never "Status" → "atus".
|
|
347
|
+
if (s.length > p.length && s.startsWith(p) && /[A-Z]/.test(s.charAt(p.length))) {
|
|
348
|
+
s = s.slice(p.length);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
for (const suf of HUMANISE_SUFFIXES) {
|
|
353
|
+
if (s.length > suf.length && s.endsWith(suf)) {
|
|
354
|
+
s = s.slice(0, -suf.length);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const words = splitWords(s);
|
|
359
|
+
if (words.length === 0)
|
|
360
|
+
return titleCaseWords(splitWords(name)); // prefix/suffix ate the whole name
|
|
361
|
+
return titleCaseWords(words);
|
|
362
|
+
}
|
|
363
|
+
/** Humanise an action token, prefixing create/add/new verbs with "+ ". */
|
|
364
|
+
function humaniseAction(action) {
|
|
365
|
+
const human = titleCaseWords(splitWords(action));
|
|
366
|
+
return /^(new|add|create)([^a-z]|$)/i.test(action) ? `+ ${human}` : human;
|
|
367
|
+
}
|
|
368
|
+
function splitWords(s) {
|
|
369
|
+
return s
|
|
370
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
371
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
372
|
+
.replace(/[_\-.]+/g, " ")
|
|
373
|
+
.trim()
|
|
374
|
+
.split(/\s+/)
|
|
375
|
+
.filter(Boolean);
|
|
376
|
+
}
|
|
377
|
+
function titleCaseWords(words) {
|
|
378
|
+
return words.map((w) => (w === w.toUpperCase() ? w : w.charAt(0).toUpperCase() + w.slice(1))).join(" ");
|
|
379
|
+
}
|
|
380
|
+
function dash(prov) {
|
|
381
|
+
return prov === "heuristic" ? ' stroke-dasharray="5 3"' : prov === "fallback" ? ' stroke-dasharray="1 4"' : "";
|
|
382
|
+
}
|
|
383
|
+
function rect(x, y, w, h, prov, container, fill) {
|
|
384
|
+
const f = fill ?? (container ? "#f4f6f8" : "#fdfdfe");
|
|
385
|
+
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="3" fill="${f}" stroke="#64748b" stroke-width="1"${dash(prov)}/>`;
|
|
386
|
+
}
|
|
387
|
+
function line(x1, y1, x2, y2) {
|
|
388
|
+
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#cbd5e1" stroke-width="1"/>`;
|
|
389
|
+
}
|
|
390
|
+
function text(x, y, content, cls) {
|
|
391
|
+
return `<text x="${x}" y="${y}" class="${cls}">${content}</text>`;
|
|
392
|
+
}
|
|
393
|
+
function svg(width, height, body) {
|
|
394
|
+
return [
|
|
395
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
|
|
396
|
+
`<style>text{font-family:ui-monospace,monospace;font-size:11px;fill:#334155}.title{font-size:13px;font-weight:600;fill:#0f172a}.lbl{fill:#334155}.lblw{fill:#f8fafc}.dim{fill:#94a3b8}.btn{fill:#1e40af;font-weight:600}.arch{font-size:9px;fill:#a8b3c2}</style>`,
|
|
397
|
+
`<rect width="${width}" height="${height}" fill="#fff"/>`,
|
|
398
|
+
body,
|
|
399
|
+
`</svg>`,
|
|
400
|
+
].join("\n");
|
|
401
|
+
}
|
|
402
|
+
function emptySvg(title, message) {
|
|
403
|
+
const body = [
|
|
404
|
+
title ? text(PAD, 16, escapeXml(title), "title") : "",
|
|
405
|
+
text(PAD, title ? 36 : 16, escapeXml(message), "lbl"),
|
|
406
|
+
]
|
|
407
|
+
.filter(Boolean)
|
|
408
|
+
.join("\n");
|
|
409
|
+
return svg(240, title ? 48 : 28, body);
|
|
410
|
+
}
|
|
411
|
+
function escapeXml(s) {
|
|
412
|
+
return s
|
|
413
|
+
.replace(/&/g, "&")
|
|
414
|
+
.replace(/</g, "<")
|
|
415
|
+
.replace(/>/g, ">")
|
|
416
|
+
.replace(/"/g, """);
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=wireframe.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `catalogue baseline-check` — compares emitted findings against a repo's
|
|
3
|
+
* findings baseline BY IDENTITY (M-ADR 0132 §9), and reports the four outcomes
|
|
4
|
+
* a re-take needs to be reviewable: unbaselined, reworded, closed, unchanged.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS IS A COMMAND AND NOT A DISCIPLINE. The comparison was previously
|
|
7
|
+
* described in prose and performed per repo, which is why the rewording class
|
|
8
|
+
* (§9.1) went undetected twice on the same finding family: every repo
|
|
9
|
+
* implemented "is this line in the file" independently and correctly, and the
|
|
10
|
+
* defect was in the question, not the implementations.
|
|
11
|
+
*
|
|
12
|
+
* It reads findings rather than producing them, so it composes with whatever
|
|
13
|
+
* produced them and cannot disagree with a separate run of the builder:
|
|
14
|
+
*
|
|
15
|
+
* sdd-cli catalogue validate --coverage > findings.txt
|
|
16
|
+
* sdd-cli catalogue baseline-check --findings findings.txt
|
|
17
|
+
*
|
|
18
|
+
* ONLY GATE-ACTING CATEGORIES ARE COMPARED (`orphan`, `divergence` — M-ADR 0132
|
|
19
|
+
* §4.3). Filtering happens here, visibly, and the count of what was filtered is
|
|
20
|
+
* printed: a comparator that silently dropped categories would make an empty
|
|
21
|
+
* result ambiguous between "nothing gate-acting" and "nothing at all".
|
|
22
|
+
*/
|
|
23
|
+
import { readFile } from "node:fs/promises";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { compareToBaseline } from "./finding-identity.js";
|
|
26
|
+
/** The categories a baseline governs, per M-ADR 0132 §4.3. */
|
|
27
|
+
const GATE_ACTING = new Set(["orphan", "divergence"]);
|
|
28
|
+
export const DEFAULT_BASELINE_REL = join("orchestration", "catalogue-findings-baseline.txt");
|
|
29
|
+
const FINDING_LINE = /^\[finding\] (\w+)(?: \[error\])?[ (:]/;
|
|
30
|
+
/**
|
|
31
|
+
* Findings from a stream of mixed output. Non-finding lines (metrics, headers,
|
|
32
|
+
* blank lines, a baseline file's `#` provenance) are not findings and are
|
|
33
|
+
* dropped without comment; a `[finding]` line of a non-gate-acting category is
|
|
34
|
+
* dropped and COUNTED, because that number is the difference between "nothing
|
|
35
|
+
* to compare" and "nothing was comparable".
|
|
36
|
+
*/
|
|
37
|
+
export function extractGateActing(text) {
|
|
38
|
+
const lines = [];
|
|
39
|
+
let nonGateActing = 0;
|
|
40
|
+
for (const raw of text.split("\n")) {
|
|
41
|
+
const line = raw.trimEnd();
|
|
42
|
+
const m = FINDING_LINE.exec(line);
|
|
43
|
+
if (!m)
|
|
44
|
+
continue;
|
|
45
|
+
if (GATE_ACTING.has(m[1]))
|
|
46
|
+
lines.push(line);
|
|
47
|
+
else
|
|
48
|
+
nonGateActing++;
|
|
49
|
+
}
|
|
50
|
+
return { lines, nonGateActing };
|
|
51
|
+
}
|
|
52
|
+
export async function runBaselineCheck(options, write) {
|
|
53
|
+
const root = options.sddPath ?? process.cwd();
|
|
54
|
+
const baselinePath = options.baseline ?? join(root, DEFAULT_BASELINE_REL);
|
|
55
|
+
let findingsText;
|
|
56
|
+
if (options.findings !== undefined) {
|
|
57
|
+
findingsText = await readFile(options.findings, "utf-8");
|
|
58
|
+
}
|
|
59
|
+
else if (options.stdin) {
|
|
60
|
+
findingsText = await options.stdin();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
write("[error] baseline-check: no findings source (--findings <path>, or pipe them in)\n");
|
|
64
|
+
return { exitCode: 2 };
|
|
65
|
+
}
|
|
66
|
+
// An input with no lines at all is not "zero findings": every real findings
|
|
67
|
+
// stream (build, coverage, validate output) carries at least its own metric
|
|
68
|
+
// and verdict lines. The default source is stdin, so an empty pipe arrives
|
|
69
|
+
// here as nothing — refused, never compared (empty-set sweep, R2).
|
|
70
|
+
if (findingsText.trim() === "") {
|
|
71
|
+
write(`[error] baseline-check: the findings stream is EMPTY — no lines of any kind. ` +
|
|
72
|
+
`This is not zero findings; it is no measurement (an empty pipe, or the wrong ` +
|
|
73
|
+
`file). Pass --findings <path> to the captured stream\n`);
|
|
74
|
+
return { exitCode: 2 };
|
|
75
|
+
}
|
|
76
|
+
let baselineText;
|
|
77
|
+
try {
|
|
78
|
+
baselineText = await readFile(baselinePath, "utf-8");
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err.code !== "ENOENT")
|
|
82
|
+
throw err;
|
|
83
|
+
// A MISSING baseline is not an empty one. Under §9.3 the canonical zero is
|
|
84
|
+
// an empty-but-present file, precisely so that "measured, and there were
|
|
85
|
+
// none" is distinguishable from "never measured" — so this reports rather
|
|
86
|
+
// than treating every live finding as unbaselined.
|
|
87
|
+
write(`[error] baseline-check: no baseline at ${baselinePath} — a missing file is not a ` +
|
|
88
|
+
`recorded zero (M-ADR 0132 §9.3); create it empty, with its provenance header, ` +
|
|
89
|
+
`to record that findings were measured and there were none\n`);
|
|
90
|
+
return { exitCode: 2 };
|
|
91
|
+
}
|
|
92
|
+
const live = extractGateActing(findingsText);
|
|
93
|
+
const base = extractGateActing(baselineText);
|
|
94
|
+
const cmp = compareToBaseline(live.lines, base.lines);
|
|
95
|
+
write(`[metric] baseline_check: ${live.lines.length} gate-acting finding(s) against ` +
|
|
96
|
+
`${base.lines.length} baseline entr(ies) — ${cmp.unchanged} unchanged, ` +
|
|
97
|
+
`${cmp.reworded.length} reworded, ${cmp.unbaselined.length} unbaselined, ` +
|
|
98
|
+
`${cmp.stale.length} closed; ${live.nonGateActing} non-gate-acting finding(s) not compared\n`);
|
|
99
|
+
// ⚠ THE WHOLE-BASELINE-CLOSED GUARD. An all-closed result against a non-empty
|
|
100
|
+
// baseline is far more likely to mean the comparison read the WRONG STREAM than
|
|
101
|
+
// that an estate fixed everything at once, and until now it rendered as a clean
|
|
102
|
+
// green number: "0 gate-acting findings, 215 closed" reads as success and is
|
|
103
|
+
// indistinguishable from a gate comparing against nothing.
|
|
104
|
+
//
|
|
105
|
+
// Raised by an adopter estate that hit it on a 215-entry baseline: its
|
|
106
|
+
// baseline had been captured from one stream/tier and was being compared
|
|
107
|
+
// against another, so every entry reported closed and the regression gate
|
|
108
|
+
// passed unconditionally. The `--findings` default is stdin, so an empty pipe
|
|
109
|
+
// reaches here as legitimately-zero findings — the same estate had already
|
|
110
|
+
// been caught once by a bare invocation reporting 832 closed.
|
|
111
|
+
//
|
|
112
|
+
// The condition is deliberately narrow: live gate-acting findings ZERO, baseline
|
|
113
|
+
// non-empty, and EVERY entry closed. That triple cannot arise from ordinary
|
|
114
|
+
// remediation without also being worth a human look.
|
|
115
|
+
const allClosed = live.lines.length === 0 && base.lines.length > 0 && cmp.stale.length === base.lines.length;
|
|
116
|
+
if (allClosed) {
|
|
117
|
+
write(`[finding] divergence: baseline_check compared ZERO live gate-acting findings ` +
|
|
118
|
+
`against ${base.lines.length} baseline entr(ies) and reported ALL of them closed. ` +
|
|
119
|
+
`This is reported as a DEFECT rather than a pass: an all-closed baseline is far ` +
|
|
120
|
+
`more likely to mean the findings stream was empty or was the wrong one than that ` +
|
|
121
|
+
`every finding was fixed at once. Check that --findings received the stream the ` +
|
|
122
|
+
`baseline was CAPTURED from (note the default is stdin, so an empty pipe arrives ` +
|
|
123
|
+
`here as zero findings), and that --tier matches. Re-taking the baseline against ` +
|
|
124
|
+
`this comparison would bake the empty stream in as the new truth and destroy the ` +
|
|
125
|
+
`${base.lines.length}-entry record.\n`);
|
|
126
|
+
}
|
|
127
|
+
// Reported even at zero. While it is above zero those entries are matched on
|
|
128
|
+
// whole text and remain exposed to the rewording class; silence here would
|
|
129
|
+
// imply a protection the repo has not got.
|
|
130
|
+
write(`[metric] baseline_check.legacy_keyed: ${cmp.legacyKeyed} baseline entr(ies) carry no ` +
|
|
131
|
+
`identity and are matched on their whole line — re-take them as their emission ` +
|
|
132
|
+
`sites migrate (M-ADR 0132 §9.2)\n`);
|
|
133
|
+
for (const r of cmp.reworded) {
|
|
134
|
+
// Named, not counted only: a re-take must be able to show which line
|
|
135
|
+
// replaced which, and "N reworded" is not reviewable evidence.
|
|
136
|
+
write(`[reworded] ${r.baseline}\n[reworded] -> ${r.live}\n`);
|
|
137
|
+
}
|
|
138
|
+
for (const s of cmp.stale)
|
|
139
|
+
write(`[closed] ${s}\n`);
|
|
140
|
+
for (const u of cmp.unbaselined)
|
|
141
|
+
write(`[unbaselined] ${u}\n`);
|
|
142
|
+
// Only genuinely unbaselined findings fail. A reworded finding is the case
|
|
143
|
+
// this whole mechanism exists to stop failing, and a closed one is good news
|
|
144
|
+
// that a re-take tidies — neither is a regression.
|
|
145
|
+
// The all-closed guard FAILS the gate (empty-set sweep, R2, ruled 2026-09-29):
|
|
146
|
+
// it used to print "a DEFECT rather than a pass" and exit 0, so the regression
|
|
147
|
+
// gate still passed on exactly the empty/wrong stream it was written to catch.
|
|
148
|
+
// A genuinely all-fixed estate re-takes its baseline once, deliberately.
|
|
149
|
+
return { exitCode: cmp.unbaselined.length > 0 || allClosed ? 1 : 0 };
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=baseline-check.js.map
|