@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.
Files changed (206) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +17 -0
  3. package/README.md +6 -2
  4. package/dist/lib/adr-origin.js +59 -0
  5. package/dist/lib/agent-harness.js +28 -0
  6. package/dist/lib/briefing/index.js +13 -0
  7. package/dist/lib/briefing/render.js +123 -0
  8. package/dist/lib/briefing/variables.js +92 -0
  9. package/dist/lib/bundle.js +123 -0
  10. package/dist/lib/catalogue/aggregate.js +2404 -0
  11. package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
  12. package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
  13. package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
  14. package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
  15. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
  16. package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
  17. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
  18. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
  19. package/dist/lib/catalogue/archetype/resolver.js +103 -0
  20. package/dist/lib/catalogue/archetype/wireframe.js +418 -0
  21. package/dist/lib/catalogue/baseline-check.js +151 -0
  22. package/dist/lib/catalogue/build.js +1502 -0
  23. package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
  24. package/dist/lib/catalogue/builders/adrs.js +222 -0
  25. package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
  26. package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
  27. package/dist/lib/catalogue/builders/c4-components.js +229 -0
  28. package/dist/lib/catalogue/builders/c4-containers.js +240 -0
  29. package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
  30. package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
  31. package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
  32. package/dist/lib/catalogue/builders/c4-systems.js +98 -0
  33. package/dist/lib/catalogue/builders/capabilities.js +488 -0
  34. package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
  35. package/dist/lib/catalogue/builders/capability-edges.js +256 -0
  36. package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
  37. package/dist/lib/catalogue/builders/capability-spec.js +348 -0
  38. package/dist/lib/catalogue/builders/container-dependency.js +183 -0
  39. package/dist/lib/catalogue/builders/contracts.js +402 -0
  40. package/dist/lib/catalogue/builders/data-items.js +344 -0
  41. package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
  42. package/dist/lib/catalogue/builders/domain-models.js +175 -0
  43. package/dist/lib/catalogue/builders/domains.js +119 -0
  44. package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
  45. package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
  46. package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
  47. package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
  48. package/dist/lib/catalogue/builders/entity-slug.js +47 -0
  49. package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
  50. package/dist/lib/catalogue/builders/event-edges.js +327 -0
  51. package/dist/lib/catalogue/builders/events.js +168 -0
  52. package/dist/lib/catalogue/builders/evidence-class.js +43 -0
  53. package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
  54. package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
  55. package/dist/lib/catalogue/builders/feature-contract.js +77 -0
  56. package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
  57. package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
  58. package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
  59. package/dist/lib/catalogue/builders/feature-groups.js +63 -0
  60. package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
  61. package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
  62. package/dist/lib/catalogue/builders/features.js +300 -0
  63. package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
  64. package/dist/lib/catalogue/builders/glossary.js +204 -0
  65. package/dist/lib/catalogue/builders/implementations.js +214 -0
  66. package/dist/lib/catalogue/builders/index.js +64 -0
  67. package/dist/lib/catalogue/builders/integration-components.js +344 -0
  68. package/dist/lib/catalogue/builders/integrations.js +181 -0
  69. package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
  70. package/dist/lib/catalogue/builders/persona-container.js +133 -0
  71. package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
  72. package/dist/lib/catalogue/builders/personas.js +152 -0
  73. package/dist/lib/catalogue/builders/product-edges.js +106 -0
  74. package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
  75. package/dist/lib/catalogue/builders/products.js +162 -0
  76. package/dist/lib/catalogue/builders/quality-scores.js +635 -0
  77. package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
  78. package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
  79. package/dist/lib/catalogue/builders/repo-edges.js +340 -0
  80. package/dist/lib/catalogue/builders/repositories.js +458 -0
  81. package/dist/lib/catalogue/builders/scaffolds.js +197 -0
  82. package/dist/lib/catalogue/builders/screen-tree.js +222 -0
  83. package/dist/lib/catalogue/builders/screens.js +396 -0
  84. package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
  85. package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
  86. package/dist/lib/catalogue/builders/service-contract.js +74 -0
  87. package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
  88. package/dist/lib/catalogue/builders/services.js +232 -0
  89. package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
  90. package/dist/lib/catalogue/builders/user-journeys.js +253 -0
  91. package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
  92. package/dist/lib/catalogue/cli-version.js +10 -0
  93. package/dist/lib/catalogue/contract-id.js +49 -0
  94. package/dist/lib/catalogue/contract-path-resolution.js +149 -0
  95. package/dist/lib/catalogue/coverage-predicates.js +603 -0
  96. package/dist/lib/catalogue/coverage-render.js +552 -0
  97. package/dist/lib/catalogue/coverage-run.js +115 -0
  98. package/dist/lib/catalogue/csv-reader.js +97 -0
  99. package/dist/lib/catalogue/csv-writer.js +96 -0
  100. package/dist/lib/catalogue/data-item-coverage.js +142 -0
  101. package/dist/lib/catalogue/data-plane-coherence.js +77 -0
  102. package/dist/lib/catalogue/ddl-generator.js +233 -0
  103. package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
  104. package/dist/lib/catalogue/engine-validator.js +239 -0
  105. package/dist/lib/catalogue/ext-id.js +76 -0
  106. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
  107. package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
  108. package/dist/lib/catalogue/finding-identity.js +183 -0
  109. package/dist/lib/catalogue/findings.js +46 -0
  110. package/dist/lib/catalogue/fk-validator.js +136 -0
  111. package/dist/lib/catalogue/freshness.js +69 -0
  112. package/dist/lib/catalogue/gate-checks.js +338 -0
  113. package/dist/lib/catalogue/graph-projection.js +346 -0
  114. package/dist/lib/catalogue/graph-run.js +64 -0
  115. package/dist/lib/catalogue/impact-run.js +228 -0
  116. package/dist/lib/catalogue/manifest-loader.js +435 -0
  117. package/dist/lib/catalogue/platform-scope.js +7 -0
  118. package/dist/lib/catalogue/population-checks.js +87 -0
  119. package/dist/lib/catalogue/relationship-semantics.js +66 -0
  120. package/dist/lib/catalogue/reverse-product-quality.js +99 -0
  121. package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
  122. package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
  123. package/dist/lib/catalogue/scaffold-delta.js +261 -0
  124. package/dist/lib/catalogue/schema-compat.js +52 -0
  125. package/dist/lib/catalogue/schema-loader.js +87 -0
  126. package/dist/lib/catalogue/scores-coverage.js +573 -0
  127. package/dist/lib/catalogue/screen-validation-loader.js +89 -0
  128. package/dist/lib/catalogue/service-architecture-parser.js +193 -0
  129. package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
  130. package/dist/lib/catalogue/staleness-verdicts.js +345 -0
  131. package/dist/lib/catalogue/traceability-loader.js +350 -0
  132. package/dist/lib/catalogue/validate.js +200 -0
  133. package/dist/lib/catalogue/version.js +18 -0
  134. package/dist/lib/ci-binding/index.js +220 -0
  135. package/dist/lib/ci-binding/module.js +204 -0
  136. package/dist/lib/conformance/binding.js +70 -0
  137. package/dist/lib/context-pull/index.js +151 -0
  138. package/dist/lib/context-pull/resolve.js +70 -0
  139. package/dist/lib/generate-briefing/index.js +77 -0
  140. package/dist/lib/hooks-seed/copilot.js +167 -0
  141. package/dist/lib/hooks-seed/entries.js +47 -0
  142. package/dist/lib/hooks-seed/index.js +17 -0
  143. package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
  144. package/dist/lib/hooks-seed/merge.js +128 -0
  145. package/dist/lib/hooks-seed/types.js +20 -0
  146. package/dist/lib/init/ci-workflow.js +98 -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/mcp-seed/entries.js +14 -0
  152. package/dist/lib/mcp-seed/index.js +28 -0
  153. package/dist/lib/mcp-seed/merge.js +99 -0
  154. package/dist/lib/mcp-seed/types.js +13 -0
  155. package/dist/lib/pack/builder.js +303 -0
  156. package/dist/lib/pack/index.js +87 -0
  157. package/dist/lib/pack/leakage.js +144 -0
  158. package/dist/lib/pack/log.js +18 -0
  159. package/dist/lib/pack/publish-transform.js +233 -0
  160. package/dist/lib/pack/reference-closure.js +125 -0
  161. package/dist/lib/pack/schema.js +121 -0
  162. package/dist/lib/pack/seed.js +182 -0
  163. package/dist/lib/pack/validate.js +49 -0
  164. package/dist/lib/package-version.js +92 -0
  165. package/dist/lib/repo-kind.js +129 -0
  166. package/dist/lib/scripts.js +7 -0
  167. package/dist/lib/snapshot/catalogue-diff.js +219 -0
  168. package/dist/lib/snapshot/manifest.js +206 -0
  169. package/dist/lib/sync/adr-transitions.js +250 -0
  170. package/dist/lib/sync/apply.js +91 -0
  171. package/dist/lib/sync/briefing-merge.js +90 -0
  172. package/dist/lib/sync/bundle-cache.js +109 -0
  173. package/dist/lib/sync/bundle-source.js +61 -0
  174. package/dist/lib/sync/config.js +72 -0
  175. package/dist/lib/sync/copilot-hooks-merge.js +108 -0
  176. package/dist/lib/sync/diff.js +251 -0
  177. package/dist/lib/sync/index.js +818 -0
  178. package/dist/lib/sync/settings-merge.js +216 -0
  179. package/dist/lib/sync/yaml-block-merge.js +120 -0
  180. package/dist/lib/validate/aggregate.js +51 -0
  181. package/dist/lib/validate/bundle-coherence.js +74 -0
  182. package/dist/lib/validate/dispatch.js +243 -0
  183. package/dist/lib/validate/index.js +110 -0
  184. package/dist/lib/validate/plan.js +54 -0
  185. package/dist/lib/validate/runner.js +85 -0
  186. package/dist/lib/validate/yaml-parse.js +112 -0
  187. package/dist/maintain-bin.js +8 -0
  188. package/dist/maintain-program.js +91 -0
  189. package/dist/maintain-version.json +4 -0
  190. package/dist/verbs/catalogue-maintain.js +110 -0
  191. package/dist/verbs/catalogue-verify.js +114 -0
  192. package/dist/verbs/context.js +39 -0
  193. package/dist/verbs/generate-briefing.js +29 -0
  194. package/dist/verbs/list.js +45 -0
  195. package/dist/verbs/pack-build.js +31 -0
  196. package/dist/verbs/pack-validate.js +17 -0
  197. package/dist/verbs/path-option.js +25 -0
  198. package/dist/verbs/render.js +87 -0
  199. package/dist/verbs/sync.js +110 -0
  200. package/dist/verbs/validate.js +48 -0
  201. package/dist/verify-program.js +122 -0
  202. package/dist/verify-version.json +4 -0
  203. package/package.json +32 -4
  204. package/scripts/validate-app-dependencies.sh +664 -0
  205. package/scripts/validate-app-docs.sh +199 -0
  206. package/scripts/validate-integration-docs.sh +505 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Merge logic for the method-managed portion of `.claude/settings.json`.
