@sdd-method/maintain-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/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +28 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +123 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/bundle.js +123 -0
- package/dist/lib/catalogue/aggregate.js +2404 -0
- package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
- package/dist/lib/catalogue/application-archetype/expectations.js +119 -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/contract-path-resolution.js +149 -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-item-coverage.js +142 -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 +69 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/graph-run.js +64 -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/ci-binding/index.js +220 -0
- package/dist/lib/ci-binding/module.js +204 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/context-pull/index.js +151 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/hooks-seed/copilot.js +167 -0
- package/dist/lib/hooks-seed/entries.js +47 -0
- package/dist/lib/hooks-seed/index.js +17 -0
- package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
- package/dist/lib/hooks-seed/merge.js +128 -0
- package/dist/lib/hooks-seed/types.js +20 -0
- package/dist/lib/init/ci-workflow.js +98 -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/mcp-seed/entries.js +14 -0
- package/dist/lib/mcp-seed/index.js +28 -0
- package/dist/lib/mcp-seed/merge.js +99 -0
- package/dist/lib/mcp-seed/types.js +13 -0
- package/dist/lib/pack/builder.js +303 -0
- package/dist/lib/pack/index.js +87 -0
- package/dist/lib/pack/leakage.js +144 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/publish-transform.js +233 -0
- package/dist/lib/pack/reference-closure.js +125 -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/repo-kind.js +129 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/snapshot/catalogue-diff.js +219 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/sync/adr-transitions.js +250 -0
- package/dist/lib/sync/apply.js +91 -0
- package/dist/lib/sync/briefing-merge.js +90 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/bundle-source.js +61 -0
- package/dist/lib/sync/config.js +72 -0
- package/dist/lib/sync/copilot-hooks-merge.js +108 -0
- package/dist/lib/sync/diff.js +251 -0
- package/dist/lib/sync/index.js +818 -0
- package/dist/lib/sync/settings-merge.js +216 -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-bin.js +8 -0
- package/dist/maintain-program.js +91 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/catalogue-maintain.js +110 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-build.js +31 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/sync.js +110 -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,233 @@
|
|
|
1
|
+
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { ORIGIN_PREFIX_SRC, originPrefixForNumber, originPrefixForPath, } from "../adr-origin.js";
|
|
4
|
+
export { originPrefixForNumber };
|
|
5
|
+
/**
|
|
6
|
+
* Publish transform (sdd-method ADR 0183 / 0184). Produces the point-in-time,
|
|
7
|
+
* producer-agnostic *published* form of method markdown by subtraction from the
|
|
8
|
+
* canon, and gates the result with a fail-closed deny-class lint.
|
|
9
|
+
*
|
|
10
|
+
* The strip is a pure structural transform — it removes marked structures and
|
|
11
|
+
* never reads meaning, so it cannot misinterpret. The lint runs on the produced
|
|
12
|
+
* output and proves nothing leaked; it never decides what to strip. Authors
|
|
13
|
+
* carry the burden of placing evolution / producer content in the structured
|
|
14
|
+
* places (lineage regions, stripped fields), enforced at author/CI time.
|
|
15
|
+
*
|
|
16
|
+
* Enabled only for method-baseline builds (BuildOptions.publishTransform);
|
|
17
|
+
* adopter-pack builds are unaffected.
|
|
18
|
+
*/
|
|
19
|
+
// Header fields stripped on publish (ADR 0184 — adr-metadata-schema.md).
|
|
20
|
+
const STRIPPED_FIELDS = [
|
|
21
|
+
"Status History",
|
|
22
|
+
"Acceptance Rationale",
|
|
23
|
+
"Supersedes",
|
|
24
|
+
"Superseded By",
|
|
25
|
+
"Amends",
|
|
26
|
+
"Source",
|
|
27
|
+
"Grounding",
|
|
28
|
+
"Introduced In",
|
|
29
|
+
];
|
|
30
|
+
const STRIPPED_FIELD_RE = new RegExp(`^\\* \\*\\*(${STRIPPED_FIELDS.join("|")}):\\*\\*`);
|
|
31
|
+
const LINEAGE_START = "<!-- lineage:start -->";
|
|
32
|
+
const LINEAGE_END = "<!-- lineage:end -->";
|
|
33
|
+
// Published-surface deny-classes. Kept in lock-step with the reference gate in
|
|
34
|
+
// sdd-method orchestration/scripts/validate-adr-structure.sh (--publish-lint).
|
|
35
|
+
// A line carrying `pit-ok` is exempt; a file carrying `pit-ok-file` is exempt
|
|
36
|
+
// wholesale (a meta/versioning ADR whose deny-vocabulary IS its subject).
|
|
37
|
+
const DENY_TEMPORAL = [
|
|
38
|
+
/since retired/i,
|
|
39
|
+
/previously/i,
|
|
40
|
+
/formerly/i,
|
|
41
|
+
/supersed/i,
|
|
42
|
+
/no longer/i,
|
|
43
|
+
/pre-federation/i,
|
|
44
|
+
/used to/i,
|
|
45
|
+
/bumps? to/i,
|
|
46
|
+
/sdd-cli [0-9]/i,
|
|
47
|
+
/\bv[0-9]+\.[0-9]+\.[0-9]+/i,
|
|
48
|
+
];
|
|
49
|
+
const DENY_PRODUCER = [
|
|
50
|
+
/sdd-method#/i,
|
|
51
|
+
/sdd-method-gov/i,
|
|
52
|
+
/sdd-method repo/i,
|
|
53
|
+
/the method repo/i,
|
|
54
|
+
/governance repo/i,
|
|
55
|
+
/synced from (sdd-method|the method)/i,
|
|
56
|
+
/bootstrap.?procedure/i,
|
|
57
|
+
];
|
|
58
|
+
/** True when the markdown is an ADR with `Status: Accepted`. */
|
|
59
|
+
export function isAccepted(content) {
|
|
60
|
+
const m = content.match(/^\*\s+\*\*Status:\*\*\s*([A-Za-z]+)/m);
|
|
61
|
+
return m?.[1] === "Accepted";
|
|
62
|
+
}
|
|
63
|
+
/** Path is an ADR file (under an `adr/` or `platform-adr/` dir), bare or prefixed. */
|
|
64
|
+
export function isAdrPath(relPath) {
|
|
65
|
+
return new RegExp(`adr\\/${ORIGIN_PREFIX_SRC}ADR \\d`, "i").test(relPath);
|
|
66
|
+
}
|
|
67
|
+
/** Remove lineage regions + stripped-class header fields. Deterministic. */
|
|
68
|
+
export function stripStructures(content) {
|
|
69
|
+
const out = [];
|
|
70
|
+
let inLineage = false;
|
|
71
|
+
for (const line of content.split("\n")) {
|
|
72
|
+
if (line.includes(LINEAGE_START)) {
|
|
73
|
+
inLineage = true;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (line.includes(LINEAGE_END)) {
|
|
77
|
+
inLineage = false;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (inLineage)
|
|
81
|
+
continue;
|
|
82
|
+
if (STRIPPED_FIELD_RE.test(line))
|
|
83
|
+
continue;
|
|
84
|
+
out.push(line);
|
|
85
|
+
}
|
|
86
|
+
return out.join("\n");
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Flatten cross-repo references to the flat in-namespace form (M-ADR 0184 §4.3):
|
|
90
|
+
* `sdd-method#<id>` becomes the canonical prefixed identifier for that number's
|
|
91
|
+
* band (`sdd-method#0174` → `M-ADR 0174`), never a bare `ADR` (M-ADR 0186 — no
|
|
92
|
+
* bare ADR on any SDD surface). Numbers outside the assigned bands fall back to
|
|
93
|
+
* the bare form.
|
|
94
|
+
*/
|
|
95
|
+
export function flattenCrossRefs(content) {
|
|
96
|
+
return content.replace(/\bsdd-method#(\d{3,4})\b/g, (_m, num) => {
|
|
97
|
+
const p = originPrefixForNumber(num);
|
|
98
|
+
return p ? `${p} ${num}` : `ADR ${num}`;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Rewrite bare in-namespace `ADR NNNN` refs to their PRODUCER origin prefix by
|
|
103
|
+
* number band (`0xxx`→`M-ADR`, `9xxx`→`RA-ADR`). Adopter refs (`1000–8999`) are
|
|
104
|
+
* left bare here — their `P-`/`D-` prefix is scope-derived (M-ADR 0186 §4.3) and
|
|
105
|
+
* applied by the estate rename from a corpus map, not from the number alone.
|
|
106
|
+
* Matches the prose form ("ADR 0140") and the URL-encoded link-target form
|
|
107
|
+
* ("ADR%200140"), separator preserved, so renamed files and their hrefs stay in
|
|
108
|
+
* lock-step. The `(?<![\w-])` guard leaves an already-prefixed ref untouched
|
|
109
|
+
* (the `-` before `ADR` fails the lookbehind) — idempotent.
|
|
110
|
+
*/
|
|
111
|
+
export function applyOriginPrefix(content) {
|
|
112
|
+
return content.replace(/(?<![\w-])ADR(%20| )(\d{3,4})(?!\d)/g, (m, sep, num) => {
|
|
113
|
+
const p = originPrefixForNumber(num);
|
|
114
|
+
return p ? `${p}${sep}${num}` : m;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** @deprecated Back-compat alias for `applyOriginPrefix` (producer-band prefixing). */
|
|
118
|
+
export const applyMAdrPrefix = applyOriginPrefix;
|
|
119
|
+
/**
|
|
120
|
+
* The canonical filename for an ADR: the *basename* `ADR NNNN…` gains its origin
|
|
121
|
+
* prefix (`M-ADR`/`RA-ADR` by number band, `P-ADR`/`D-ADR` by scope — M-ADR 0186).
|
|
122
|
+
* The containing directory is never touched, only the leading `ADR ` of the
|
|
123
|
+
* basename; the directory is what determines an adopter ADR's scope. Returns the
|
|
124
|
+
* path unchanged when it is not an `ADR NNNN` file or the prefix is undeterminable.
|
|
125
|
+
* Idempotent: an already-prefixed name is left alone.
|
|
126
|
+
*/
|
|
127
|
+
export function mAdrFilename(relPath) {
|
|
128
|
+
return relPath.replace(/(^|\/)ADR (\d{3,4})/, (m, pre, num) => {
|
|
129
|
+
const p = originPrefixForPath(relPath, num);
|
|
130
|
+
return p ? `${pre}${p} ${num}` : m;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** The full published form of a markdown document. */
|
|
134
|
+
export function transformPublished(content, opts = {}) {
|
|
135
|
+
let c = stripStructures(content);
|
|
136
|
+
c = flattenCrossRefs(c);
|
|
137
|
+
if (opts.mAdrPrefix)
|
|
138
|
+
c = applyMAdrPrefix(c);
|
|
139
|
+
return c;
|
|
140
|
+
}
|
|
141
|
+
/** Scan already-published content for deny-class leaks. */
|
|
142
|
+
export function lintPublished(published, relPath) {
|
|
143
|
+
if (published.includes("pit-ok-file"))
|
|
144
|
+
return [];
|
|
145
|
+
const violations = [];
|
|
146
|
+
const lines = published.split("\n");
|
|
147
|
+
lines.forEach((line, i) => {
|
|
148
|
+
if (line.includes("pit-ok"))
|
|
149
|
+
return;
|
|
150
|
+
for (const re of DENY_TEMPORAL) {
|
|
151
|
+
if (re.test(line)) {
|
|
152
|
+
violations.push({
|
|
153
|
+
path: relPath,
|
|
154
|
+
cls: "temporal",
|
|
155
|
+
pattern: re.source,
|
|
156
|
+
line: i + 1,
|
|
157
|
+
text: line.slice(0, 140),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const re of DENY_PRODUCER) {
|
|
162
|
+
if (re.test(line)) {
|
|
163
|
+
violations.push({
|
|
164
|
+
path: relPath,
|
|
165
|
+
cls: "producer",
|
|
166
|
+
pattern: re.source,
|
|
167
|
+
line: i + 1,
|
|
168
|
+
text: line.slice(0, 140),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return violations;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Apply the publish transform to a staged payload in place: drop non-Accepted
|
|
177
|
+
* ADRs, transform every markdown file to its published form, and lint the
|
|
178
|
+
* result. Returns the retained paths + any violations (caller fails closed).
|
|
179
|
+
*/
|
|
180
|
+
export async function applyPublishTransform(payloadRoot, paths, opts = {}) {
|
|
181
|
+
const keptPaths = [];
|
|
182
|
+
const droppedPaths = [];
|
|
183
|
+
const violations = [];
|
|
184
|
+
for (const p of paths) {
|
|
185
|
+
if (!p.endsWith(".md")) {
|
|
186
|
+
// Non-Markdown config/data (registry.yaml, seed lists, schemas): flatten
|
|
187
|
+
// cross-repo refs so the published config surface is producer-agnostic,
|
|
188
|
+
// and — under the origin-prefix rename — range-prefix bare in-namespace
|
|
189
|
+
// ADR refs so the config matches the renamed decision files. No lineage
|
|
190
|
+
// strip / Accepted filter / file rename applies here. Mirrors the method
|
|
191
|
+
// baseline builder's yaml/yml/txt handling.
|
|
192
|
+
if (/\.(ya?ml|txt)$/.test(p)) {
|
|
193
|
+
const abs = join(payloadRoot, p);
|
|
194
|
+
const content = await readFile(abs, "utf8");
|
|
195
|
+
let rewritten = flattenCrossRefs(content);
|
|
196
|
+
if (opts.mAdrPrefix)
|
|
197
|
+
rewritten = applyOriginPrefix(rewritten);
|
|
198
|
+
if (rewritten !== content)
|
|
199
|
+
await writeFile(abs, rewritten);
|
|
200
|
+
}
|
|
201
|
+
keptPaths.push(p);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const abs = join(payloadRoot, p);
|
|
205
|
+
const content = await readFile(abs, "utf8");
|
|
206
|
+
// Accepted-only: a non-Accepted ADR is not part of "what the method is".
|
|
207
|
+
if (isAdrPath(p) && !isAccepted(content)) {
|
|
208
|
+
await rm(abs, { force: true });
|
|
209
|
+
droppedPaths.push(p);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const published = transformPublished(content, opts);
|
|
213
|
+
violations.push(...lintPublished(published, p));
|
|
214
|
+
if (published !== content) {
|
|
215
|
+
await writeFile(abs, published);
|
|
216
|
+
}
|
|
217
|
+
// Under the M-ADR rename, the published *file* is renamed too so the
|
|
218
|
+
// filename matches its (rewritten) heading. Canon filenames stay `ADR`;
|
|
219
|
+
// this is published-output only. The renamed path is what flows into
|
|
220
|
+
// MANAGED_PATHS + checksums, so the lock + sync stay self-consistent.
|
|
221
|
+
let outPath = p;
|
|
222
|
+
if (opts.mAdrPrefix && isAdrPath(p)) {
|
|
223
|
+
const renamed = mAdrFilename(p);
|
|
224
|
+
if (renamed !== p) {
|
|
225
|
+
await rename(abs, join(payloadRoot, renamed));
|
|
226
|
+
outPath = renamed;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
keptPaths.push(outPath);
|
|
230
|
+
}
|
|
231
|
+
return { keptPaths, droppedPaths, violations };
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=publish-transform.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference-closure reporter — M-ADR 0151 §4.6.
|
|
3
|
+
*
|
|
4
|
+
* A context pack ships a SUBSET of its producer's corpus, so payload
|
|
5
|
+
* documents may reference files the pack does not carry. Policy: a link
|
|
6
|
+
* resolves in the repository that OWNS the document; a distributed copy
|
|
7
|
+
* referencing the wider corpus is informational. This reporter makes the
|
|
8
|
+
* property VISIBLE at cut time — the pack declares "ships N references
|
|
9
|
+
* that resolve only in the producer" instead of a consumer discovering it
|
|
10
|
+
* by resolving the whole corpus after distribution.
|
|
11
|
+
*
|
|
12
|
+
* Reporting-only, by design: the deliberate asymmetry with method-baseline
|
|
13
|
+
* bundles, whose seed lists ship whole corpora and therefore carry closure
|
|
14
|
+
* ENFORCEMENT (validate-link-closure.sh). A subset cannot satisfy closure
|
|
15
|
+
* without ceasing to be a subset.
|
|
16
|
+
*
|
|
17
|
+
* Parser hardening comes from the estate that measured this problem — the
|
|
18
|
+
* four traps their link audit hit are this module's controls: markdown
|
|
19
|
+
* targets containing SPACES (unencoded paths must not truncate at the first
|
|
20
|
+
* space), targets containing PARENTHESES (balance them; "…(ReBAC).md" broke
|
|
21
|
+
* every naive regex), URL-ENCODED targets (%20, %3A… decode before
|
|
22
|
+
* resolving), and never scanning outside the payload set (no cache dirs —
|
|
23
|
+
* guaranteed here by construction, since only staged payload paths are read).
|
|
24
|
+
*/
|
|
25
|
+
import { readFile } from "node:fs/promises";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
import { posix } from "node:path";
|
|
28
|
+
/**
|
|
29
|
+
* Extract markdown link targets from one document. Handles `[text](target)`
|
|
30
|
+
* and `` with BALANCED parentheses inside the target, the
|
|
31
|
+
* `](<target>)` angle form, and `](target "title")` suffixes.
|
|
32
|
+
*/
|
|
33
|
+
export function extractLinkTargets(content) {
|
|
34
|
+
const targets = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
for (;;) {
|
|
37
|
+
const at = content.indexOf("](", i);
|
|
38
|
+
if (at === -1)
|
|
39
|
+
break;
|
|
40
|
+
let j = at + 2;
|
|
41
|
+
let target;
|
|
42
|
+
if (content[j] === "<") {
|
|
43
|
+
const close = content.indexOf(">", j + 1);
|
|
44
|
+
if (close === -1)
|
|
45
|
+
break;
|
|
46
|
+
target = content.slice(j + 1, close);
|
|
47
|
+
i = close + 1;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
let depth = 1;
|
|
51
|
+
const start = j;
|
|
52
|
+
while (j < content.length && depth > 0) {
|
|
53
|
+
const c = content[j];
|
|
54
|
+
if (c === "(")
|
|
55
|
+
depth += 1;
|
|
56
|
+
else if (c === ")")
|
|
57
|
+
depth -= 1;
|
|
58
|
+
if (depth > 0)
|
|
59
|
+
j += 1;
|
|
60
|
+
}
|
|
61
|
+
if (depth !== 0)
|
|
62
|
+
break;
|
|
63
|
+
target = content.slice(start, j);
|
|
64
|
+
i = j + 1;
|
|
65
|
+
}
|
|
66
|
+
// Strip a `"title"` suffix (markdown allows `](url "title")`).
|
|
67
|
+
const title = target.match(/^(.*\S)\s+"[^"]*"$/s);
|
|
68
|
+
if (title)
|
|
69
|
+
target = title[1];
|
|
70
|
+
targets.push(target.trim());
|
|
71
|
+
}
|
|
72
|
+
return targets;
|
|
73
|
+
}
|
|
74
|
+
function decodeTarget(raw) {
|
|
75
|
+
try {
|
|
76
|
+
return decodeURIComponent(raw);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined; // malformed escape — count as unresolvable via caller
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve every markdown link in the payload's .md files against the
|
|
84
|
+
* payload's own file set.
|
|
85
|
+
*/
|
|
86
|
+
export async function checkReferenceClosure(payloadRoot, payloadPaths) {
|
|
87
|
+
const inPayload = new Set(payloadPaths.map((p) => posix.normalize(p)));
|
|
88
|
+
let total = 0;
|
|
89
|
+
let unresolvable = 0;
|
|
90
|
+
const misses = new Set();
|
|
91
|
+
for (const p of payloadPaths) {
|
|
92
|
+
if (!p.endsWith(".md"))
|
|
93
|
+
continue;
|
|
94
|
+
const content = await readFile(join(payloadRoot, p), "utf-8");
|
|
95
|
+
const dir = posix.dirname(p);
|
|
96
|
+
for (const rawTarget of extractLinkTargets(content)) {
|
|
97
|
+
// External and non-file targets are out of scope.
|
|
98
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(rawTarget))
|
|
99
|
+
continue; // http:, mailto:, …
|
|
100
|
+
const withoutAnchor = rawTarget.split("#")[0];
|
|
101
|
+
if (withoutAnchor === "")
|
|
102
|
+
continue; // pure anchor
|
|
103
|
+
const decoded = decodeTarget(withoutAnchor);
|
|
104
|
+
total += 1;
|
|
105
|
+
if (decoded === undefined) {
|
|
106
|
+
unresolvable += 1;
|
|
107
|
+
misses.add(rawTarget);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const resolved = decoded.startsWith("/")
|
|
111
|
+
? posix.normalize(decoded.slice(1))
|
|
112
|
+
: posix.normalize(posix.join(dir, decoded));
|
|
113
|
+
if (!inPayload.has(resolved)) {
|
|
114
|
+
unresolvable += 1;
|
|
115
|
+
misses.add(resolved);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
totalReferences: total,
|
|
121
|
+
unresolvable,
|
|
122
|
+
unresolvableTargets: [...misses].sort(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=reference-closure.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for adopter pack seeds. Mirrors
|
|
4
|
+
* docs/method/foundations/schemas/context-pack-seed-schema.yaml.
|
|
5
|
+
*
|
|
6
|
+
* Used by `sdd-cli pack validate` for fast feedback during authoring.
|
|
7
|
+
* The structural parse in seed.ts already enforces presence of required
|
|
8
|
+
* fields; this schema layer adds value-shape checks (semver patterns,
|
|
9
|
+
* known forward-base bundle names, non-empty managed_paths).
|
|
10
|
+
*/
|
|
11
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
12
|
+
const VERSIONED_RANGE_PATTERN = /^(\^|~|>=)?v?\d+\.\d+\.\d+$/;
|
|
13
|
+
// Exported so the canonical-schema conformance test can assert this vocabulary
|
|
14
|
+
// matches context-pack-seed-schema.yaml's `forward_base.bundle` enum. The two had
|
|
15
|
+
// drifted (canonical carried three values, this four) with nothing to detect it.
|
|
16
|
+
export const KNOWN_FORWARD_BASES = [
|
|
17
|
+
"app-baseline",
|
|
18
|
+
"integration-baseline",
|
|
19
|
+
"platform-baseline",
|
|
20
|
+
"reverse-baseline",
|
|
21
|
+
];
|
|
22
|
+
export const PackSeedSchema = z.object({
|
|
23
|
+
version: z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(SEMVER_PATTERN, "version must be semver (X.Y.Z)"),
|
|
26
|
+
scope: z
|
|
27
|
+
.string()
|
|
28
|
+
.regex(/^[a-z0-9-]+$/, "scope must be lower-kebab-case slug"),
|
|
29
|
+
// Optional per ADR 0176 §4.6: absent = profile-agnostic pack.
|
|
30
|
+
forwardBase: z
|
|
31
|
+
.object({
|
|
32
|
+
bundle: z.enum(KNOWN_FORWARD_BASES),
|
|
33
|
+
minimumVersion: z
|
|
34
|
+
.string()
|
|
35
|
+
.regex(/^v\d+\.\d+\.\d+$/, "forward_base.minimum_version must be vX.Y.Z"),
|
|
36
|
+
sourceRepo: z.string().min(1),
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
// Vendor-base pack pin (M-ADR 0175, seed schema 1.2.0). Present only on a
|
|
40
|
+
// vendor-base pack: names the vendor and the vendor version whose documented
|
|
41
|
+
// truth this pack's content encodes.
|
|
42
|
+
//
|
|
43
|
+
// The point of a PACK-LEVEL pin is that a consuming SDD does not restate it. A
|
|
44
|
+
// consumer pins the pack exactly (its bundles-list entry + sha256) and the
|
|
45
|
+
// vendor version it inherits is DERIVED from here. Recording it consumer-side
|
|
46
|
+
// as well would be a second source of truth that can silently disagree, and the
|
|
47
|
+
// pack is authoritative for its own vendor pin.
|
|
48
|
+
//
|
|
49
|
+
// `vendorVersion` is free-form by design: it carries either a formal vendor
|
|
50
|
+
// version or the anchor schema's `<vendor>-docs@YYYY-MM-DD` snapshot form, and
|
|
51
|
+
// vendors' own version strings are not the method's to constrain.
|
|
52
|
+
vendorBase: z
|
|
53
|
+
.object({
|
|
54
|
+
vendor: z
|
|
55
|
+
.string()
|
|
56
|
+
.regex(/^[a-z][a-z0-9-]*$/, "vendor_base.vendor must be a lower-kebab-case slug"),
|
|
57
|
+
vendorVersion: z.string().min(1, "vendor_base.vendor_version must be non-empty"),
|
|
58
|
+
})
|
|
59
|
+
.optional(),
|
|
60
|
+
minimumSddCliVersion: z
|
|
61
|
+
.string()
|
|
62
|
+
.regex(SEMVER_PATTERN, "minimum_sdd_cli_version must be semver (X.Y.Z)"),
|
|
63
|
+
description: z.string().min(1),
|
|
64
|
+
include: z.array(z.string()).optional(),
|
|
65
|
+
managedPaths: z
|
|
66
|
+
.array(z.string().min(1))
|
|
67
|
+
.min(1, "managed_paths must have at least one entry"),
|
|
68
|
+
forbiddenPaths: z.array(z.string()).optional(),
|
|
69
|
+
// ADR 0176 §4.3: scaffold-source path prefixes; each must cover at
|
|
70
|
+
// least one managed_paths entry (checked in the superRefine below).
|
|
71
|
+
scaffoldSourcePaths: z.array(z.string().min(1)).optional(),
|
|
72
|
+
requiresPack: z
|
|
73
|
+
.object({
|
|
74
|
+
name: z.string().min(1),
|
|
75
|
+
version: z
|
|
76
|
+
.string()
|
|
77
|
+
.regex(VERSIONED_RANGE_PATTERN, "requires_pack.version must be a semver range (e.g. ^v1.2.0)"),
|
|
78
|
+
})
|
|
79
|
+
.optional(),
|
|
80
|
+
sourcePath: z.string(),
|
|
81
|
+
});
|
|
82
|
+
const PackSeedSchemaRefined = PackSeedSchema.superRefine((seed, ctx) => {
|
|
83
|
+
// A vendor-base pack is PROFILE-AGNOSTIC (M-ADR 0176 §4.6, M-ADR 0175): its
|
|
84
|
+
// content is vendor-defined, so it layers over any baseline and must not pin
|
|
85
|
+
// one. Declaring both is a contradiction the producer should hear at validate
|
|
86
|
+
// time rather than discover when a consumer on a different baseline cannot
|
|
87
|
+
// apply it.
|
|
88
|
+
if (seed.vendorBase && seed.forwardBase) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: z.ZodIssueCode.custom,
|
|
91
|
+
path: ["forwardBase"],
|
|
92
|
+
message: "a vendor_base pack is profile-agnostic and must omit forward_base " +
|
|
93
|
+
"(M-ADR 0176 §4.6); remove forward_base, or remove vendor_base if this " +
|
|
94
|
+
"is not a vendor-base pack",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (!seed.scaffoldSourcePaths)
|
|
98
|
+
return;
|
|
99
|
+
for (const [i, prefix] of seed.scaffoldSourcePaths.entries()) {
|
|
100
|
+
const covers = seed.managedPaths.some((mp) => mp.startsWith(prefix));
|
|
101
|
+
if (!covers) {
|
|
102
|
+
ctx.addIssue({
|
|
103
|
+
code: z.ZodIssueCode.custom,
|
|
104
|
+
path: ["scaffoldSourcePaths", i],
|
|
105
|
+
message: `scaffold_source_paths entry "${prefix}" matches no managed_paths entry`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
export function validateSeed(seed) {
|
|
111
|
+
const result = PackSeedSchemaRefined.safeParse(seed);
|
|
112
|
+
if (result.success) {
|
|
113
|
+
return { ok: true, errors: [] };
|
|
114
|
+
}
|
|
115
|
+
const errors = result.error.issues.map((issue) => ({
|
|
116
|
+
field: issue.path.join("."),
|
|
117
|
+
message: issue.message,
|
|
118
|
+
}));
|
|
119
|
+
return { ok: false, errors };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Adopter pack seed file. Per ADR 0151 + context-pack-seed-schema.yaml.
|
|
6
|
+
*
|
|
7
|
+
* Loaded by `sdd-cli pack build` and `sdd-cli pack validate` from
|
|
8
|
+
* `orchestration/distribution/{pack-name}-seed.yaml` in adopter repos.
|
|
9
|
+
*
|
|
10
|
+
* Includes are resolved before validation: the merged seed presents
|
|
11
|
+
* all `include`'d managed_paths + forbidden_paths concatenated with
|
|
12
|
+
* the host seed's own.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Authored seed keys, snake_case → the camelCase field they map to.
|
|
16
|
+
*
|
|
17
|
+
* THE SINGLE SOURCE OF TRUTH for "what may appear in a seed file". It lived only
|
|
18
|
+
* inside the canonical-conformance test, which meant the loader had no idea what a
|
|
19
|
+
* valid key set was — so an unrecognised key was dropped by the hand-mapping below
|
|
20
|
+
* without a word. An adopter following a schema that documents a field their sdd-cli
|
|
21
|
+
* predates got a clean validation and a silent no-op: the same failure M-ADR 0175's
|
|
22
|
+
* history records for `packaging`, and the same one fixed for `.sdd/sdd-manifest.yaml`
|
|
23
|
+
* in 0.110.1 — but by a different mechanism, which is why that fix did not cover this.
|
|
24
|
+
*
|
|
25
|
+
* The conformance test now imports this rather than declaring its own copy. Two
|
|
26
|
+
* hand-maintained lists of the same facts is how the lifecycle expectation drifted.
|
|
27
|
+
*/
|
|
28
|
+
export const SEED_KEY_TO_FIELD = {
|
|
29
|
+
version: "version",
|
|
30
|
+
scope: "scope",
|
|
31
|
+
forward_base: "forwardBase",
|
|
32
|
+
vendor_base: "vendorBase",
|
|
33
|
+
minimum_sdd_cli_version: "minimumSddCliVersion",
|
|
34
|
+
description: "description",
|
|
35
|
+
include: "include",
|
|
36
|
+
managed_paths: "managedPaths",
|
|
37
|
+
forbidden_paths: "forbiddenPaths",
|
|
38
|
+
scaffold_source_paths: "scaffoldSourcePaths",
|
|
39
|
+
requires_pack: "requiresPack",
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Top-level keys in a seed file that this build does not recognise.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately TOP-LEVEL only, stated as a limit rather than left implied: nested
|
|
45
|
+
* blocks (`forward_base`, `vendor_base`, `requires_pack`) are hand-mapped field by
|
|
46
|
+
* field too, so an unknown key inside one is still dropped silently today.
|
|
47
|
+
*/
|
|
48
|
+
export function unknownSeedKeys(raw) {
|
|
49
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
50
|
+
return [];
|
|
51
|
+
return Object.keys(raw)
|
|
52
|
+
.filter((k) => !(k in SEED_KEY_TO_FIELD))
|
|
53
|
+
.sort();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load a seed file from disk, resolve its `include:` directives,
|
|
57
|
+
* and return a merged PackSeed. Throws if any include cannot be read
|
|
58
|
+
* or if circular includes are detected.
|
|
59
|
+
*
|
|
60
|
+
* NOTE: This does not validate the seed structure — only parses YAML
|
|
61
|
+
* and assembles the typed shape. Use `validateSeed` (separate module)
|
|
62
|
+
* for schema enforcement.
|
|
63
|
+
*/
|
|
64
|
+
export async function loadSeed(seedPath) {
|
|
65
|
+
const absPath = isAbsolute(seedPath) ? seedPath : resolve(seedPath);
|
|
66
|
+
const visited = new Set();
|
|
67
|
+
return loadSeedRecursive(absPath, visited);
|
|
68
|
+
}
|
|
69
|
+
async function loadSeedRecursive(absPath, visited) {
|
|
70
|
+
if (visited.has(absPath)) {
|
|
71
|
+
throw new Error(`Circular include detected at ${absPath}`);
|
|
72
|
+
}
|
|
73
|
+
visited.add(absPath);
|
|
74
|
+
let raw;
|
|
75
|
+
try {
|
|
76
|
+
raw = await readFile(absPath, "utf8");
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new Error(`Cannot read seed file ${absPath}: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
const parsed = parseYaml(raw);
|
|
82
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
83
|
+
throw new Error(`Seed file ${absPath} is not a YAML object`);
|
|
84
|
+
}
|
|
85
|
+
const baseDir = dirname(absPath);
|
|
86
|
+
const includes = Array.isArray(parsed.include) ? parsed.include : [];
|
|
87
|
+
const includedManaged = [];
|
|
88
|
+
const includedForbidden = [];
|
|
89
|
+
const includedUnknown = [];
|
|
90
|
+
for (const inc of includes) {
|
|
91
|
+
if (typeof inc !== "string") {
|
|
92
|
+
throw new Error(`Non-string include entry in ${absPath}: ${String(inc)}`);
|
|
93
|
+
}
|
|
94
|
+
const incPath = isAbsolute(inc) ? inc : resolve(baseDir, inc);
|
|
95
|
+
const includedSeed = await loadSeedRecursive(incPath, visited);
|
|
96
|
+
includedManaged.push(...includedSeed.managedPaths);
|
|
97
|
+
if (includedSeed.forbiddenPaths) {
|
|
98
|
+
includedForbidden.push(...includedSeed.forbiddenPaths);
|
|
99
|
+
}
|
|
100
|
+
if (includedSeed.unknownKeys)
|
|
101
|
+
includedUnknown.push(...includedSeed.unknownKeys);
|
|
102
|
+
}
|
|
103
|
+
// Unknown keys from this file, plus everything its includes reported. An included
|
|
104
|
+
// seed's stray key is as silently dropped as the root's, so the queue is unioned.
|
|
105
|
+
const ownUnknown = unknownSeedKeys(parsed);
|
|
106
|
+
const own = parsed;
|
|
107
|
+
const managedPaths = [
|
|
108
|
+
...includedManaged,
|
|
109
|
+
...asStringArray(own.managed_paths, "managed_paths", absPath),
|
|
110
|
+
];
|
|
111
|
+
const forbiddenPaths = includedForbidden.length > 0 || Array.isArray(own.forbidden_paths)
|
|
112
|
+
? [
|
|
113
|
+
...includedForbidden,
|
|
114
|
+
...asStringArray(own.forbidden_paths, "forbidden_paths", absPath, true),
|
|
115
|
+
]
|
|
116
|
+
: undefined;
|
|
117
|
+
const scaffoldSourcePaths = Array.isArray(own.scaffold_source_paths)
|
|
118
|
+
? asStringArray(own.scaffold_source_paths, "scaffold_source_paths", absPath, true)
|
|
119
|
+
: undefined;
|
|
120
|
+
const seed = {
|
|
121
|
+
version: asString(own.version, "version", absPath),
|
|
122
|
+
scope: asString(own.scope, "scope", absPath),
|
|
123
|
+
// forward_base optional per ADR 0176 §4.6 (profile-agnostic packs omit it)
|
|
124
|
+
...(own.forward_base && {
|
|
125
|
+
forwardBase: {
|
|
126
|
+
bundle: asString(own.forward_base.bundle, "forward_base.bundle", absPath),
|
|
127
|
+
minimumVersion: asString(own.forward_base.minimum_version, "forward_base.minimum_version", absPath),
|
|
128
|
+
sourceRepo: own.forward_base.source_repo
|
|
129
|
+
? asString(own.forward_base.source_repo, "forward_base.source_repo", absPath)
|
|
130
|
+
: "sdd-method",
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
// vendor_base present only on a vendor-base pack (M-ADR 0175); the
|
|
134
|
+
// profile-agnostic/forward_base contradiction is caught in validateSeed.
|
|
135
|
+
...(own.vendor_base && {
|
|
136
|
+
vendorBase: {
|
|
137
|
+
vendor: asString(own.vendor_base.vendor, "vendor_base.vendor", absPath),
|
|
138
|
+
vendorVersion: asString(own.vendor_base.vendor_version, "vendor_base.vendor_version", absPath),
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
minimumSddCliVersion: asString(own.minimum_sdd_cli_version, "minimum_sdd_cli_version", absPath),
|
|
142
|
+
description: asString(own.description, "description", absPath),
|
|
143
|
+
managedPaths,
|
|
144
|
+
sourcePath: absPath,
|
|
145
|
+
...(ownUnknown.length + includedUnknown.length > 0
|
|
146
|
+
? { unknownKeys: [...new Set([...includedUnknown, ...ownUnknown])].sort() }
|
|
147
|
+
: {}),
|
|
148
|
+
...(includes.length > 0 && { include: includes }),
|
|
149
|
+
...(forbiddenPaths && { forbiddenPaths }),
|
|
150
|
+
...(scaffoldSourcePaths && scaffoldSourcePaths.length > 0 && { scaffoldSourcePaths }),
|
|
151
|
+
...(own.requires_pack && {
|
|
152
|
+
requiresPack: {
|
|
153
|
+
name: asString(own.requires_pack.name, "requires_pack.name", absPath),
|
|
154
|
+
version: asString(own.requires_pack.version, "requires_pack.version", absPath),
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
return seed;
|
|
159
|
+
}
|
|
160
|
+
function asString(value, field, file) {
|
|
161
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
162
|
+
throw new Error(`Seed field "${field}" must be a non-empty string in ${file}`);
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
function asStringArray(value, field, file, allowEmpty = false) {
|
|
167
|
+
if (value === undefined || value === null) {
|
|
168
|
+
if (allowEmpty)
|
|
169
|
+
return [];
|
|
170
|
+
throw new Error(`Seed field "${field}" is required in ${file}`);
|
|
171
|
+
}
|
|
172
|
+
if (!Array.isArray(value)) {
|
|
173
|
+
throw new Error(`Seed field "${field}" must be a list in ${file}`);
|
|
174
|
+
}
|
|
175
|
+
for (const entry of value) {
|
|
176
|
+
if (typeof entry !== "string") {
|
|
177
|
+
throw new Error(`Seed field "${field}" entries must be strings in ${file}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=seed.js.map
|