@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,191 @@
1
+ /**
2
+ * `sdd-cli context pull` — write the code-repo briefing into a code repository from its upstream
3
+ * SDD (M-ADR 0220 §4.5, re-homing M-ADR 0150 §4.4 Phase D).
4
+ *
5
+ * The successor to the code-repo half of `generate-claude-md.sh`. No dynamic content is baked
6
+ * in: the briefing points at the SDD's files. A sibling verb to `generate-briefing`, not an extension of it:
7
+ * `generate-briefing` needs the target to be an SDD (marker, lock, synced
8
+ * templates), and a code repository is none of those. They share the
9
+ * placeholder substitution and the wrapper's marker-block merge.
10
+ *
11
+ * What it writes, and nothing else (M-ADR 0218 §4.3 item 9, a MAINTAIN verb):
12
+ * agent-instructions.md whole file, method-managed
13
+ * CLAUDE.md, AGENTS.md the method-managed block only; adopter content survives (AGENTS.md is
14
+ * the pointer, M-ADR 0220 §4.6, when the SDD's baseline carries it)
15
+ * Templates are read from the SDD, which carries them via its baseline bundle. MCP_SETUP.md is no
16
+ * longer written (M-ADR 0220 §4.5); a copy this verb wrote earlier is removed when it still
17
+ * matches the method's last render, and left with a notice when it does not.
18
+ */
19
+ import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
20
+ import { basename, dirname, join, resolve } from "node:path";
21
+ import { AGENT_INSTRUCTIONS_REL_PATH, AGENTS_POINTER, WRAPPER_BLOCK_BEGIN, WRAPPER_BLOCK_END, findUnresolvedPlaceholders, harnessWrapperOutputPath, resolveBriefingVariables, splitWrapperTemplate, } from "../briefing/index.js";
22
+ import { readLockFile } from "../lock-file.js";
23
+ import { readRepoKind } from "../repo-kind.js";
24
+ import { writeMarkedBlockFile } from "../sync/yaml-block-merge.js";
25
+ import { defaultSearchRoots, resolveCodeRepoName, resolveSdd } from "./resolve.js";
26
+ import { RETIRED_MCP_SETUP_TEMPLATE } from "./retired-mcp-setup.js";
27
+ export const CODE_REPO_BRIEFING_ROOT = "orchestration/templates/briefing/code-repo";
28
+ export const MCP_SETUP_REL_PATH = "MCP_SETUP.md";
29
+ const WRAPPER_TEMPLATE = {
30
+ "claude-code": "claude-code/CLAUDE.md.hbs",
31
+ };
32
+ export async function runContextPull(opts, out = (line) => process.stdout.write(line)) {
33
+ const codeRepo = resolve(opts.codeRepo);
34
+ if ((await readRepoKind(codeRepo)) !== null) {
35
+ out(fail(`${codeRepo} is an SDD repository (it has .sdd-repo-kind). context pull writes into a code repository; for an SDD use sdd-cli generate-briefing.`));
36
+ return { exitCode: 1 };
37
+ }
38
+ const repoName = resolveCodeRepoName(codeRepo, opts.repoName);
39
+ const outcome = await resolveSdd({
40
+ repoName,
41
+ sddPath: opts.sddPath,
42
+ searchRoots: opts.searchRoots ?? defaultSearchRoots(codeRepo),
43
+ codeRepo,
44
+ });
45
+ if (outcome.kind === "not-an-sdd") {
46
+ out(fail(`--sdd ${outcome.path} is not an SDD repository (no .sdd-repo-kind).`));
47
+ return { exitCode: 1 };
48
+ }
49
+ if (outcome.kind === "not-found") {
50
+ out(fail(`No SDD lists repository "${repoName}" in its catalogues/repositories.csv (searched ${outcome.searched.length} SDD(s) under ${(opts.searchRoots ?? defaultSearchRoots(codeRepo)).join(", ")}). Pass --sdd <path>, or --repo <id> if this repository is catalogued under another id.`));
51
+ return { exitCode: 1 };
52
+ }
53
+ if (outcome.kind === "ambiguous") {
54
+ out(fail(`Repository "${repoName}" is listed by ${outcome.candidates.length} SDDs: ${outcome.candidates.join(", ")}. Pass --sdd <path> to choose.`));
55
+ return { exitCode: 1 };
56
+ }
57
+ const sddRoot = outcome.sddRoot;
58
+ if (!outcome.catalogued) {
59
+ out(warn(`${basename(sddRoot)}'s catalogues/repositories.csv does not list "${repoName}"; continuing because --sdd was given.`));
60
+ }
61
+ // The SDD's own identity, profile and baseline, exactly as its own briefing renders them.
62
+ const lock = await readLockFile(sddRoot);
63
+ const forwardBase = lock?.bundles.find((b) => b.isForwardBase);
64
+ if (!forwardBase) {
65
+ out(fail(`${sddRoot} has no forward-base bundle in method-baseline.lock; sync it first.`));
66
+ return { exitCode: 1 };
67
+ }
68
+ const sdd = await resolveBriefingVariables({ targetRepo: sddRoot, baselineVersionOverride: forwardBase.bundleVersion });
69
+ const vars = {
70
+ repo_name: repoName,
71
+ sdd_name: sdd.sdd_name,
72
+ sdd_root: sddRoot,
73
+ repo_kind: sdd.repo_kind,
74
+ baseline_version: sdd.baseline_version,
75
+ pull_date: opts.pullDate ?? new Date().toISOString().slice(0, 10),
76
+ };
77
+ const aiTemplate = join(sddRoot, CODE_REPO_BRIEFING_ROOT, "agent-instructions.md.hbs");
78
+ const wrapperTemplate = join(sddRoot, CODE_REPO_BRIEFING_ROOT, WRAPPER_TEMPLATE[opts.agentHarness]);
79
+ for (const t of [aiTemplate, wrapperTemplate]) {
80
+ if (!(await exists(t))) {
81
+ out(fail(`${sddRoot} does not carry ${t.slice(sddRoot.length + 1)}: its method baseline predates context pull. Sync the SDD to a baseline that ships the code-repo templates.`));
82
+ return { exitCode: 1 };
83
+ }
84
+ }
85
+ // The AGENTS.md pointer (M-ADR 0220 §4.6) arrived later than the other code-repo templates: an SDD
86
+ // whose baseline does not carry it yet gets no pointer, and everything else proceeds.
87
+ const pointerTemplate = join(sddRoot, CODE_REPO_BRIEFING_ROOT, AGENTS_POINTER.templateRel);
88
+ const wrapperSources = [[harnessWrapperOutputPath(opts.agentHarness), wrapperTemplate]];
89
+ if (await exists(pointerTemplate))
90
+ wrapperSources.push([AGENTS_POINTER.outputRel, pointerTemplate]);
91
+ const rendered = {};
92
+ for (const [rel, path] of [[AGENT_INSTRUCTIONS_REL_PATH, aiTemplate], ...wrapperSources]) {
93
+ const content = substituteAll(await readFile(path, "utf8"), vars);
94
+ const unresolved = findUnresolvedPlaceholders(content);
95
+ if (unresolved.length > 0) {
96
+ out(fail(`${rel}: the SDD's template has placeholders this CLI does not know: ${unresolved.join(", ")}. Upgrade sdd-cli.`));
97
+ return { exitCode: 1 };
98
+ }
99
+ rendered[rel] = content;
100
+ }
101
+ // The wrappers first: they are the only writes that can be refused, and a refusal must leave
102
+ // nothing written, so every wrapper is checked before any is written.
103
+ const existingOf = {};
104
+ for (const [rel] of wrapperSources) {
105
+ const path = join(codeRepo, rel);
106
+ const existing = (await exists(path)) ? await readFile(path, "utf8") : null;
107
+ existingOf[rel] = existing;
108
+ const hasMarkers = existing !== null && existing.includes(WRAPPER_BLOCK_BEGIN) && existing.includes(WRAPPER_BLOCK_END);
109
+ if (existing !== null && !hasMarkers && !opts.allowOverwrite) {
110
+ // No markers: a pre-briefing-layer file (the bash generator wrote these whole) or adopter-authored.
111
+ out(fail(`${rel} exists without method-managed markers, so it is treated as adopter-authored. Nothing was written. Re-run with --allow-overwrite to replace it with the rendered wrapper (move anything worth keeping below its markers afterwards).`));
112
+ return { exitCode: 1 };
113
+ }
114
+ }
115
+ const changed = [];
116
+ for (const [rel] of wrapperSources) {
117
+ const path = join(codeRepo, rel);
118
+ const existing = existingOf[rel] ?? null;
119
+ const hasMarkers = existing !== null && existing.includes(WRAPPER_BLOCK_BEGIN) && existing.includes(WRAPPER_BLOCK_END);
120
+ if (existing !== null && !hasMarkers) {
121
+ await writeFile(path, rendered[rel], "utf8");
122
+ if (existing !== rendered[rel])
123
+ changed.push(rel);
124
+ continue;
125
+ }
126
+ const parts = splitWrapperTemplate(rendered[rel]);
127
+ const merge = await writeMarkedBlockFile(path, existing === null ? rendered[rel] : parts.block, { begin: WRAPPER_BLOCK_BEGIN, end: WRAPPER_BLOCK_END }, opts.allowOverwrite, existing === null ? undefined : { pre: parts.pre, post: parts.post });
128
+ if (merge.changed)
129
+ changed.push(rel);
130
+ }
131
+ for (const rel of [AGENT_INSTRUCTIONS_REL_PATH]) {
132
+ const path = join(codeRepo, rel);
133
+ const before = (await exists(path)) ? await readFile(path, "utf8") : null;
134
+ if (before !== rendered[rel]) {
135
+ await mkdir(dirname(path), { recursive: true });
136
+ await writeFile(path, rendered[rel], "utf8");
137
+ changed.push(rel);
138
+ }
139
+ }
140
+ const mcpSetup = await retireMcpSetup(codeRepo);
141
+ out(info(`Code repo "${repoName}" is specified by ${sdd.sdd_name} (${sdd.repo_kind}, ${sdd.baseline_version}) at ${sddRoot}.`));
142
+ out(info(changed.length > 0 ? `Wrote ${changed.join(", ")}.` : `Already current: ${[AGENT_INSTRUCTIONS_REL_PATH, ...wrapperSources.map(([rel]) => rel)].join(", ")}.`));
143
+ if (mcpSetup === "removed")
144
+ out(info(`Removed ${MCP_SETUP_REL_PATH}: the method no longer ships it (M-ADR 0220), and this copy was unedited.`));
145
+ if (mcpSetup === "kept")
146
+ out(info(`${MCP_SETUP_REL_PATH} is no longer written (M-ADR 0220). This copy differs from the method's last render, so it was left; delete it if nothing in it is yours.`));
147
+ return { exitCode: 0 };
148
+ }
149
+ /**
150
+ * Remove an MCP_SETUP.md this verb wrote before M-ADR 0220, when it is byte-identical to the
151
+ * method's last template rendered with the values its own header records. Anything else is the
152
+ * adopter's now, and stays.
153
+ */
154
+ export async function retireMcpSetup(codeRepo) {
155
+ const path = join(codeRepo, MCP_SETUP_REL_PATH);
156
+ if (!(await exists(path)))
157
+ return "absent";
158
+ const text = await readFile(path, "utf8");
159
+ const repo = /^\* \*\*Emitted by:\*\* `sdd-cli context pull`, for code repo `([^`]*)`$/m.exec(text);
160
+ const upstream = /^\* \*\*Upstream SDD:\*\* `([^`]*)` at `([^`]*)`$/m.exec(text);
161
+ const baseline = /^\* \*\*Baseline version:\*\* `([^`]*)`$/m.exec(text);
162
+ if (!repo || !upstream || !baseline)
163
+ return "kept";
164
+ const render = substituteAll(RETIRED_MCP_SETUP_TEMPLATE, {
165
+ repo_name: repo[1],
166
+ upstream_sdd_name: upstream[1],
167
+ sdd_root: upstream[2],
168
+ sdd_method_baseline_version: baseline[1],
169
+ });
170
+ if (render !== text)
171
+ return "kept";
172
+ await rm(path);
173
+ return "removed";
174
+ }
175
+ /** `{{name}}` substitution over an open variable set (the briefing renderer's is closed to four). */
176
+ export function substituteAll(template, vars) {
177
+ return template.replace(/\{\{\s*([a-z_][a-z0-9_]*)\s*\}\}/g, (full, key) => (key in vars ? vars[key] : full));
178
+ }
179
+ async function exists(path) {
180
+ try {
181
+ await access(path);
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ const info = (m) => `[INFO] ${m}\n`;
189
+ const warn = (m) => `[WARN] ${m}\n`;
190
+ const fail = (m) => `[FAIL] ${m}\n`;
191
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Code-repo SDD-source resolution for `sdd-cli context pull` (M-ADR 0128 §1,
3
+ * M-ADR 0150 §4.4 Phase D).
4
+ *
5
+ * A code repository has no `.sdd-repo-kind`, no lock and no bundle, so nothing
6
+ * in it names the SDD that specifies it. The SDD names the code repository
7
+ * instead: every repository it owns is a row in its own
8
+ * `catalogues/repositories.csv`, keyed by id. Resolution is therefore a lookup
9
+ * in that direction: find the SDD whose catalogue lists this repository.
10
+ *
11
+ * Only the SDD's OWN catalogue counts, never `catalogues-aggregated/`: a
12
+ * platform root's aggregate lists every member's repositories too, so reading
13
+ * it would make every code repo resolve to two SDDs.
14
+ */
15
+ import { access } from "node:fs/promises";
16
+ import { spawnSync } from "node:child_process";
17
+ import { basename, dirname, join, resolve } from "node:path";
18
+ import { readCatalogueCsv } from "../catalogue/csv-reader.js";
19
+ import { discoverSddRepos } from "../list/discover.js";
20
+ import { readRepoKind } from "../repo-kind.js";
21
+ export const REPOSITORIES_CATALOGUE = "catalogues/repositories.csv";
22
+ /** The code repository's id: `--repo` when given, else its git top-level's directory name. */
23
+ export function resolveCodeRepoName(codeRepo, override) {
24
+ if (override !== undefined && override.trim() !== "")
25
+ return override.trim();
26
+ const top = spawnSync("git", ["-C", codeRepo, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
27
+ if (top.status === 0 && top.stdout.trim() !== "")
28
+ return basename(top.stdout.trim());
29
+ return basename(resolve(codeRepo));
30
+ }
31
+ /** Does this SDD's own repositories catalogue list `repoName`? */
32
+ export async function catalogueListsRepo(sddRoot, repoName) {
33
+ const path = join(sddRoot, REPOSITORIES_CATALOGUE);
34
+ try {
35
+ await access(path);
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ const { rows } = await readCatalogueCsv(path);
41
+ return rows.some((r) => r["id"] === repoName);
42
+ }
43
+ export async function resolveSdd(opts) {
44
+ if (opts.sddPath !== undefined) {
45
+ const root = resolve(opts.sddPath);
46
+ if ((await readRepoKind(root)) === null)
47
+ return { kind: "not-an-sdd", path: root };
48
+ // An explicit --sdd is the caller's assertion, so an uncatalogued repository is reported
49
+ // (the caller may be wiring a repository the SDD has not catalogued yet), not refused.
50
+ return { kind: "resolved", sddRoot: root, catalogued: await catalogueListsRepo(root, opts.repoName) };
51
+ }
52
+ const self = resolve(opts.codeRepo);
53
+ const found = await discoverSddRepos(opts.searchRoots);
54
+ const searched = found.map((r) => r.path).filter((p) => resolve(p) !== self);
55
+ const matches = [];
56
+ for (const p of searched) {
57
+ if (await catalogueListsRepo(p, opts.repoName))
58
+ matches.push(p);
59
+ }
60
+ if (matches.length === 1)
61
+ return { kind: "resolved", sddRoot: matches[0], catalogued: true };
62
+ if (matches.length === 0)
63
+ return { kind: "not-found", searched };
64
+ return { kind: "ambiguous", candidates: matches };
65
+ }
66
+ /** Default discovery root: the directory holding the code repository. */
67
+ export function defaultSearchRoots(codeRepo) {
68
+ return [dirname(resolve(codeRepo))];
69
+ }
70
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The last MCP_SETUP.md template the method shipped (sdd-method 52bbc36), kept so `context pull`
3
+ * can tell a copy it wrote from one an adopter edited after M-ADR 0220 §4.5 retired the file.
4
+ * The SDD's own baseline stops carrying the template, so the comparison cannot read it from there.
5
+ */
6
+ export const RETIRED_MCP_SETUP_TEMPLATE = "# MCP_SETUP.md\n\n* **Source:** sdd-method-baseline\n* **Emitted by:** `sdd-cli context pull`, for code repo `{{repo_name}}`\n* **Upstream SDD:** `{{upstream_sdd_name}}` at `{{sdd_root}}`\n* **Baseline version:** `{{sdd_method_baseline_version}}`\n\nThis file is **method-shipped guidance for agents working in this code repo**. It tells the agent how to reach the upstream SDD's live context via MCP. Read it alongside `agent-instructions.md` at session start.\n\nPer M-ADR 0150 (MCP as the primary dynamic-context layer), the **primary dynamic-context surface** for this code repo is the upstream SDD's MCP server, not baked-in snapshot content. Prefer MCP queries over reading static files for anything that might have changed since the last `sdd-cli context pull`.\n\n## \u00a71 What MCP gives you here\n\nThis code repo (`{{repo_name}}`) consumes specifications from the upstream SDD `{{upstream_sdd_name}}`. The upstream SDD exposes a read-only MCP server (`sdd-mcp`) you can query for:\n\n* **Catalogue rows** \u2014 current capability inventory, service registry, contract list, feature tree, cross-SDD edges.\n* **Gate state** \u2014 lock-in recommendations, intent-quality scores, domain-architecture scores, contract-quality scores per capability.\n* **Plan progress** \u2014 active plans, phases shipped, deferred follow-ups.\n* **Spec lookups** \u2014 ADRs, capability specs, feature specs, traceability manifests.\n\nThe static briefing in this repo (`agent-instructions.md` and the harness wrapper) covers conventions and orientation that are stable across syncs. Anything that changes faster than syncs run \u2014 gate state, catalogue rows, lock-in status \u2014 belongs behind MCP.\n\n## \u00a72 Server identity\n\n* **Server name:** `sdd-mcp`\n* **Upstream SDD path:** `{{sdd_root}}`\n* **Transport:** stdio (per M-ADR 0128's three-MCP-server commitment)\n\n`{{sdd_root}}` is where the SDD was checked out when this file was written; adjust it if your checkout differs. The MCP server resolves the SDD root with this precedence (per sdd-cli):\n\n1. `SDD_REPO_PATH` environment variable.\n2. `--sdd-repo <path>` flag on `sdd-cli mcp serve`.\n3. A platform SDD serves itself: when the git root's own `.sdd-repo-kind` reads `platform-product`, that repository is the SDD, whatever its siblings carry (so a platform repo needs no `SDD_REPO_PATH`).\n4. Sibling-directory auto-discovery from the code repo.\n\n## \u00a73 Harness wiring\n\n### Claude Code (reference)\n\nAdd this fragment to `.claude/settings.json` in this code repo. If the SDD is checked out in a sibling directory, discovery finds it and the `--sdd-repo` argument can be dropped:\n\n```jsonc\n{\n \"mcpServers\": {\n \"sdd-mcp\": {\n \"command\": \"sdd-cli\",\n \"args\": [\"mcp\", \"serve\", \"--sdd-repo\", \"{{sdd_root}}\"]\n }\n }\n}\n```\n\nVerify the wiring: open this code repo in Claude Code; in a session, ask the agent \"list available MCP resources\". You should see `sdd://` URIs from `sdd-mcp`.\n\n### GitHub Copilot CLI\n\nCopilot CLI's MCP wiring uses `.github/copilot/mcp.json` (verify against the current Copilot CLI docs at the time of setup; this surface is evolving). Equivalent shape:\n\n```jsonc\n{\n \"servers\": {\n \"sdd-mcp\": {\n \"command\": \"sdd-cli\",\n \"args\": [\"mcp\", \"serve\", \"--sdd-repo\", \"{{sdd_root}}\"]\n }\n }\n}\n```\n\n### Other harnesses\n\nFor any other harness (OpenAI Codex CLI, OpenCode, a generic-stdio harness, or custom in-house): follow the adopter-responsibility flow per M-ADR 0141 \u00a74.5. Read the portable contract of each MCP resource (in the upstream SDD's `docs/method/methods/`) and instantiate against your harness's native MCP-client interface. Document the wiring in an adopter-side ADR or guide.\n\n## \u00a74 Resource surface \u2014 quick reference\n\nThe upstream `sdd-mcp` server exposes resources keyed by `sdd://` URIs. The most relevant for code-repo agents:\n\n| URI pattern | Returns | Typical use |\n|---|---|---|\n| `sdd://catalogue/{name}` | All rows from one catalogue (e.g. `services`, `capabilities`, `features`, `contracts`) | Discover what exists in the SDD |\n| `sdd://catalogue/metamodel` | The catalogue metamodel doc | Understand the entity / edge model |\n| `sdd://catalogue/schema` | Parsed schema (entities + columns + FK constraints) | Reason about data shape |\n| `sdd://gates/{gate}` | Current gate state per capability | Check whether your dependency has locked in |\n| `sdd://specs/{path}` | Spec content lookup | Read a specific feature / capability spec by ID |\n| `sdd://adrs/{id}` | ADR content + metadata | Cross-reference architectural decisions |\n\nNewly-added resource families ship via baseline sync; consult the upstream SDD's MCP setup docs for the current list.\n\nTools (callable via MCP `tools/call`): `find_producers`, `find_consumers`, `find_consumers_of_contract`, `find_dependencies`, `find_dependents`, `cascade_impact`, `query_edges_for_entity`, `find_orphan_rows`, `query_catalogue_coverage`, `find_products_by_category`, `find_related_specs`, `search_specs`, `list_adrs`, `query_gate_status`. Ask the server for `tools/list` for each tool's arguments.\n\n## \u00a75 Troubleshooting\n\n**`sdd-mcp` unreachable / \"Connection refused\"**\nThe MCP server is launched on-demand by the harness; if it isn't starting, check:\n1. `sdd-cli` is installed and on `$PATH` (`which sdd-cli` should resolve).\n2. The harness's MCP config exists and points at `sdd-cli mcp serve`.\n3. The `--sdd-repo` path (or the sibling the server discovers) is a real SDD root.\n\n**\"SDD root not found\" or \"manifest missing\"**\nThe SDD path doesn't resolve to a valid SDD. The path should contain `.sdd/sdd-manifest.yaml` (or the legacy `docs/method/foundations/` layout).\n\n**Version mismatch on resources**\nSome resource families require a minimum `sdd-cli` version. Check `npm list -g @sdd-method/sdd-cli`; upgrade via `npm install -g @sdd-method/sdd-cli@latest` if a resource returns \"unsupported by this server version\".\n\n**Permission denied**\nFor adopter-scoped resources (per M-ADR 0130), `platform-mcp` / `partner-mcp` enforce scope. If you're hitting permission errors, confirm you're querying the right MCP server for the resource (operator scope vs partner scope).\n\n## \u00a76 Fallback semantics \u2014 no silent stale-data\n\nIf `sdd-mcp` is unreachable for any reason, **the agent must report the error and continue with whatever static context exists in this code repo** \u2014 `CLAUDE.md` snapshot + the orientation in this file. **There is no silent fallback to stale baked-in dynamic state.** Per M-ADR 0128 \u00a71, falling back to stale data re-introduces exactly the problem MCP exists to fix.\n\nIf MCP-served dynamic context is unavailable, surface this back to the user explicitly. The user can choose to:\n\n* Proceed without dynamic context (accept the limitation).\n* Restart the MCP server / fix the connection.\n* Re-run `sdd-cli context pull` for a fresh static briefing, acknowledging it is still a point-in-time read.\n\n## Cross-references\n\n* Upstream SDD: `{{sdd_root}}`\n* M-ADR 0128 (MCP live-query infrastructure)\n* M-ADR 0150 (MCP as primary dynamic-context layer)\n* M-ADR 0130 (adopter MCP composition pattern \u2014 `platform-mcp` / `partner-mcp`)\n* M-ADR 0148 (the static-briefing layer this file complements)\n\n---\n\n<!-- This file is rewritten whole by sdd-cli context pull. Put repo-specific MCP guidance in the harness wrapper, below its method-managed markers. -->\n";
7
+ //# sourceMappingURL=retired-mcp-setup.js.map
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Contract availability manifest (M-ADR 0202).
3
+ *
4
+ * `.sdd/contract-availability.yaml` — the committed attestation that lets an
5
+ * instrument distinguish "absent because wrong" from "absent because not
6
+ * checked out" when an SDD references contract artefacts living in sibling
7
+ * repositories (the M-ADR 0110 contracts-* pattern).
8
+ *
9
+ * Two modes, per the format spec (docs/method/foundations/
10
+ * contract-availability-manifest.md in the method baseline):
11
+ * --refresh runs where siblings ARE checked out: resolves every collected
12
+ * reference, writes/updates entries with content digest and
13
+ * repo@commit coordinate. Preserves entries it cannot currently
14
+ * see (still referenced, artefact absent here); drops entries no
15
+ * longer referenced anywhere, and says so.
16
+ * --verify runs anywhere: present artefacts are digest-compared (drift);
17
+ * absent-but-attested is satisfied; referenced-but-unattested-
18
+ * and-absent is the failure class.
19
+ *
20
+ * References are collected from BOTH observed forms (M-ADR 0200 §4.5 — read
21
+ * what the estate writes):
22
+ * 1. traceability.contracts declarations (string and object entries), when
23
+ * the declared path does not resolve inside the repo;
24
+ * 2. markdown links under docs/ whose target escapes the repo root.
25
+ */
26
+ import { execFile as execFileCb } from "node:child_process";
27
+ import { createHash } from "node:crypto";
28
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
29
+ import { existsSync } from "node:fs";
30
+ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
31
+ import { promisify } from "node:util";
32
+ import { parse as parseYaml } from "yaml";
33
+ import { loadTraceabilityManifests } from "../catalogue/traceability-loader.js";
34
+ import { FindingsLog } from "../catalogue/findings.js";
35
+ const execFile = promisify(execFileCb);
36
+ export const AVAILABILITY_MANIFEST_REL_PATH = ".sdd/contract-availability.yaml";
37
+ export async function runAttest(opts) {
38
+ const log = [];
39
+ const repo = resolve(opts.targetRepo);
40
+ const codeRoot = resolve(opts.codeRoot ?? join(repo, ".."));
41
+ const manifestAbs = join(repo, AVAILABILITY_MANIFEST_REL_PATH);
42
+ const refs = await collectReferences(repo);
43
+ const existing = await readManifest(manifestAbs);
44
+ const existingByPath = new Map(existing.map((e) => [e.path, e]));
45
+ const refSet = new Set(refs.map((r) => r.ref));
46
+ if (opts.mode === "refresh") {
47
+ const entries = [];
48
+ const unresolvable = [];
49
+ const preserved = [];
50
+ for (const r of refs) {
51
+ const abs = resolveRef(r, repo, codeRoot);
52
+ if (abs !== null) {
53
+ entries.push(await attestOne(r.ref, abs));
54
+ }
55
+ else if (existingByPath.has(r.ref)) {
56
+ // Still referenced; artefact not visible from this machine. Keep the
57
+ // prior attestation rather than silently discarding evidence this
58
+ // run cannot re-establish.
59
+ entries.push(existingByPath.get(r.ref));
60
+ preserved.push(r.ref);
61
+ }
62
+ else {
63
+ unresolvable.push(r.ref);
64
+ }
65
+ }
66
+ const dropped = existing.filter((e) => !refSet.has(e.path)).map((e) => e.path);
67
+ await writeManifest(manifestAbs, entries);
68
+ log.push(`references collected: ${refs.length}`);
69
+ log.push(`attested: ${entries.length - preserved.length}`);
70
+ report(log, "preserved (referenced; artefact not visible here)", preserved);
71
+ report(log, "unresolvable on this machine (NOT attested)", unresolvable);
72
+ report(log, "dropped (no longer referenced anywhere)", dropped);
73
+ return { exitCode: 0, log };
74
+ }
75
+ // verify
76
+ const drift = [];
77
+ const attestedAbsent = [];
78
+ const presentClean = [];
79
+ const stale = [];
80
+ for (const e of existing) {
81
+ if (!refSet.has(e.path)) {
82
+ stale.push(e.path);
83
+ continue;
84
+ }
85
+ const abs = resolveRefPath(e.path, repo, codeRoot);
86
+ if (abs === null) {
87
+ attestedAbsent.push(e.path);
88
+ continue;
89
+ }
90
+ const digest = await sha256File(abs);
91
+ if (digest === e.sha256)
92
+ presentClean.push(e.path);
93
+ else
94
+ drift.push(`${e.path} (manifest ${e.sha256.slice(0, 12)}… vs present ${digest.slice(0, 12)}…)`);
95
+ }
96
+ const unattested = refs
97
+ .filter((r) => !existingByPath.has(r.ref) && resolveRef(r, repo, codeRoot) === null)
98
+ .map((r) => r.ref);
99
+ log.push(`entries: ${existing.length}, references: ${refs.length}`);
100
+ report(log, "present and clean", presentClean);
101
+ report(log, "attested (absent here, satisfied by manifest)", attestedAbsent);
102
+ report(log, "DRIFT (present artefact disagrees with attestation)", drift);
103
+ report(log, "UNATTESTED (referenced, absent, no entry)", unattested);
104
+ report(log, "stale entries (no longer referenced — remove)", stale);
105
+ const exitCode = drift.length > 0 || unattested.length > 0 ? 1 : 0;
106
+ return { exitCode, log };
107
+ }
108
+ async function collectReferences(repo) {
109
+ const out = new Map();
110
+ // 1. traceability.contracts declarations that do not resolve in-repo.
111
+ const manifests = await loadTraceabilityManifests({
112
+ sddPath: repo,
113
+ findings: new FindingsLog(),
114
+ });
115
+ for (const m of manifests) {
116
+ const block = m.traceability.contracts;
117
+ if (!block)
118
+ continue;
119
+ for (const list of [block.openapi, block.proto, block.avro, block.spicedb]) {
120
+ if (!Array.isArray(list))
121
+ continue;
122
+ for (const entry of list) {
123
+ const p = typeof entry === "string" ? entry : entry.path;
124
+ if (typeof p !== "string" || p === "")
125
+ continue;
126
+ if (existsSync(join(repo, p)))
127
+ continue;
128
+ out.set(p, { ref: p });
129
+ }
130
+ }
131
+ }
132
+ // 2. markdown links under docs/ escaping the repo root.
133
+ const files = await walkMd(join(repo, "docs"));
134
+ const LINK = /(?<!!)\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
135
+ for (const f of files) {
136
+ const txt = await readFile(f, "utf8").catch(() => "");
137
+ for (const match of txt.matchAll(LINK)) {
138
+ const raw = decodeURIComponent((match[1] ?? "").split("#")[0] ?? "").trim();
139
+ if (raw === "" || /^(https?|mailto|tel):/i.test(raw) || raw.startsWith("#"))
140
+ continue;
141
+ if (isAbsolute(raw) || raw.includes("{") || raw.includes("<"))
142
+ continue;
143
+ const abs = normalize(join(dirname(f), raw));
144
+ if (!relative(repo, abs).startsWith(".."))
145
+ continue;
146
+ out.set(raw, { ref: raw, relTo: dirname(f) });
147
+ }
148
+ }
149
+ return [...out.values()];
150
+ }
151
+ function resolveRef(r, repo, codeRoot) {
152
+ if (r.relTo !== undefined) {
153
+ const abs = normalize(join(r.relTo, r.ref));
154
+ return existsSync(abs) ? abs : null;
155
+ }
156
+ return resolveRefPath(r.ref, repo, codeRoot);
157
+ }
158
+ /** Repo-root-relative declaration: in-repo first, then under codeRoot. Doc-relative refs (../…) resolve against repo root's parent chain via normalize. */
159
+ function resolveRefPath(ref, repo, codeRoot) {
160
+ for (const base of [repo, codeRoot]) {
161
+ const abs = normalize(join(base, ref));
162
+ if (existsSync(abs))
163
+ return abs;
164
+ }
165
+ return null;
166
+ }
167
+ async function attestOne(ref, abs) {
168
+ const sha256 = await sha256File(abs);
169
+ const source = await sourceCoordinate(abs);
170
+ const kind = inferKind(ref);
171
+ return { path: ref, sha256, source, ...(kind ? { kind } : {}) };
172
+ }
173
+ async function sha256File(abs) {
174
+ const buf = await readFile(abs);
175
+ return createHash("sha256").update(buf).digest("hex");
176
+ }
177
+ async function sourceCoordinate(abs) {
178
+ try {
179
+ const dir = dirname(abs);
180
+ const { stdout: top } = await execFile("git", ["-C", dir, "rev-parse", "--show-toplevel"]);
181
+ const { stdout: head } = await execFile("git", ["-C", dir, "rev-parse", "--short", "HEAD"]);
182
+ const name = top.trim().split(sep).pop() ?? "unknown";
183
+ return `${name}@${head.trim()}`;
184
+ }
185
+ catch {
186
+ return "unversioned";
187
+ }
188
+ }
189
+ function inferKind(ref) {
190
+ if (ref.endsWith(".avsc"))
191
+ return "event";
192
+ if (ref.endsWith(".proto"))
193
+ return "rpc";
194
+ if (ref.endsWith(".zed"))
195
+ return "authz";
196
+ if (/openapi/.test(ref) && /\.(ya?ml|json)$/.test(ref))
197
+ return "rest";
198
+ return undefined;
199
+ }
200
+ async function readManifest(abs) {
201
+ if (!existsSync(abs))
202
+ return [];
203
+ const raw = await readFile(abs, "utf8");
204
+ const doc = parseYaml(raw);
205
+ if (!doc || !Array.isArray(doc.artefacts))
206
+ return [];
207
+ const out = [];
208
+ for (const a of doc.artefacts) {
209
+ if (a === null || typeof a !== "object")
210
+ continue;
211
+ const o = a;
212
+ if (typeof o["path"] !== "string" || typeof o["sha256"] !== "string")
213
+ continue;
214
+ out.push({
215
+ path: o["path"],
216
+ sha256: o["sha256"],
217
+ source: typeof o["source"] === "string" ? o["source"] : "unversioned",
218
+ ...(typeof o["kind"] === "string" ? { kind: o["kind"] } : {}),
219
+ });
220
+ }
221
+ return out;
222
+ }
223
+ async function writeManifest(abs, entries) {
224
+ const sorted = [...entries].sort((a, b) => a.path.localeCompare(b.path));
225
+ const lines = ['schema_version: "1.0"', "artefacts:"];
226
+ for (const e of sorted) {
227
+ lines.push(` - path: ${JSON.stringify(e.path)}`);
228
+ lines.push(` sha256: "${e.sha256}"`);
229
+ lines.push(` source: ${JSON.stringify(e.source)}`);
230
+ if (e.kind)
231
+ lines.push(` kind: "${e.kind}"`);
232
+ }
233
+ await mkdir(dirname(abs), { recursive: true });
234
+ await writeFile(abs, lines.join("\n") + "\n", "utf8");
235
+ }
236
+ async function walkMd(root) {
237
+ const { readdir } = await import("node:fs/promises");
238
+ const out = [];
239
+ async function rec(dir) {
240
+ let names;
241
+ try {
242
+ names = await readdir(dir, { withFileTypes: true });
243
+ }
244
+ catch {
245
+ return;
246
+ }
247
+ for (const d of names) {
248
+ const p = join(dir, d.name);
249
+ if (d.isDirectory()) {
250
+ if (d.name === "node_modules" || d.name === ".git")
251
+ continue;
252
+ await rec(p);
253
+ }
254
+ else if (d.name.endsWith(".md"))
255
+ out.push(p);
256
+ }
257
+ }
258
+ await rec(root);
259
+ return out;
260
+ }
261
+ function report(log, label, items) {
262
+ // Count AND list, always (M-ADR 0200 §4.3).
263
+ log.push(`${label}: ${items.length}`);
264
+ for (const i of items)
265
+ log.push(` - ${i}`);
266
+ }
267
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `sdd-cli generate-briefing` — regenerate `agent-instructions.md` +
3
+ * the harness wrapper for an existing SDD repo without pulling a
4
+ * bundle. Reuses the briefing-merge sync helpers so behaviour matches
5
+ * what `sdd-cli sync` writes (the templates already on disk are the
6
+ * source of truth).
7
+ *
8
+ * Replaces the v0.3.0 `generate-claude-md` verb whose rich-content
9
+ * output diverges from the ADR 0148 thin-pointer shape.
10
+ */
11
+ import { access } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { AGENT_INSTRUCTIONS_REL_PATH, BRIEFING_TEMPLATE_ROOT, harnessWrapperOutputPath, } from "../briefing/index.js";
14
+ import { syncBriefingFiles } from "../sync/briefing-merge.js";
15
+ import { DEFAULT_AGENT_HARNESS, resolveAgentHarness, } from "../agent-harness.js";
16
+ import { readRepoKind } from "../repo-kind.js";
17
+ import { readLockFile } from "../lock-file.js";
18
+ export async function runGenerateBriefing(opts, stdout = (line) => process.stdout.write(line)) {
19
+ const repoKind = await readRepoKind(opts.targetDir);
20
+ if (repoKind === null) {
21
+ stdout(fail(`Not an SDD repo: no .sdd-repo-kind marker in ${opts.targetDir}`));
22
+ return { exitCode: 1 };
23
+ }
24
+ const lock = await readLockFile(opts.targetDir);
25
+ if (!lock) {
26
+ stdout(fail(`Cannot regenerate briefing: method-baseline.lock missing at ${opts.targetDir}. Run sdd-cli sync first.`));
27
+ return { exitCode: 1 };
28
+ }
29
+ const forwardBase = lock.bundles.find((b) => b.isForwardBase);
30
+ if (!forwardBase) {
31
+ stdout(fail(`Cannot regenerate briefing: no forward-base bundle in method-baseline.lock at ${opts.targetDir}.`));
32
+ return { exitCode: 1 };
33
+ }
34
+ // Templates ship in the bundle and land here on sync. Refusing
35
+ // up-front when they're absent gives a clearer error than the
36
+ // graceful skip downstream.
37
+ const templatesPresent = await fileExists(join(opts.targetDir, BRIEFING_TEMPLATE_ROOT, "agent-instructions.md.hbs"));
38
+ if (!templatesPresent) {
39
+ stdout(fail(`Briefing templates not found under ${BRIEFING_TEMPLATE_ROOT}/ — sync against a baseline that ships ADR 0148 templates first.`));
40
+ return { exitCode: 1 };
41
+ }
42
+ const result = await syncBriefingFiles({
43
+ targetRepo: opts.targetDir,
44
+ repoKind,
45
+ baselineVersion: forwardBase.bundleVersion,
46
+ agentHarness: opts.agentHarness ?? DEFAULT_AGENT_HARNESS,
47
+ allowOverwrite: opts.allowOverwrite,
48
+ });
49
+ if (result.refused) {
50
+ stdout(fail(`${result.refused} has adopter edits outside the method-managed markers. Re-run with --allow-overwrite to replace the file.`));
51
+ return { exitCode: 1 };
52
+ }
53
+ for (const line of result.log)
54
+ stdout(info(line));
55
+ if (result.log.length === 0) {
56
+ stdout(info(`Briefing files already match templates — no changes (${AGENT_INSTRUCTIONS_REL_PATH}, ${harnessWrapperOutputPath(opts.agentHarness ?? DEFAULT_AGENT_HARNESS)}).`));
57
+ }
58
+ return { exitCode: 0 };
59
+ }
60
+ // Re-export so the verb layer can use the validator without an extra import line.
61
+ export { resolveAgentHarness };
62
+ async function fileExists(path) {
63
+ try {
64
+ await access(path);
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ function info(msg) {
72
+ return `[INFO] ${msg}\n`;
73
+ }
74
+ function fail(msg) {
75
+ return `[FAIL] ${msg}\n`;
76
+ }
77
+ //# sourceMappingURL=index.js.map