3
+ *
4
+ * Ownership model: the method owns specific server ids (listed in
5
+ * `METHOD_MANAGED_SERVER_IDS`). On upsert, those ids are rewritten
6
+ * with the method-supplied entry; any other server ids the adopter
7
+ * has added under `mcpServers` survive untouched, as do all other
8
+ * top-level keys (hooks, permissions, env, …).
9
+ *
10
+ * The merge is conservative: if a method id exists with a value that
11
+ * is byte-identical to the method's current seed, we return the
12
+ * original object unmodified (stable output → no spurious writes).
13
+ */
14
+ export function upsertMethodManagedServers(existing, owned) {
15
+ const added = [];
16
+ const updated = [];
17
+ const unchanged = [];
18
+ const currentServers = isPlainObject(existing["mcpServers"])
19
+ ? existing["mcpServers"]
20
+ : {};
21
+ const nextServers = { ...currentServers };
22
+ for (const [id, entry] of Object.entries(owned)) {
23
+ const prior = currentServers[id];
24
+ if (prior === undefined) {
25
+ nextServers[id] = entry;
26
+ added.push(id);
27
+ }
28
+ else if (!entriesEqual(prior, entry)) {
29
+ nextServers[id] = entry;
30
+ updated.push(id);
31
+ }
32
+ else {
33
+ unchanged.push(id);
34
+ }
35
+ }
36
+ const changed = added.length > 0 || updated.length > 0;
37
+ if (!changed) {
38
+ return {
39
+ settings: existing,
40
+ added,
41
+ updated,
42
+ unchanged,
43
+ changed: false,
44
+ };
45
+ }
46
+ const nextSettings = {
47
+ ...existing,
48
+ mcpServers: nextServers,
49
+ };
50
+ return {
51
+ settings: nextSettings,
52
+ added,
53
+ updated,
54
+ unchanged,
55
+ changed: true,
56
+ };
57
+ }
58
+ /**
59
+ * Detect adopter-edited method-managed entries. Returns the ids whose
60
+ * current value differs from the method-supplied seed — these are the
61
+ * ones that a refuse-or-overwrite sync should flag unless
62
+ * `--allow-overwrite` is passed.
63
+ */
64
+ export function detectManagedDrift(existing, owned) {
65
+ const currentServers = isPlainObject(existing["mcpServers"])
66
+ ? existing["mcpServers"]
67
+ : {};
68
+ const drifted = [];
69
+ for (const [id, entry] of Object.entries(owned)) {
70
+ const prior = currentServers[id];
71
+ if (prior !== undefined && !entriesEqual(prior, entry)) {
72
+ drifted.push(id);
73
+ }
74
+ }
75
+ return drifted;
76
+ }
77
+ function entriesEqual(a, b) {
78
+ // Stable-order JSON comparison. The entry shape is small and flat
79
+ // enough that stringifying is cheaper than writing a structural
80
+ // walker.
81
+ return stableStringify(a) === stableStringify(b);
82
+ }
83
+ function stableStringify(value) {
84
+ if (value === null || typeof value !== "object")
85
+ return JSON.stringify(value);
86
+ if (Array.isArray(value)) {
87
+ return "[" + value.map((v) => stableStringify(v)).join(",") + "]";
88
+ }
89
+ const keys = Object.keys(value).sort();
90
+ return ("{" +
91
+ keys
92
+ .map((k) => JSON.stringify(k) + ":" + stableStringify(value[k]))
93
+ .join(",") +
94
+ "}");
95
+ }
96
+ function isPlainObject(v) {
97
+ return v !== null && typeof v === "object" && !Array.isArray(v);
98
+ }
99
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Types for the MCP settings-seed library. Mirrors the shape Claude
3
+ * Code expects at `.claude/settings.json`, restricted to the fields
4
+ * we read or write.
5
+ */
6
+ /**
7
+ * Server ids owned by this method plan. Sync upserts these and leaves
8
+ * everything else alone. When Phases 3+4 ship adopter servers, extend
9
+ * the list — or better, let each plan contribute its own list and
10
+ * combine at merge time.
11
+ */
12
+ export const METHOD_MANAGED_SERVER_IDS = ["sdd-mcp"];
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,303 @@
1
+ import { execFile as execFileCb } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join, relative } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { checkLeakage } from "./leakage.js";
8
+ import { applyPublishTransform } from "./publish-transform.js";
9
+ import { checkReferenceClosure, } from "./reference-closure.js";
10
+ const execFile = promisify(execFileCb);
11
+ export async function buildPack(opts) {
12
+ const packName = opts.packName ?? derivePackName(opts.seed.sourcePath);
13
+ const outDir = opts.outDir ?? join(opts.producerRepo, "dist", packName);
14
+ // 1. Resolve globs against producer repo
15
+ const resolvedPaths = await resolveManagedPaths(opts.seed.managedPaths, opts.producerRepo);
16
+ // 2. Producer-side leakage check
17
+ const leakage = await checkLeakage({
18
+ seed: { ...opts.seed, managedPaths: resolvedPaths },
19
+ ...(opts.forwardBaseManagedPathsFile && {
20
+ forwardBaseManagedPathsFile: opts.forwardBaseManagedPathsFile,
21
+ }),
22
+ producerRepo: opts.producerRepo,
23
+ });
24
+ if (!leakage.ok) {
25
+ const summary = leakage.violations
26
+ .map((v) => ` - ${v.path} (conflicts with ${v.conflictWith}: ${v.conflictSource})`)
27
+ .join("\n");
28
+ throw new Error(`Producer-side leakage check failed for ${packName}@${opts.seed.version}:\n${summary}`);
29
+ }
30
+ // 3. Stage payload
31
+ const stage = await mkdtemp(join(tmpdir(), "sdd-cli-pack-build-"));
32
+ const rootName = `${packName}-v${opts.seed.version}`;
33
+ const root = join(stage, rootName);
34
+ const payload = join(root, "payload");
35
+ await mkdir(payload, { recursive: true });
36
+ try {
37
+ for (const p of resolvedPaths) {
38
+ const src = join(opts.producerRepo, p);
39
+ const dest = join(payload, p);
40
+ await mkdir(dirname(dest), { recursive: true });
41
+ await copyFile(src, dest);
42
+ }
43
+ // 3b. Publish transform (ADR 0183/0184) — method-baseline builds only.
44
+ // Produces the point-in-time / producer-agnostic published payload and
45
+ // fails closed on any published-surface leak, before anything is hashed
46
+ // or tarred. Non-Accepted ADRs are dropped from the payload.
47
+ let publishedPaths = resolvedPaths;
48
+ if (opts.publishTransform) {
49
+ const transform = await applyPublishTransform(payload, resolvedPaths, {
50
+ mAdrPrefix: opts.mAdrPrefix ?? false,
51
+ });
52
+ if (transform.violations.length > 0) {
53
+ const summary = transform.violations
54
+ .slice(0, 20)
55
+ .map((v) => ` ${v.path}:${v.line} [${v.cls}] /${v.pattern}/ → ${v.text}`)
56
+ .join("\n");
57
+ const more = transform.violations.length > 20
58
+ ? `\n …and ${transform.violations.length - 20} more`
59
+ : "";
60
+ throw new Error(`Published-surface lint failed for ${packName}@${opts.seed.version} ` +
61
+ `(${transform.violations.length} leak(s)):\n${summary}${more}`);
62
+ }
63
+ publishedPaths = transform.keptPaths;
64
+ }
65
+ // 3c. Reference closure (M-ADR 0151 §4.6) — reporting-only, by design:
66
+ // a subset pack cannot satisfy closure without ceasing to be a subset,
67
+ // so it declares its unresolvable-reference count instead of gating.
68
+ const referenceClosure = await checkReferenceClosure(payload, publishedPaths);
69
+ // 4. Checksums (over the published payload)
70
+ const checksums = await computeChecksums(payload, publishedPaths);
71
+ // 5. Manifest + managed_paths + checksums files
72
+ const gitInfo = await readGitInfo(opts.producerRepo);
73
+ await writeFile(join(root, "MANAGED_PATHS.txt"), publishedPaths.join("\n") + "\n");
74
+ await writeFile(join(root, "CHECKSUMS.sha256"), checksums);
75
+ await writeFile(join(root, "BUNDLE_MANIFEST.yaml"), renderManifest({
76
+ packName,
77
+ seed: opts.seed,
78
+ producerRepo: opts.producerRepo,
79
+ managedPathCount: publishedPaths.length,
80
+ sourceCommit: gitInfo.commit,
81
+ sourceBranch: gitInfo.branch,
82
+ }));
83
+ // 6. tar -czf
84
+ await mkdir(outDir, { recursive: true });
85
+ const tarballName = `${rootName}.tar.gz`;
86
+ const tarballPath = join(outDir, tarballName);
87
+ await execFile("tar", ["-czf", tarballPath, "-C", stage, rootName]);
88
+ // Also emit a sidecar checksum file for the tarball itself
89
+ const tarballHash = await sha256File(tarballPath);
90
+ const sidecar = `${tarballHash} ${tarballName}\n`;
91
+ const checksumPath = join(outDir, `${rootName}.sha256`);
92
+ await writeFile(checksumPath, sidecar);
93
+ return {
94
+ tarballPath,
95
+ checksumPath,
96
+ packName,
97
+ bundleVersion: `v${opts.seed.version}`,
98
+ managedPathCount: publishedPaths.length,
99
+ leakage,
100
+ referenceClosure,
101
+ };
102
+ }
103
+ finally {
104
+ await rm(stage, { recursive: true, force: true });
105
+ }
106
+ }
107
+ function derivePackName(seedPath) {
108
+ // .../{pack-name}-seed.yaml → {pack-name}
109
+ const base = seedPath.split("/").pop() ?? seedPath;
110
+ return base.replace(/-seed\.yaml$/, "").replace(/\.yaml$/, "");
111
+ }
112
+ async function resolveManagedPaths(patterns, producerRepo) {
113
+ const out = [];
114
+ const seen = new Set();
115
+ for (const pattern of patterns) {
116
+ if (pattern.includes("*")) {
117
+ const matches = await expandGlob(pattern, producerRepo);
118
+ for (const m of matches) {
119
+ if (!seen.has(m)) {
120
+ seen.add(m);
121
+ out.push(m);
122
+ }
123
+ }
124
+ }
125
+ else {
126
+ const abs = join(producerRepo, pattern);
127
+ try {
128
+ await stat(abs);
129
+ }
130
+ catch {
131
+ throw new Error(`managed_paths entry does not exist in producer repo: ${pattern}`);
132
+ }
133
+ if (!seen.has(pattern)) {
134
+ seen.add(pattern);
135
+ out.push(pattern);
136
+ }
137
+ }
138
+ }
139
+ out.sort();
140
+ return out;
141
+ }
142
+ async function expandGlob(pattern, producerRepo) {
143
+ // Minimal glob: split on first `*`, walk directory, match.
144
+ // Supports `**` (any depth) and `*` (any non-slash chars).
145
+ const re = globToRegex(pattern);
146
+ const out = [];
147
+ async function walk(dir, rel) {
148
+ let entries;
149
+ try {
150
+ entries = await readdir(dir, { withFileTypes: true });
151
+ }
152
+ catch {
153
+ return;
154
+ }
155
+ for (const e of entries) {
156
+ const childRel = rel === "" ? e.name : `${rel}/${e.name}`;
157
+ const childAbs = join(dir, e.name);
158
+ if (e.isDirectory()) {
159
+ await walk(childAbs, childRel);
160
+ }
161
+ else if (e.isFile() && re.test(childRel)) {
162
+ out.push(childRel);
163
+ }
164
+ }
165
+ }
166
+ await walk(producerRepo, "");
167
+ return out;
168
+ }
169
+ function globToRegex(pattern) {
170
+ let regex = "";
171
+ let i = 0;
172
+ while (i < pattern.length) {
173
+ const ch = pattern[i];
174
+ if (ch === "*") {
175
+ if (pattern[i + 1] === "*") {
176
+ regex += ".*";
177
+ i += 2;
178
+ }
179
+ else {
180
+ regex += "[^/]*";
181
+ i += 1;
182
+ }
183
+ }
184
+ else if (/[.+?^${}()|[\]\\]/.test(ch)) {
185
+ regex += `\\${ch}`;
186
+ i += 1;
187
+ }
188
+ else {
189
+ regex += ch;
190
+ i += 1;
191
+ }
192
+ }
193
+ return new RegExp(`^${regex}$`);
194
+ }
195
+ async function computeChecksums(payloadRoot, paths) {
196
+ const lines = [];
197
+ for (const p of paths) {
198
+ const abs = join(payloadRoot, p);
199
+ const hash = await sha256File(abs);
200
+ lines.push(`${hash} payload/${p}`);
201
+ }
202
+ return lines.join("\n") + "\n";
203
+ }
204
+ async function sha256File(path) {
205
+ const buf = await readFile(path);
206
+ return createHash("sha256").update(buf).digest("hex");
207
+ }
208
+ function renderManifest(ctx) {
209
+ // Direct YAML emission — matches the shape declared in
210
+ // sdd-method/orchestration/templates/bundle/BUNDLE_MANIFEST.yaml.hbs.
211
+ // We don't go through a Handlebars engine because that would be the
212
+ // only template-rendered file in sdd-cli; everything else is direct.
213
+ const generatedAt = new Date().toISOString();
214
+ const sourceRepo = deriveSourceRepoName(ctx.producerRepo);
215
+ const lines = [];
216
+ lines.push(`bundle_name: "${ctx.packName}"`);
217
+ lines.push(`bundle_version: "v${ctx.seed.version}"`);
218
+ lines.push(`scope: "${ctx.seed.scope}"`);
219
+ lines.push(`source_repo: "${sourceRepo}"`);
220
+ lines.push(`source_branch: "${ctx.sourceBranch}"`);
221
+ lines.push(`source_commit: "${ctx.sourceCommit}"`);
222
+ lines.push(`generated_at_utc: "${generatedAt}"`);
223
+ lines.push(``);
224
+ lines.push(`# Bundle topology (per ADR 0151 §4)`);
225
+ lines.push(`is_forward_base: false`);
226
+ lines.push(`produced_by_adopter: "${sourceRepo}"`);
227
+ if (ctx.seed.forwardBase) {
228
+ lines.push(`requires_forward_base:`);
229
+ lines.push(` bundle: "${ctx.seed.forwardBase.bundle}"`);
230
+ lines.push(` minimum_version: "${ctx.seed.forwardBase.minimumVersion}"`);
231
+ lines.push(` source_repo: "${ctx.seed.forwardBase.sourceRepo}"`);
232
+ }
233
+ if (ctx.seed.scaffoldSourcePaths && ctx.seed.scaffoldSourcePaths.length > 0) {
234
+ lines.push(`scaffold_source_paths:`);
235
+ for (const sp of ctx.seed.scaffoldSourcePaths) {
236
+ lines.push(` - "${sp}"`);
237
+ }
238
+ }
239
+ if (ctx.seed.requiresPack) {
240
+ lines.push(`requires_pack:`);
241
+ lines.push(` name: "${ctx.seed.requiresPack.name}"`);
242
+ lines.push(` version: "${ctx.seed.requiresPack.version}"`);
243
+ }
244
+ lines.push(``);
245
+ lines.push(`# Payload references`);
246
+ lines.push(`managed_path_count: ${ctx.managedPathCount}`);
247
+ lines.push(`managed_paths_file: "MANAGED_PATHS.txt"`);
248
+ lines.push(`checksums_file: "CHECKSUMS.sha256"`);
249
+ lines.push(``);
250
+ lines.push(`# Consumer compatibility`);
251
+ lines.push(`minimum_sdd_cli_version: "${ctx.seed.minimumSddCliVersion}"`);
252
+ lines.push(``);
253
+ lines.push(`description: |`);
254
+ for (const dl of ctx.seed.description.split("\n")) {
255
+ lines.push(` ${dl}`);
256
+ }
257
+ return lines.join("\n") + "\n";
258
+ }
259
+ /**
260
+ * Read git provenance from the producer working tree. Mirrors the
261
+ * behaviour of sdd-method/orchestration/scripts/build-method-baseline-
262
+ * bundle.sh which embeds HEAD commit + current branch into BUNDLE_MANIFEST.yaml.
263
+ *
264
+ * Falls back to safe non-empty sentinels when the producer is not a
265
+ * git working tree (e.g. building from a tarball or a CI artifact
266
+ * dir). source_commit MUST be non-empty for the consumer-side
267
+ * parseManifest check to pass.
268
+ */
269
+ async function readGitInfo(producerRepo) {
270
+ let commit = "uncommitted";
271
+ let branch = "";
272
+ try {
273
+ const { stdout } = await execFile("git", ["-C", producerRepo, "rev-parse", "HEAD"]);
274
+ commit = stdout.trim() || commit;
275
+ }
276
+ catch {
277
+ // Not a git working tree; keep sentinel.
278
+ }
279
+ try {
280
+ const { stdout } = await execFile("git", [
281
+ "-C",
282
+ producerRepo,
283
+ "rev-parse",
284
+ "--abbrev-ref",
285
+ "HEAD",
286
+ ]);
287
+ branch = stdout.trim();
288
+ if (branch === "HEAD")
289
+ branch = ""; // detached
290
+ }
291
+ catch {
292
+ // Not a git working tree.
293
+ }
294
+ return { commit, branch };
295
+ }
296
+ function deriveSourceRepoName(producerRepo) {
297
+ // Last path segment of producerRepo (e.g. "/.../example-platform" → "example-platform")
298
+ const segs = producerRepo.split("/").filter((s) => s.length > 0);
299
+ return segs[segs.length - 1] ?? "unknown-adopter";
300
+ }
301
+ // Re-export so callers can use the relative path utility
302
+ export { relative as relativePath };
303
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1,87 @@
1
+ import { buildPack } from "./builder.js";
2
+ import { validateSeed } from "./schema.js";
3
+ import { loadSeed } from "./seed.js";
4
+ import { getSddCliVersion } from "../package-version.js";
5
+ import { fail, info, pass, section } from "./log.js";
6
+ export { runPackValidate, } from "./validate.js";
7
+ export async function runPackBuild(opts, stdout = (s) => process.stdout.write(s)) {
8
+ stdout(section("Pack build"));
9
+ let seed;
10
+ try {
11
+ seed = await loadSeed(opts.seedPath);
12
+ }
13
+ catch (err) {
14
+ stdout(fail(`Seed load failed: ${err.message}`));
15
+ return { exitCode: 1 };
16
+ }
17
+ // A seed key this build does not know is REPORTED rather than dropped in silence.
18
+ // Not fatal: an older sdd-cli must still build a seed that declares something newer
19
+ // than itself, exactly as for `.sdd/sdd-manifest.yaml`. Silence here is what let an
20
+ // adopter follow the schema and get a clean validation with no effect.
21
+ if (seed.unknownKeys && seed.unknownKeys.length > 0) {
22
+ stdout(info(`${seed.unknownKeys.length} seed key(s) not understood by sdd-cli ${getSddCliVersion()} ` +
23
+ `and therefore IGNORED: ${seed.unknownKeys.join(", ")}. If the seed follows a newer ` +
24
+ `context-pack-seed-schema than this build, upgrade sdd-cli; otherwise remove or correct the key.`));
25
+ }
26
+ const validation = validateSeed(seed);
27
+ if (!validation.ok) {
28
+ stdout(fail("Seed validation failed:"));
29
+ for (const e of validation.errors) {
30
+ stdout(fail(` ${e.field}: ${e.message}`));
31
+ }
32
+ return { exitCode: 1 };
33
+ }
34
+ stdout(info(`Seed loaded: ${seed.scope} v${seed.version}`));
35
+ stdout(info(seed.forwardBase
36
+ ? `Forward base: ${seed.forwardBase.bundle} >= ${seed.forwardBase.minimumVersion}`
37
+ : "Forward base: none (profile-agnostic pack, ADR 0176 §4.6)"));
38
+ stdout(info(`Managed paths declared: ${seed.managedPaths.length}`));
39
+ // Origin-prefix implies the publish transform: the ADR rename lives inside it,
40
+ // so the flag would be a silent no-op otherwise.
41
+ const publishTransform = opts.publishTransform === true || opts.originPrefix === true;
42
+ if (publishTransform) {
43
+ stdout(info(`Publish transform: ON${opts.originPrefix ? " + origin-prefix (M-ADR/RA-ADR by band)" : ""}`));
44
+ }
45
+ let result;
46
+ try {
47
+ result = await buildPack({
48
+ seed,
49
+ producerRepo: opts.producerRepo,
50
+ ...(opts.outDir && { outDir: opts.outDir }),
51
+ ...(opts.forwardBaseManagedPathsFile && {
52
+ forwardBaseManagedPathsFile: opts.forwardBaseManagedPathsFile,
53
+ }),
54
+ ...(publishTransform && { publishTransform: true }),
55
+ ...(opts.originPrefix === true && { mAdrPrefix: true }),
56
+ });
57
+ }
58
+ catch (err) {
59
+ stdout(fail(err.message));
60
+ return { exitCode: 1 };
61
+ }
62
+ stdout(pass(`Built ${result.packName}@${result.bundleVersion}`));
63
+ stdout(info(`Tarball: ${result.tarballPath}`));
64
+ stdout(info(`Checksum: ${result.checksumPath}`));
65
+ stdout(info(`Managed paths in bundle: ${result.managedPathCount}`));
66
+ // Reference closure (M-ADR 0151 §4.6): stated at every cut so the pack
67
+ // DECLARES what resolves only in the producer, instead of a consumer
68
+ // discovering it by resolving the corpus after distribution.
69
+ const rc = result.referenceClosure;
70
+ if (rc.unresolvable === 0) {
71
+ stdout(pass(`Reference closure: all ${rc.totalReferences} payload reference(s) resolve within the pack`));
72
+ }
73
+ else {
74
+ stdout(info(`Reference closure (M-ADR 0151 §4.6): ${rc.unresolvable} of ${rc.totalReferences} ` +
75
+ `payload reference(s) resolve only in the producer, across ${rc.unresolvableTargets.length} ` +
76
+ `distinct target(s) — informational in member repos by policy, not a defect. ` +
77
+ `Expand the seeds to shrink this figure.`));
78
+ for (const t of rc.unresolvableTargets.slice(0, 10)) {
79
+ stdout(info(` → ${t}`));
80
+ }
81
+ if (rc.unresolvableTargets.length > 10) {
82
+ stdout(info(` … and ${rc.unresolvableTargets.length - 10} more`));
83
+ }
84
+ }
85
+ return { exitCode: 0, build: result };
86
+ }
87
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,144 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export async function checkLeakage(opts) {
4
+ const violations = [];
5
+ // 1. Disjointness with the forward base. Profile-agnostic packs
6
+ // (no forward_base, ADR 0176 §4.6) compare against an explicitly
7
+ // supplied MANAGED_PATHS.txt when given; with neither declared nor
8
+ // supplied, the forward-base half of the check is skipped (the
9
+ // forbidden-paths half below still runs).
10
+ if (opts.seed.forwardBase || opts.forwardBaseManagedPathsFile) {
11
+ const forwardBasePaths = await loadForwardBaseManagedPaths(opts);
12
+ const forwardBaseSet = new Set(forwardBasePaths);
13
+ const sourceLabel = opts.seed.forwardBase?.bundle ??
14
+ opts.forwardBaseManagedPathsFile ??
15
+ "forward-base";
16
+ for (const p of opts.seed.managedPaths) {
17
+ if (forwardBaseSet.has(p)) {
18
+ violations.push({
19
+ path: p,
20
+ conflictWith: "forward-base",
21
+ conflictSource: sourceLabel,
22
+ });
23
+ }
24
+ }
25
+ }
26
+ // 2. Disjointness with declared forbidden_paths globs (seed self-contract).
27
+ if (opts.seed.forbiddenPaths) {
28
+ for (const p of opts.seed.managedPaths) {
29
+ for (const pattern of opts.seed.forbiddenPaths) {
30
+ if (matchesGlob(p, pattern)) {
31
+ violations.push({
32
+ path: p,
33
+ conflictWith: "forbidden-pattern",
34
+ conflictSource: pattern,
35
+ });
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return { ok: violations.length === 0, violations };
41
+ }
42
+ async function loadForwardBaseManagedPaths(opts) {
43
+ if (opts.forwardBaseManagedPathsFile) {
44
+ const raw = await readFile(opts.forwardBaseManagedPathsFile, "utf8");
45
+ return raw
46
+ .split("\n")
47
+ .map((l) => l.trim())
48
+ .filter((l) => l.length > 0 && !l.startsWith("#"));
49
+ }
50
+ if (opts.producerRepo) {
51
+ const lockPath = join(opts.producerRepo, "method-baseline.lock");
52
+ try {
53
+ const raw = await readFile(lockPath, "utf8");
54
+ // Reachable only when seed.forwardBase is set (callers guard);
55
+ // the non-null assertion keeps the lock-derived path working.
56
+ return extractManagedPathsForBundle(raw, opts.seed.forwardBase.bundle);
57
+ }
58
+ catch (err) {
59
+ throw new Error(`Cannot read forward-base managed_paths: ${lockPath} unreadable ` +
60
+ `(${err.message}). Pass --forward-base-managed-paths ` +
61
+ `to provide MANAGED_PATHS.txt directly.`);
62
+ }
63
+ }
64
+ throw new Error("Cannot resolve forward-base managed_paths: provide either " +
65
+ "--producer-repo (with method-baseline.lock present) or " +
66
+ "--forward-base-managed-paths (path to MANAGED_PATHS.txt).");
67
+ }
68
+ /**
69
+ * Extract managed_paths for the named bundle from a method-baseline.lock
70
+ * file. Minimal parser — the lock file is line-oriented YAML and we
71
+ * only need a single nested list per named bundle.
72
+ */
73
+ export function extractManagedPathsForBundle(lockYaml, bundleName) {
74
+ const lines = lockYaml.split("\n");
75
+ let inBundles = false;
76
+ let inTargetBundle = false;
77
+ let inManagedPaths = false;
78
+ const paths = [];
79
+ for (const line of lines) {
80
+ if (line.startsWith("bundles:")) {
81
+ inBundles = true;
82
+ continue;
83
+ }
84
+ if (!inBundles)
85
+ continue;
86
+ if (/^\s*-\s*name:\s*(.+)$/.test(line)) {
87
+ const m = /^\s*-\s*name:\s*(.+)$/.exec(line);
88
+ inTargetBundle = m !== null && m[1]?.trim() === bundleName;
89
+ inManagedPaths = false;
90
+ continue;
91
+ }
92
+ if (inTargetBundle && /^\s+managed_paths:\s*$/.test(line)) {
93
+ inManagedPaths = true;
94
+ continue;
95
+ }
96
+ if (inManagedPaths) {
97
+ const m = /^\s+-\s+(.+)$/.exec(line);
98
+ if (m && m[1]) {
99
+ paths.push(m[1].trim());
100
+ }
101
+ else if (/^\s+\w+:/.test(line) || /^\s*-\s*name:/.test(line)) {
102
+ // End of managed_paths list (either next field or next bundle)
103
+ inManagedPaths = false;
104
+ }
105
+ }
106
+ }
107
+ return paths;
108
+ }
109
+ /**
110
+ * Simple glob matcher supporting `*` (any non-slash chars) and `**`
111
+ * (any chars including slashes). Used for forbidden_paths.
112
+ */
113
+ export function matchesGlob(path, pattern) {
114
+ const re = globToRegex(pattern);
115
+ return re.test(path);
116
+ }
117
+ function globToRegex(pattern) {
118
+ // Escape regex metachars except *, then translate * and **.
119
+ let regex = "";
120
+ let i = 0;
121
+ while (i < pattern.length) {
122
+ const ch = pattern[i];
123
+ if (ch === "*") {
124
+ if (pattern[i + 1] === "*") {
125
+ regex += ".*";
126
+ i += 2;
127
+ }
128
+ else {
129
+ regex += "[^/]*";
130
+ i += 1;
131
+ }
132
+ }
133
+ else if (/[.+?^${}()|[\]\\]/.test(ch)) {
134
+ regex += `\\${ch}`;
135
+ i += 1;
136
+ }
137
+ else {
138
+ regex += ch;
139
+ i += 1;
140
+ }
141
+ }
142
+ return new RegExp(`^${regex}$`);
143
+ }
144
+ //# sourceMappingURL=leakage.js.map
@@ -0,0 +1,18 @@
1
+ // Output helpers shared by `pack build` and `pack validate`. Split out so
2
+ // `pack validate` can be imported without pulling in the builder — the
3
+ // verify-only CLI ships validation and must not ship the code that writes
4
+ // a pack (M-ADR 0212 §4.3b).
5
+ export const SECTION_RULE = "═".repeat(59);
6
+ export function section(title) {
7
+ return `\n${SECTION_RULE}\n ${title}\n${SECTION_RULE}\n\n`;
8
+ }
9
+ export function info(msg) {
10
+ return `[INFO] ${msg}\n`;
11
+ }
12
+ export function pass(msg) {
13
+ return `[PASS] ${msg}\n`;
14
+ }
15
+ export function fail(msg) {
16
+ return `[FAIL] ${msg}\n`;
17
+ }
18
+ //# sourceMappingURL=log.js.map