@sdd-method/maintain-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +28 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +123 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/bundle.js +123 -0
- package/dist/lib/catalogue/aggregate.js +2404 -0
- package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
- package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/contract-path-resolution.js +149 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-item-coverage.js +142 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +69 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/graph-run.js +64 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/ci-binding/index.js +220 -0
- package/dist/lib/ci-binding/module.js +204 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/context-pull/index.js +151 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/hooks-seed/copilot.js +167 -0
- package/dist/lib/hooks-seed/entries.js +47 -0
- package/dist/lib/hooks-seed/index.js +17 -0
- package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
- package/dist/lib/hooks-seed/merge.js +128 -0
- package/dist/lib/hooks-seed/types.js +20 -0
- package/dist/lib/init/ci-workflow.js +98 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/mcp-seed/entries.js +14 -0
- package/dist/lib/mcp-seed/index.js +28 -0
- package/dist/lib/mcp-seed/merge.js +99 -0
- package/dist/lib/mcp-seed/types.js +13 -0
- package/dist/lib/pack/builder.js +303 -0
- package/dist/lib/pack/index.js +87 -0
- package/dist/lib/pack/leakage.js +144 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/publish-transform.js +233 -0
- package/dist/lib/pack/reference-closure.js +125 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/snapshot/catalogue-diff.js +219 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/sync/adr-transitions.js +250 -0
- package/dist/lib/sync/apply.js +91 -0
- package/dist/lib/sync/briefing-merge.js +90 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/bundle-source.js +61 -0
- package/dist/lib/sync/config.js +72 -0
- package/dist/lib/sync/copilot-hooks-merge.js +108 -0
- package/dist/lib/sync/diff.js +251 -0
- package/dist/lib/sync/index.js +818 -0
- package/dist/lib/sync/settings-merge.js +216 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-bin.js +8 -0
- package/dist/maintain-program.js +91 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/catalogue-maintain.js +110 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-build.js +31 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/sync.js +110 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,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
|