@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,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,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
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Builders + drift detection for the method-managed GitHub Copilot CLI
3
+ * hooks, per `docs/method/methods/hook-contracts.md` (Hook 1, Hook 2)
4
+ * and the portable contracts in `hook-portable-contract-schema.yaml`.
5
+ *
6
+ * The Copilot CLI surface differs from Claude Code in three ways
7
+ * (per ADR 0141 §4.3 + hook-contracts.md):
8
+ *
9
+ * 1. Hook configs live in per-event JSON files (`.github/hooks/<event>.json`),
10
+ * not a single settings.json.
11
+ * 2. No per-tool matcher — every `preToolUse` hook fires for every
12
+ * tool, with in-script filtering instead.
13
+ * 3. Approval semantic is declarative (`approval: deny-on-nonzero`)
14
+ * rather than exit-code based.
15
+ *
16
+ * Ownership is sentinel-based, same as Claude Code: the `bash` and
17
+ * `powershell` command strings carry `# sdd-method-managed:true`.
18
+ * Adopter-added events in the same `events[]` array lack the sentinel
19
+ * and survive sync.
20
+ */
21
+ import { METHOD_MANAGED_SENTINEL } from "./types.js";
22
+ import { nativeEventName } from "./lifecycle-mapping.js";
23
+ const HARNESS = "github-copilot-cli";
24
+ /** Filename per portable lifecycle class. */
25
+ const HOOK_FILE_BY_PORTABLE = {
26
+ pre_tool_use: "pre-tool-use.json",
27
+ session_start: "session-start.json",
28
+ };
29
+ export function copilotHookFilePath(portable) {
30
+ const name = HOOK_FILE_BY_PORTABLE[portable];
31
+ if (!name) {
32
+ throw new Error(`No Copilot CLI hook file mapped for portable class "${portable}"`);
33
+ }
34
+ return `.github/hooks/${name}`;
35
+ }
36
+ function withSentinel(command) {
37
+ if (command.includes(METHOD_MANAGED_SENTINEL))
38
+ return command;
39
+ return `${command} ${METHOD_MANAGED_SENTINEL}`;
40
+ }
41
+ /**
42
+ * Hook 1 — `block-edits-to-method-managed-paths` (pre_tool_use).
43
+ * Copilot CLI has no per-tool matcher, so the script
44
+ * (orchestration/scripts/copilot-method-guard.{sh,ps1}) does the
45
+ * filtering on tool name in addition to the path check. Method-side
46
+ * authors the scripts; we just emit the wiring.
47
+ */
48
+ export function buildCopilotPreToolUseEvent() {
49
+ return {
50
+ type: nativeEventName(HARNESS, "pre_tool_use"),
51
+ command: {
52
+ bash: withSentinel("./orchestration/scripts/copilot-method-guard.sh"),
53
+ powershell: withSentinel("./orchestration/scripts/copilot-method-guard.ps1"),
54
+ },
55
+ timeout_ms: 5000,
56
+ approval: "deny-on-nonzero",
57
+ };
58
+ }
59
+ /**
60
+ * Hook 2 — `validate-adr-structure-on-session-start` (session_start).
61
+ * Output is surfaced as session-baseline context; non-blocking
62
+ * (`approval: always-allow`) to match the portable contract's `warn`
63
+ * exit-code semantic.
64
+ */
65
+ export function buildCopilotSessionStartEvent() {
66
+ return {
67
+ type: nativeEventName(HARNESS, "session_start"),
68
+ command: {
69
+ bash: withSentinel("sdd-cli validate --validator adr-structure 2>&1 | head -20"),
70
+ powershell: withSentinel("sdd-cli validate --validator adr-structure 2>&1 | Select-Object -First 20"),
71
+ },
72
+ timeout_ms: 30000,
73
+ approval: "always-allow",
74
+ };
75
+ }
76
+ export function buildMethodManagedCopilotHooks() {
77
+ return {
78
+ pre_tool_use: {
79
+ filePath: copilotHookFilePath("pre_tool_use"),
80
+ events: [buildCopilotPreToolUseEvent()],
81
+ },
82
+ session_start: {
83
+ filePath: copilotHookFilePath("session_start"),
84
+ events: [buildCopilotSessionStartEvent()],
85
+ },
86
+ };
87
+ }
88
+ /**
89
+ * An event entry is method-managed iff its `command.bash` (or
90
+ * `command.powershell`) carries the sentinel.
91
+ */
92
+ export function isMethodManagedEvent(event) {
93
+ const bash = typeof event.command?.bash === "string" ? event.command.bash : "";
94
+ const ps = typeof event.command?.powershell === "string"
95
+ ? event.command.powershell
96
+ : "";
97
+ return (bash.includes(METHOD_MANAGED_SENTINEL) ||
98
+ ps.includes(METHOD_MANAGED_SENTINEL));
99
+ }
100
+ /**
101
+ * Strip the sentinel and trailing whitespace from a command string so
102
+ * two commands can be compared modulo ownership marker. Symmetric to
103
+ * the Claude Code merge logic.
104
+ */
105
+ function normaliseCommandString(command) {
106
+ return command.replace(METHOD_MANAGED_SENTINEL, "").trimEnd();
107
+ }
108
+ function eventsEquivalentExcludingSentinel(a, b) {
109
+ if (a.type !== b.type)
110
+ return false;
111
+ if (a.approval !== b.approval)
112
+ return false;
113
+ if (a.timeout_ms !== b.timeout_ms)
114
+ return false;
115
+ const aBash = a.command?.bash ?? "";
116
+ const bBash = b.command?.bash ?? "";
117
+ const aPs = a.command?.powershell ?? "";
118
+ const bPs = b.command?.powershell ?? "";
119
+ return (normaliseCommandString(aBash) === normaliseCommandString(bBash) &&
120
+ normaliseCommandString(aPs) === normaliseCommandString(bPs));
121
+ }
122
+ /**
123
+ * Upsert method-managed events into an existing hook file. Adopter-
124
+ * added events (no sentinel) survive. An unmarked adopter event that
125
+ * is equivalent (modulo sentinel) to a method-managed event we're
126
+ * adding gets de-duped — same safety net as the Claude Code merge
127
+ * post-v0.12.2.
128
+ */
129
+ export function upsertCopilotHookFile(existing, owned) {
130
+ const currentEvents = existing?.events ?? [];
131
+ let dedupedAny = false;
132
+ const adopterEvents = currentEvents.filter((evt) => {
133
+ if (isMethodManagedEvent(evt))
134
+ return false;
135
+ const equivalent = owned.some((o) => eventsEquivalentExcludingSentinel(evt, o));
136
+ if (equivalent) {
137
+ dedupedAny = true;
138
+ return false;
139
+ }
140
+ return true;
141
+ });
142
+ const before = currentEvents.filter((e) => isMethodManagedEvent(e));
143
+ const after = owned;
144
+ const methodChanged = JSON.stringify(before) !== JSON.stringify(after);
145
+ const nextFile = {
146
+ version: 1,
147
+ events: [...adopterEvents, ...owned],
148
+ };
149
+ return {
150
+ file: nextFile,
151
+ changed: methodChanged || dedupedAny,
152
+ };
153
+ }
154
+ /**
155
+ * Detect adopter drift: an event still carrying the sentinel but whose
156
+ * content no longer matches the canonical version we'd write. Returns
157
+ * `true` iff drift is present.
158
+ */
159
+ export function detectCopilotHookDrift(existing, owned) {
160
+ if (!existing)
161
+ return false;
162
+ const existingMethod = existing.events.filter((e) => isMethodManagedEvent(e));
163
+ if (existingMethod.length === 0)
164
+ return false;
165
+ return JSON.stringify(existingMethod) !== JSON.stringify(owned);
166
+ }
167
+ //# sourceMappingURL=copilot.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Builders for the method-managed hook matchers per ADR 0129 §4.2.
3
+ *
4
+ * Every command carries the METHOD_MANAGED_SENTINEL suffix so the
5
+ * merge path can identify it on refresh. Adopter-added hooks under
6
+ * the same event survive because they lack the sentinel.
7
+ */
8
+ import { METHOD_MANAGED_SENTINEL } from "./types.js";
9
+ function withSentinel(command) {
10
+ if (command.includes(METHOD_MANAGED_SENTINEL))
11
+ return command;
12
+ return `${command} ${METHOD_MANAGED_SENTINEL}`;
13
+ }
14
+ export function buildMethodGuardPreToolUse() {
15
+ return {
16
+ matcher: "Edit|Write|NotebookEdit",
17
+ hooks: [
18
+ {
19
+ type: "command",
20
+ command: withSentinel('bash "${CLAUDE_PROJECT_DIR:-$(pwd)}/orchestration/scripts/claude-method-guard.sh"'),
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ export function buildSessionStartValidate() {
26
+ return {
27
+ matcher: "startup",
28
+ hooks: [
29
+ {
30
+ type: "command",
31
+ command: withSentinel('sdd-cli validate --target-repo "${CLAUDE_PROJECT_DIR:-$(pwd)}" --validator adr-structure 2>&1 | head -20'),
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ /**
37
+ * Full method-managed hooks block. When extending, add a new event
38
+ * name to the returned object and a corresponding builder above.
39
+ */
40
+ export function buildMethodManagedHooks() {
41
+ const hooks = {
42
+ PreToolUse: [buildMethodGuardPreToolUse()],
43
+ SessionStart: [buildSessionStartValidate()],
44
+ };
45
+ return hooks;
46
+ }
47
+ //# sourceMappingURL=entries.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public facade for the method-managed hooks seed library.
3
+ *
4
+ * Scope: this module owns method-managed entries inside
5
+ * `.claude/settings.json`'s `hooks` key. Ownership is identified by
6
+ * the `# sdd-method-managed:true` sentinel in each hook command.
7
+ *
8
+ * Integration: `src/lib/sync/settings-merge.ts` conditionally calls
9
+ * `upsertMethodManagedHooks` after `upsertMethodManagedServers` when
10
+ * the adopter opted into `--agent-harness claude-code` at init time.
11
+ */
12
+ export { METHOD_MANAGED_SENTINEL, isMethodManagedMatcher } from "./types.js";
13
+ export { buildMethodManagedHooks } from "./entries.js";
14
+ export { detectHooksDrift, upsertMethodManagedHooks, } from "./merge.js";
15
+ export { buildMethodManagedCopilotHooks, copilotHookFilePath, detectCopilotHookDrift, isMethodManagedEvent, upsertCopilotHookFile, } from "./copilot.js";
16
+ export { nativeEventName, } from "./lifecycle-mapping.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Portable lifecycle event class → harness-native event mapping per
3
+ * ADR 0141 §4.3 and the table in `docs/method/foundations/lifecycle-event-classes.yaml`.
4
+ *
5
+ * Adding a new harness binding is mechanical: extend `AgentHarness`
6
+ * in src/lib/agent-harness.ts and add a row here. Hook builders
7
+ * consume this mapping rather than hand-coding native event names.
8
+ */
9
+ const MAPPINGS = {
10
+ "claude-code": {
11
+ pre_tool_use: "PreToolUse",
12
+ post_tool_use: "PostToolUse",
13
+ user_prompt_submit: "UserPromptSubmit",
14
+ session_start: "SessionStart",
15
+ session_stop: "Stop",
16
+ },
17
+ "github-copilot-cli": {
18
+ pre_tool_use: "preToolUse",
19
+ post_tool_use: "postToolUse",
20
+ user_prompt_submit: "userPromptSubmitted",
21
+ session_start: "sessionStart",
22
+ session_stop: "agentStop",
23
+ },
24
+ };
25
+ export function nativeEventName(harness, portable) {
26
+ return MAPPINGS[harness][portable];
27
+ }
28
+ //# sourceMappingURL=lifecycle-mapping.js.map
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Merge logic for the method-managed hooks slice of
3
+ * `.claude/settings.json`. Ownership is sentinel-based: a method-
4
+ * managed matcher carries `# sdd-method-managed:true` in one of its
5
+ * commands. Adopter-added matchers lack the sentinel and survive
6
+ * sync untouched.
7
+ */
8
+ import { METHOD_MANAGED_SENTINEL, isMethodManagedMatcher } from "./types.js";
9
+ /**
10
+ * Strip the method-managed sentinel and trailing whitespace from a hook
11
+ * command so two commands can be compared modulo ownership marker.
12
+ */
13
+ function normaliseCommand(command) {
14
+ return command.replace(METHOD_MANAGED_SENTINEL, "").trimEnd();
15
+ }
16
+ /**
17
+ * Two matchers are equivalent (modulo sentinel) when:
18
+ * - their `matcher` strings are equal, AND
19
+ * - their `hooks` arrays have the same length, AND
20
+ * - each pair of corresponding `hooks[i]` has the same `type` and the
21
+ * same normalised `command` (sentinel suffix stripped, trailing
22
+ * whitespace trimmed).
23
+ *
24
+ * This handles the case where an adopter manually authored the method's
25
+ * hook before the first managed sync — the resulting entry differs from
26
+ * the method-supplied one only in the missing sentinel suffix.
27
+ */
28
+ function matchersEquivalentExcludingSentinel(a, b) {
29
+ if (a.matcher !== b.matcher)
30
+ return false;
31
+ if (a.hooks.length !== b.hooks.length)
32
+ return false;
33
+ for (let i = 0; i < a.hooks.length; i++) {
34
+ const ha = a.hooks[i];
35
+ const hb = b.hooks[i];
36
+ if (ha.type !== hb.type)
37
+ return false;
38
+ if (normaliseCommand(ha.command) !== normaliseCommand(hb.command)) {
39
+ return false;
40
+ }
41
+ }
42
+ return true;
43
+ }
44
+ export function upsertMethodManagedHooks(existing, owned) {
45
+ const current = isHooksObject(existing["hooks"])
46
+ ? existing["hooks"]
47
+ : {};
48
+ const nextHooks = {};
49
+ const changedEvents = [];
50
+ // Start by copying every existing event's adopter-owned matchers.
51
+ // Drop adopter matchers that are equivalent (modulo sentinel) to a
52
+ // method-managed matcher we're about to add — otherwise we'd leave
53
+ // a duplicated entry. This handles two cases:
54
+ // (a) Adopter authored the method's hook by hand before the first
55
+ // managed sync (no sentinel on their entry).
56
+ // (b) A historical sync that already produced the duplicate state
57
+ // (one marked entry + one unmarked equivalent); the unmarked
58
+ // twin is auto-cleaned on next merge.
59
+ let dedupedAny = false;
60
+ for (const [event, matchers] of Object.entries(current)) {
61
+ if (!matchers)
62
+ continue;
63
+ const ownedForEvent = owned[event] ?? [];
64
+ const adopterMatchers = matchers.filter((m) => {
65
+ if (isMethodManagedMatcher(m))
66
+ return false;
67
+ const equivalent = ownedForEvent.some((o) => matchersEquivalentExcludingSentinel(m, o));
68
+ if (equivalent) {
69
+ dedupedAny = true;
70
+ return false;
71
+ }
72
+ return true;
73
+ });
74
+ if (adopterMatchers.length > 0)
75
+ nextHooks[event] = [...adopterMatchers];
76
+ }
77
+ // Layer method-managed matchers on top.
78
+ for (const [event, ownedMatchers] of Object.entries(owned)) {
79
+ if (!ownedMatchers || ownedMatchers.length === 0)
80
+ continue;
81
+ const before = current[event]?.filter((m) => isMethodManagedMatcher(m)) ?? [];
82
+ const after = ownedMatchers;
83
+ if (JSON.stringify(before) !== JSON.stringify(after)) {
84
+ changedEvents.push(event);
85
+ }
86
+ const bucket = nextHooks[event] ?? [];
87
+ bucket.push(...ownedMatchers);
88
+ nextHooks[event] = bucket;
89
+ }
90
+ const changed = changedEvents.length > 0 || dedupedAny;
91
+ if (!changed &&
92
+ Object.keys(nextHooks).length === Object.keys(current).length) {
93
+ // No method-managed changes AND no adopter matchers reshuffled.
94
+ return { settings: existing, changedEvents: [], changed: false };
95
+ }
96
+ const nextSettings = {
97
+ ...existing,
98
+ hooks: nextHooks,
99
+ };
100
+ return { settings: nextSettings, changedEvents, changed };
101
+ }
102
+ /**
103
+ * Detect adopter drift inside a method-managed matcher — i.e., an
104
+ * entry that still carries the sentinel but whose content differs
105
+ * from what we would write now. Returns the list of events where drift
106
+ * is present.
107
+ */
108
+ export function detectHooksDrift(existing, owned) {
109
+ const current = isHooksObject(existing["hooks"])
110
+ ? existing["hooks"]
111
+ : {};
112
+ const drifted = [];
113
+ for (const [event, ownedMatchers] of Object.entries(owned)) {
114
+ if (!ownedMatchers)
115
+ continue;
116
+ const existingMethod = current[event]?.filter((m) => isMethodManagedMatcher(m)) ?? [];
117
+ if (existingMethod.length === 0)
118
+ continue; // no drift; freshly adding
119
+ if (JSON.stringify(existingMethod) !== JSON.stringify(ownedMatchers)) {
120
+ drifted.push(event);
121
+ }
122
+ }
123
+ return drifted;
124
+ }
125
+ function isHooksObject(v) {
126
+ return v !== null && typeof v === "object" && !Array.isArray(v);
127
+ }
128
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Types for the method-managed hooks slice of `.claude/settings.json`.
3
+ * Mirrors Claude Code's hooks shape, narrowed to the fields we read
4
+ * and write.
5
+ */
6
+ /** Sentinel suffix carried by method-managed hook commands. */
7
+ export const METHOD_MANAGED_SENTINEL = "# sdd-method-managed:true";
8
+ /**
9
+ * A hook entry is method-managed iff at least one of its commands
10
+ * carries the sentinel.
11
+ */
12
+ export function isMethodManagedMatcher(entry) {
13
+ for (const h of entry.hooks) {
14
+ if (typeof h.command === "string" && h.command.includes(METHOD_MANAGED_SENTINEL)) {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Renders .github/workflows/method-validation.yml: the GitHub Actions host wrapper (M-ADR 0219
3
+ * §4.4), which installs the pinned Dagger CLI and calls the Method Validation module. The job
4
+ * itself is defined once, in ../ci-binding/module.ts; the other hosts' wrappers are in
5
+ * ../ci-binding/index.ts.
6
+ *
7
+ * Output wraps a `# method-managed: begin` / `# method-managed: end`
8
+ * block-marker pair so the sync path can preserve adopter-authored
9
+ * content outside the block.
10
+ */
11
+ import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR } from "../ci-binding/module.js";
12
+ export const WORKFLOW_REL_PATH = ".github/workflows/method-validation.yml";
13
+ export const BLOCK_BEGIN = "# method-managed: begin";
14
+ export const BLOCK_END = "# method-managed: end";
15
+ /**
16
+ * Full file content (block + adopter-extension footer). Used by
17
+ * `sdd-cli init` to write a fresh workflow.
18
+ */
19
+ export function renderMethodValidationWorkflow(profile, runner) {
20
+ const methodBlock = buildMethodManagedBlock(profile, runner);
21
+ return methodBlock + "\n\n# Adopter-specific jobs go in a separate workflow file\n# (e.g. .github/workflows/adopter-validation.yml) — see\n# docs/method/methods/ci-baseline-guide.md.\n";
22
+ }
23
+ /**
24
+ * Method-managed slice only (begin marker through end marker). Used
25
+ * by `sdd-cli sync` as the `incomingBlock` argument to
26
+ * `writeMarkedBlockFile` so adopter content outside the markers is
27
+ * preserved verbatim.
28
+ */
29
+ export function renderMethodManagedWorkflowBlock(profile, runner) {
30
+ return buildMethodManagedBlock(profile, runner);
31
+ }
32
+ /**
33
+ * `runs-on`: GitHub's hosted runner unless the SDD chose labels (sync --ci-runner), e.g. a
34
+ * self-hosted runner, where the Dagger engine and its cache persist between runs.
35
+ */
36
+ function runsOn(runner) {
37
+ if (runner === undefined || runner.length === 0)
38
+ return "ubuntu-latest";
39
+ return runner.length === 1 ? runner[0] : `[${runner.join(", ")}]`;
40
+ }
41
+ function buildMethodManagedBlock(profile, runner) {
42
+ return [
43
+ BLOCK_BEGIN,
44
+ "# Method Validation per M-ADR 0129, run by the method's Dagger module (M-ADR 0219).",
45
+ "# Refreshed by `sdd-cli sync`; content inside the method-managed",
46
+ "# markers must not be hand-edited (sync refuses without --allow-overwrite).",
47
+ `# Profile: ${profile}`,
48
+ "#",
49
+ // The DECISION rationale is emitted because its reader is the ADOPTER, who otherwise sees a
50
+ // bare call and re-opens the question.
51
+ `# The job is defined once, in ${MODULE_DIR}/, and this file only calls it:`,
52
+ "# the same module runs on GitHub Actions, GitLab CI, Jenkins and a laptop,",
53
+ "# so CI and a local run cannot disagree. The module reads its steps from",
54
+ "# `sdd-verify validate --list` and runs them on @sdd-method/verify-cli, which",
55
+ "# ships only the verbs that read (M-ADR 0212), pinned EXACTLY to the version",
56
+ "# released with the sdd-cli that last synced this repository. The catalogue",
57
+ "# freshness step (@sdd-method/maintain-cli, pinned the same way) is advisory.",
58
+ "#",
59
+ "# Run it locally (needs a container engine):",
60
+ `# ${MODULE_CALL}`,
61
+ "",
62
+ "name: Method Validation",
63
+ "",
64
+ "on:",
65
+ " pull_request:",
66
+ " push:",
67
+ " branches: [main, master]",
68
+ ...pushPathsIgnore(profile),
69
+ " workflow_dispatch:",
70
+ "",
71
+ "jobs:",
72
+ // ONE job: GitHub bills each job rounded UP to a whole minute (adopter measurement, 2026-10-12).
73
+ " method-validation:",
74
+ " name: Method Validation",
75
+ ` runs-on: ${runsOn(runner)}`,
76
+ " steps:",
77
+ " - uses: actions/checkout@v4",
78
+ " - name: Install Dagger",
79
+ ` run: curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="$RUNNER_TEMP/dagger" DAGGER_VERSION=${DAGGER_VERSION} sh && echo "$RUNNER_TEMP/dagger" >> "$GITHUB_PATH"`,
80
+ " - name: Method Validation",
81
+ ` run: ${MODULE_CALL}`,
82
+ BLOCK_END,
83
+ ].join("\n");
84
+ }
85
+ /**
86
+ * `paths-ignore` for push, APPLICATION profile only: a push touching nothing but
87
+ * `catalogues/` (a catalogue rebuild) skips the run. Measured safe only there —
88
+ * no application validator reads the repo's own catalogues, whereas platform
89
+ * traceability and cascade-completeness and integration-docs do, so a
90
+ * catalogue-only change CAN change their verdict and they keep running.
91
+ * Push only: on pull_request a skipped workflow leaves a required check pending.
92
+ */
93
+ function pushPathsIgnore(profile) {
94
+ if (profile !== "application")
95
+ return [];
96
+ return [` paths-ignore:`, ` - "catalogues/**"`];
97
+ }
98
+ //# sourceMappingURL=ci-workflow.js.map