@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.
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 +0 -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 +437 -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 +54 -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 +48 -0
  198. package/dist/verify-program.js +122 -0
  199. package/dist/verify-version.json +4 -0
  200. package/package.json +32 -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,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
@@ -0,0 +1,49 @@
1
+ // `pack validate` — reads and checks a context-pack seed; writes nothing.
2
+ // Kept free of any import that reaches `./builder.js`, so that importing it
3
+ // cannot drag pack-building code into a package that must not author
4
+ // (M-ADR 0212 §4.3b). `index.ts` re-exports it, so existing imports are unchanged.
5
+ import { validateSeed } from "./schema.js";
6
+ import { loadSeed } from "./seed.js";
7
+ import { getSddCliVersion } from "../package-version.js";
8
+ import { fail, info, pass, section } from "./log.js";
9
+ export async function runPackValidate(opts, stdout = (s) => process.stdout.write(s)) {
10
+ stdout(section("Pack validate"));
11
+ let seed;
12
+ try {
13
+ seed = await loadSeed(opts.seedPath);
14
+ }
15
+ catch (err) {
16
+ stdout(fail(`Seed load failed: ${err.message}`));
17
+ return { exitCode: 1, errors: [{ field: "(load)", message: err.message }] };
18
+ }
19
+ // A seed key this build does not know is REPORTED rather than dropped in silence.
20
+ // Not fatal: an older sdd-cli must still build a seed that declares something newer
21
+ // than itself, exactly as for `.sdd/sdd-manifest.yaml`. Silence here is what let an
22
+ // adopter follow the schema and get a clean validation with no effect.
23
+ if (seed.unknownKeys && seed.unknownKeys.length > 0) {
24
+ stdout(info(`${seed.unknownKeys.length} seed key(s) not understood by sdd-cli ${getSddCliVersion()} ` +
25
+ `and therefore IGNORED: ${seed.unknownKeys.join(", ")}. If the seed follows a newer ` +
26
+ `context-pack-seed-schema than this build, upgrade sdd-cli; otherwise remove or correct the key.`));
27
+ }
28
+ const validation = validateSeed(seed);
29
+ if (!validation.ok) {
30
+ stdout(fail("Seed validation failed:"));
31
+ for (const e of validation.errors) {
32
+ stdout(fail(` ${e.field}: ${e.message}`));
33
+ }
34
+ return { exitCode: 1, errors: validation.errors };
35
+ }
36
+ stdout(pass(`Seed valid: ${seed.scope} v${seed.version}`));
37
+ stdout(info(seed.forwardBase
38
+ ? `Forward base: ${seed.forwardBase.bundle} >= ${seed.forwardBase.minimumVersion}`
39
+ : "Forward base: none (profile-agnostic pack, ADR 0176 §4.6)"));
40
+ if (seed.scaffoldSourcePaths) {
41
+ stdout(info(`Scaffold-source prefixes: ${seed.scaffoldSourcePaths.join(", ")}`));
42
+ }
43
+ stdout(info(`Managed paths: ${seed.managedPaths.length}`));
44
+ if (seed.requiresPack) {
45
+ stdout(info(`requires_pack: ${seed.requiresPack.name} ${seed.requiresPack.version}`));
46
+ }
47
+ return { exitCode: 0, errors: [] };
48
+ }
49
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,92 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, resolve, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Read the package version from package.json at runtime so the various
6
+ * version-string consumers (CLI --version, MCP server identity,
7
+ * catalogue error messages) cannot drift from the published version.
8
+ *
9
+ * Resolves relative to this module's compiled location:
10
+ * - In source layout: src/lib/package-version.ts → package.json (../../)
11
+ * - In dist layout: dist/lib/package-version.js → package.json (../../)
12
+ *
13
+ * Cached on first call.
14
+ */
15
+ let cached;
16
+ let cachedEngine;
17
+ /**
18
+ * The version of the sdd-cli ENGINE this code is. In @sdd-method/sdd-cli it is the package
19
+ * version. In a persona package (verify-cli, maintain-cli) the package version is the
20
+ * persona's own (0.1.0, 1.0.0), while every check and stamp that means "which sdd-cli is
21
+ * this" (a bundle's minimum_sdd_cli_version, the CLI version stamped into catalogues, the
22
+ * MCP server identity) must see the engine it was built from. The persona build writes that
23
+ * as `sddCliVersion` in the package's package.json. Found by maintain-cli's acceptance run:
24
+ * its sync refused every bundle because 0.1.0 < 1.41.0.
25
+ */
26
+ export function getSddCliVersion() {
27
+ if (cachedEngine !== undefined)
28
+ return cachedEngine;
29
+ const here = dirname(fileURLToPath(import.meta.url));
30
+ const pkg = JSON.parse(readFileSync(resolve(here, "..", "..", "package.json"), "utf8"));
31
+ cachedEngine = pkg.sddCliVersion ?? pkg.version;
32
+ return cachedEngine;
33
+ }
34
+ /** The package's own version: what `--version` prints. For the engine version, use getSddCliVersion. */
35
+ export function getPackageVersion() {
36
+ if (cached !== undefined)
37
+ return cached;
38
+ const here = dirname(fileURLToPath(import.meta.url));
39
+ const pkgPath = resolve(here, "..", "..", "package.json");
40
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
41
+ cached = pkg.version;
42
+ return cached;
43
+ }
44
+ /**
45
+ * The @sdd-method/verify-cli version released WITH this sdd-cli (M-ADR 0212
46
+ * §6.1 Phase 1). The method-managed CI workflow installs exactly this version,
47
+ * so the CLI that syncs a repository decides which verifier judges it.
48
+ *
49
+ * Read from dist/verify-version.json, which the build copies from
50
+ * scripts/verify-version.json. Never from scripts/ in a built layout: the global
51
+ * `sdd-cli` on the method's machines is a link into the checkout, so reading the
52
+ * committed file live would render a version bumped in source but not yet
53
+ * published into every workflow synced that day, while dist/ still lagged.
54
+ * Source layout (tests) has no copy and reads scripts/ directly.
55
+ *
56
+ * Throws rather than defaults: a missing version must not render a floating install.
57
+ */
58
+ const cachedPersona = {};
59
+ /**
60
+ * One exact npm version: X.Y.Z, optionally with a prerelease tag (1.1.0-rc.0), which a release
61
+ * candidate published under the `next` dist-tag needs so hosted CI can run it before the release.
62
+ * Never a range, a tag name or a wildcard.
63
+ */
64
+ export const EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$/;
65
+ function personaVersion(persona) {
66
+ const hit = cachedPersona[persona];
67
+ if (hit !== undefined)
68
+ return hit;
69
+ const here = dirname(fileURLToPath(import.meta.url));
70
+ const fromSource = here.endsWith(`${sep}src${sep}lib`);
71
+ const file = fromSource
72
+ ? resolve(here, "..", "..", "scripts", `${persona}-version.json`)
73
+ : resolve(here, "..", `${persona}-version.json`);
74
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
75
+ if (typeof parsed.version !== "string" || !EXACT_VERSION.test(parsed.version)) {
76
+ throw new Error(`${file} has no semver \`version\`; refusing to render an unpinned ${persona}-cli install`);
77
+ }
78
+ cachedPersona[persona] = parsed.version;
79
+ return parsed.version;
80
+ }
81
+ export function getVerifyCliVersion() {
82
+ return personaVersion("verify");
83
+ }
84
+ /**
85
+ * The @sdd-method/maintain-cli version released WITH this sdd-cli. The managed CI workflow's
86
+ * catalogue freshness step installs exactly this version (M-ADR 0218 §5; ruled 2026-10-15, sdm-jts.6).
87
+ * Read the same way as the verify version, for the same reason (dist/ copy, never scripts/ live).
88
+ */
89
+ export function getMaintainCliVersion() {
90
+ return personaVersion("maintain");
91
+ }
92
+ //# sourceMappingURL=package-version.js.map
@@ -0,0 +1,69 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { readSdd } from "./read-sdd.js";
4
+ import { PILOT_TEMPLATE_REL_PATHS, renderPilotKickoff, resolvePilotTemplate } from "./render-template.js";
5
+ export async function runPlanPilot(inputs, write) {
6
+ const templatePath = await resolvePilotTemplate(inputs.sddRoot);
7
+ if (templatePath === undefined) {
8
+ const msg = `[FAIL] pilot-kickoff template not found at ${join(inputs.sddRoot, PILOT_TEMPLATE_REL_PATHS[0])}. Sync the method baseline first.\n`;
9
+ write(msg);
10
+ return {
11
+ exitCode: 1,
12
+ outputPath: inputs.outputPath ?? defaultOutputPath(inputs),
13
+ rendered: "",
14
+ dryRun: inputs.dryRun === true,
15
+ };
16
+ }
17
+ const snapshot = await readSdd({
18
+ sddRoot: inputs.sddRoot,
19
+ service: inputs.service,
20
+ capability: inputs.capability,
21
+ domain: inputs.domain,
22
+ });
23
+ const rendered = await renderPilotKickoff({
24
+ snapshot,
25
+ platformCliVersionHint: inputs.platformCliVersionHint ?? "0.5.0",
26
+ today: inputs.today ?? new Date().toISOString().slice(0, 10),
27
+ });
28
+ const outputPath = inputs.outputPath ?? defaultOutputPath(inputs);
29
+ const dryRun = inputs.dryRun === true;
30
+ if (dryRun) {
31
+ write(rendered);
32
+ write(`\n[INFO] dry-run: would write to ${outputPath}\n`);
33
+ return { exitCode: 0, outputPath, rendered, dryRun: true };
34
+ }
35
+ const outputAbs = resolveOutputAbs(inputs.sddRoot, outputPath);
36
+ if (await fileExists(outputAbs)) {
37
+ write(`[FAIL] refusing to overwrite existing file at ${outputAbs}. Move or rename, or re-run with --dry-run.\n`);
38
+ return { exitCode: 1, outputPath: outputAbs, rendered, dryRun: false };
39
+ }
40
+ await mkdir(dirname(outputAbs), { recursive: true });
41
+ await writeFile(outputAbs, rendered, "utf8");
42
+ write(`[PASS] pilot-kickoff scaffolded at ${outputAbs}\n` +
43
+ ` aggregates: ${snapshot.repoConfig.aggregates.length}\n` +
44
+ ` feature specs: ${snapshot.featureSpecCount}\n` +
45
+ ` capability spec: ${snapshot.capabilitySpecPresent ? "present" : "MISSING"}\n` +
46
+ ` traceability manifest: ${snapshot.traceabilityManifestPresent ? "present" : "MISSING"}\n`);
47
+ return { exitCode: 0, outputPath: outputAbs, rendered, dryRun: false };
48
+ }
49
+ function defaultOutputPath(inputs) {
50
+ return join("content/product/plans", `${inputs.service}-pilot-kickoff.md`);
51
+ }
52
+ function resolveOutputAbs(sddRoot, outputPath) {
53
+ if (outputPath.startsWith("/"))
54
+ return outputPath;
55
+ return join(sddRoot, outputPath);
56
+ }
57
+ async function fileExists(path) {
58
+ try {
59
+ await access(path);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ // Keep readFile import in use; some test harness entry points trigger module
67
+ // tree-shake warnings otherwise.
68
+ void readFile;
69
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,102 @@
1
+ import { access, readdir, stat } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ import { liveReader } from "../sdd-reader.js";
4
+ import { parseRepoConfigFile } from "../sdd-index/repo-config.js";
5
+ /**
6
+ * Read everything `plan --pilot` needs from the SDD filesystem: the service's
7
+ * repo-config manifest (domain, service_type, aggregates, dependencies,
8
+ * contracts), the count of feature spec files under the capability, and
9
+ * presence of the capability spec + traceability manifest.
10
+ *
11
+ * Repo-config parsing delegates to the shared sdd-index module so the
12
+ * `plan --pilot` path and the MCP dependency-graph tools see the same
13
+ * view. The richer ADR 0110 shape (package/version/stability) is
14
+ * flattened back to path-strings here to preserve the legacy
15
+ * `RepoConfig` contract.
16
+ */
17
+ export async function readSdd(inputs) {
18
+ const { sddRoot, service, capability, domain } = inputs;
19
+ const manifestAbsPath = join(sddRoot, "orchestration/repo-config", domain, `${service}.yaml`);
20
+ const repoConfig = await readRepoConfigFor(sddRoot, manifestAbsPath);
21
+ const featuresDir = join(sddRoot, "docs/domains", domain, "product/features", capability);
22
+ const featureSpecs = await listFeatureSpecs(featuresDir);
23
+ const featureSpecRelativePaths = featureSpecs.map((p) => join("docs/domains", domain, "product/features", capability, p));
24
+ const capabilitySpecPath = join("docs/domains", domain, "product", `${capability}.md`);
25
+ const capabilitySpecPresent = await fileExists(join(sddRoot, capabilitySpecPath));
26
+ const traceabilityManifestPath = join("docs/domains", domain, "product", `${capability}.traceability.yaml`);
27
+ const traceabilityManifestPresent = await fileExists(join(sddRoot, traceabilityManifestPath));
28
+ return {
29
+ sddRoot,
30
+ service,
31
+ capability,
32
+ domain,
33
+ repoConfig,
34
+ featureSpecCount: featureSpecs.length,
35
+ featureSpecRelativePaths,
36
+ capabilitySpecPresent,
37
+ capabilitySpecPath,
38
+ traceabilityManifestPresent,
39
+ traceabilityManifestPath,
40
+ };
41
+ }
42
+ async function readRepoConfigFor(sddRoot, manifestAbsPath) {
43
+ const empty = {
44
+ path: manifestAbsPath,
45
+ aggregates: [],
46
+ internalDependencies: [],
47
+ contracts: { openapi: [], proto: [], avro: [], spicedb: [], graphql: [] },
48
+ };
49
+ if (!(await fileExists(manifestAbsPath)))
50
+ return empty;
51
+ const reader = liveReader(sddRoot);
52
+ const relPath = relative(sddRoot, manifestAbsPath);
53
+ const warnings = [];
54
+ const node = parseRepoConfigFile(reader, relPath, warnings);
55
+ if (!node)
56
+ return empty;
57
+ return legacyShape(node, manifestAbsPath);
58
+ }
59
+ function legacyShape(node, sourcePath) {
60
+ const byKind = {
61
+ openapi: [],
62
+ proto: [],
63
+ avro: [],
64
+ spicedb: [],
65
+ };
66
+ for (const ref of node.contracts)
67
+ byKind[ref.kind].push(ref.path);
68
+ return {
69
+ path: sourcePath,
70
+ ...(node.description !== undefined ? { description: node.description } : {}),
71
+ ...(node.serviceType !== undefined ? { serviceType: node.serviceType } : {}),
72
+ aggregates: Array.from(node.aggregates),
73
+ internalDependencies: Array.from(node.internalDependencies),
74
+ contracts: byKind,
75
+ };
76
+ }
77
+ async function listFeatureSpecs(featuresDir) {
78
+ if (!(await fileExists(featuresDir)))
79
+ return [];
80
+ const entries = await readdir(featuresDir);
81
+ const specs = [];
82
+ for (const entry of entries) {
83
+ if (!entry.endsWith(".md"))
84
+ continue;
85
+ const full = join(featuresDir, entry);
86
+ const s = await stat(full);
87
+ if (s.isFile())
88
+ specs.push(entry);
89
+ }
90
+ specs.sort();
91
+ return specs;
92
+ }
93
+ async function fileExists(path) {
94
+ try {
95
+ await access(path);
96
+ return true;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ //# sourceMappingURL=read-sdd.js.map