@sdd-method/maintain-cli 0.1.0 → 0.3.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/NOTICE +1 -1
- package/README.md +1 -1
- package/dist/lib/agent-harness.js +17 -8
- package/dist/lib/briefing/index.js +1 -1
- package/dist/lib/briefing/render.js +17 -8
- package/dist/lib/catalogue/aggregate.js +6 -6
- package/dist/lib/catalogue/aggregation-manifest-loader.js +4 -4
- package/dist/lib/catalogue/build.js +1 -1
- package/dist/lib/catalogue/builders/c4-components.js +1 -1
- package/dist/lib/catalogue/builders/c4-containers.js +1 -1
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +1 -1
- package/dist/lib/catalogue/builders/container-dependency.js +1 -1
- package/dist/lib/catalogue/builders/data-store-mapping.js +1 -1
- package/dist/lib/catalogue/builders/screen-tree.js +1 -1
- package/dist/lib/catalogue/contract-path-resolution.js +2 -2
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/freshness.js +4 -3
- package/dist/lib/catalogue/scores-coverage.js +1 -1
- package/dist/lib/catalogue/service-contract-aliases-loader.js +1 -1
- package/dist/lib/ci-binding/index.js +44 -5
- package/dist/lib/ci-binding/module.js +74 -2
- package/dist/lib/context-pull/index.js +79 -39
- package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
- package/dist/lib/hooks-seed/entries.js +55 -4
- package/dist/lib/hooks-seed/index.js +4 -7
- package/dist/lib/hooks-seed/merge.js +31 -0
- package/dist/lib/init/ci-workflow.js +26 -2
- package/dist/lib/lock-file.js +10 -0
- package/dist/lib/pack/builder.js +56 -4
- package/dist/lib/pack/index.js +1 -0
- package/dist/lib/pack/publish-transform.js +1 -1
- package/dist/lib/sync/briefing-merge.js +50 -34
- package/dist/lib/sync/diff.js +1 -1
- package/dist/lib/sync/index.js +46 -72
- package/dist/lib/sync/settings-merge.js +71 -163
- package/dist/lib/validate/plan.js +6 -3
- package/dist/maintain-program.js +6 -3
- package/dist/maintain-version.json +2 -2
- package/dist/verbs/catalogue-check.js +28 -0
- package/dist/verbs/context.js +1 -1
- package/dist/verbs/pack-build.js +22 -0
- package/dist/verbs/sync.js +17 -0
- package/dist/verbs/validate.js +6 -1
- package/dist/verify-program.js +2 -2
- package/dist/verify-version.json +2 -2
- package/package.json +2 -6
- package/dist/lib/hooks-seed/copilot.js +0 -167
- package/dist/lib/hooks-seed/lifecycle-mapping.js +0 -28
- package/dist/lib/mcp-seed/entries.js +0 -14
- package/dist/lib/mcp-seed/index.js +0 -28
- package/dist/lib/mcp-seed/merge.js +0 -99
- package/dist/lib/mcp-seed/types.js +0 -13
- package/dist/lib/sync/copilot-hooks-merge.js +0 -108
package/dist/maintain-program.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// (`context pull`). Plus the whole verify surface, because the personas nest.
|
|
11
11
|
//
|
|
12
12
|
// ⚠ As with sdd-verify, the boundary is ABSENCE, not a menu: the package ships only the import
|
|
13
|
-
// closure of maintain-bin.js, so authoring verbs (init, scaffold, plan,
|
|
13
|
+
// closure of maintain-bin.js, so authoring verbs (init, scaffold, plan, conformance,
|
|
14
14
|
// contracts, api-surface, snapshot, metamodel, migration-map) are not present to be reached.
|
|
15
15
|
// That is why every registration below imports a per-subcommand registrar, never a barrel.
|
|
16
16
|
// Unclassified verbs default to AUTHOR (M-ADR 0218 §4.2) and stay out until classified.
|
|
@@ -18,18 +18,20 @@ import { Command, CommanderError } from "commander";
|
|
|
18
18
|
import { getPackageVersion } from "./lib/package-version.js";
|
|
19
19
|
import { registerVerifySurface, rejectOutsideSurface, VERIFY_SURFACE } from "./verify-program.js";
|
|
20
20
|
import { registerCatalogueAggregate, registerCatalogueBuild, registerCatalogueDiff, registerCatalogueGraph } from "./verbs/catalogue-maintain.js";
|
|
21
|
+
import { registerCatalogueCheck } from "./verbs/catalogue-check.js";
|
|
21
22
|
import { registerContext } from "./verbs/context.js";
|
|
22
23
|
import { registerGenerateBriefing } from "./verbs/generate-briefing.js";
|
|
23
24
|
import { registerPackBuild } from "./verbs/pack-build.js";
|
|
24
25
|
import { registerRender } from "./verbs/render.js";
|
|
25
26
|
import { registerSync } from "./verbs/sync.js";
|
|
26
|
-
const SPEC_URL = "
|
|
27
|
+
const SPEC_URL = "docs/method/cli/sdd-cli-specification.md (in the method distribution)";
|
|
27
28
|
/** The verbs maintain adds to verify, as `<verb>` or `<verb> <subcommand>`. Asserted by test. */
|
|
28
29
|
export const MAINTAIN_ONLY_SURFACE = [
|
|
29
30
|
"sync",
|
|
30
31
|
"generate-briefing",
|
|
31
32
|
"context pull",
|
|
32
33
|
"catalogue build",
|
|
34
|
+
"catalogue check",
|
|
33
35
|
"catalogue aggregate",
|
|
34
36
|
"catalogue graph",
|
|
35
37
|
"catalogue diff",
|
|
@@ -65,7 +67,7 @@ export function buildMaintainProgram() {
|
|
|
65
67
|
.name("sdd-maintain")
|
|
66
68
|
.description("Bring an SDD repository current and rebuild its derived model — sync, briefing, catalogue build and aggregate, pack build, context pull — plus every read-only verify verb. Cannot author specification content.")
|
|
67
69
|
.version(getPackageVersion(), "-v, --version")
|
|
68
|
-
.addHelpText("after", `\nSpec: ${SPEC_URL}\nAuthoring verbs (
|
|
70
|
+
.addHelpText("after", `\nSpec: ${SPEC_URL}\nAuthoring verbs (scaffold, plan, conformance, …) are in @sdd-method/author-cli; init is in @sdd-method/sdd-cli.`)
|
|
69
71
|
.exitOverride()
|
|
70
72
|
.configureOutput({
|
|
71
73
|
writeOut: (str) => process.stdout.write(str),
|
|
@@ -80,6 +82,7 @@ export function buildMaintainProgram() {
|
|
|
80
82
|
const catalogue = program.commands.find((c) => c.name() === "catalogue");
|
|
81
83
|
catalogue.description("Build, aggregate, validate and query per-SDD catalogues (ADR 0134) — the derived model");
|
|
82
84
|
registerCatalogueBuild(catalogue);
|
|
85
|
+
registerCatalogueCheck(catalogue);
|
|
83
86
|
registerCatalogueAggregate(catalogue);
|
|
84
87
|
registerCatalogueGraph(catalogue);
|
|
85
88
|
registerCatalogueDiff(catalogue);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { checkCataloguesFresh } from "../lib/catalogue/freshness.js";
|
|
3
|
+
/**
|
|
4
|
+
* `catalogue check` (M-ADR 0220 §4.2, ruling 3): whether the committed catalogues are what the
|
|
5
|
+
* committed specifications build to. It builds into temporary space and writes nothing to the
|
|
6
|
+
* repository: `catalogue build --check` under its own name, so the author's surface holds no verb
|
|
7
|
+
* that writes the model. Its own registrar, so an artefact can carry it without the build,
|
|
8
|
+
* aggregate and graph registrations in catalogue-maintain.ts.
|
|
9
|
+
*/
|
|
10
|
+
export function registerCatalogueCheck(catalogue) {
|
|
11
|
+
catalogue
|
|
12
|
+
.command("check")
|
|
13
|
+
.description("Report whether the committed catalogues/ are what the committed specifications build to; builds in a temporary directory and writes nothing (exit 1 lists every stale file)")
|
|
14
|
+
.option("--sdd-path <path>", "Path to the SDD repo root (defaults to cwd)")
|
|
15
|
+
.option("--strict", "Treat build findings as failures")
|
|
16
|
+
.option("--snapshot-store <dir>", "Snapshot store root the binding's snapshot_area is relative to (default: $SDD_SNAPSHOT_STORE; M-ADR 0214)")
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const snapshotStore = opts.snapshotStore ?? process.env.SDD_SNAPSHOT_STORE;
|
|
19
|
+
const r = await checkCataloguesFresh(resolve(opts.sddPath ?? process.cwd()), {
|
|
20
|
+
label: "catalogue check",
|
|
21
|
+
...(opts.strict !== undefined ? { strict: opts.strict } : {}),
|
|
22
|
+
...(snapshotStore !== undefined ? { snapshotStore } : {}),
|
|
23
|
+
}, (s) => process.stdout.write(s));
|
|
24
|
+
if (r.exitCode !== 0)
|
|
25
|
+
process.exit(r.exitCode);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=catalogue-check.js.map
|
package/dist/verbs/context.js
CHANGED
|
@@ -8,7 +8,7 @@ export function registerContext(program) {
|
|
|
8
8
|
.description("Briefing for a code repository an SDD specifies (M-ADR 0128 §1, M-ADR 0150 §4.4)");
|
|
9
9
|
context
|
|
10
10
|
.command("pull")
|
|
11
|
-
.description("Write agent-instructions.md
|
|
11
|
+
.description("Write agent-instructions.md and the harness wrapper's method-managed block into this code repository, rendered from the templates its upstream SDD carries. The SDD is found by looking this repository up in the catalogues/repositories.csv of the SDDs beside it. Writes nothing else.")
|
|
12
12
|
.option("--target-repo <dir>", "the code repository to write into", resolvePathOption, process.cwd())
|
|
13
13
|
.option("--sdd <dir>", "the upstream SDD, skipping discovery", resolvePathOption)
|
|
14
14
|
.option("--repo <id>", "this repository's id in the SDD's catalogue (default: its git top-level directory name)")
|
package/dist/verbs/pack-build.js
CHANGED
|
@@ -13,7 +13,21 @@ export function registerPackBuild(pack) {
|
|
|
13
13
|
.option("--forward-base-managed-paths <path>", "path to forward-base MANAGED_PATHS.txt for the leakage check (defaults to extracting from method-baseline.lock)")
|
|
14
14
|
.option("--publish-transform", "apply the point-in-time / producer-agnostic publish transform (ADR 0183/0184): drop non-Accepted ADRs, strip lineage + stripped-class fields, flatten cross-repo refs, fail closed on leaks. For producer-published bundles.")
|
|
15
15
|
.option("--origin-prefix", "with the publish transform, apply the range-aware origin prefix (ADR 0140 §4.1): 0NNN→M-ADR, 9NNN→RA-ADR, adopter 1NNN stays ADR; ADR files renamed to match. Implies --publish-transform. The RA-ADR bundle cutover switch.")
|
|
16
|
+
.option("--source-commit <sha>", "provenance from a release record: the commit the manifest records (with --source-branch, --source-repo and --generated-at)")
|
|
17
|
+
.option("--source-branch <name>", "provenance from a release record: the branch the manifest records")
|
|
18
|
+
.option("--source-repo <name>", "provenance from a release record: the repository name the manifest records, whatever the checkout is called")
|
|
19
|
+
.option("--generated-at <utc>", "provenance from a release record: the manifest's generated_at_utc (YYYY-MM-DDTHH:MM:SSZ), also every archive member's file time")
|
|
16
20
|
.action(async (opts) => {
|
|
21
|
+
const rec = [opts.sourceCommit, opts.sourceBranch, opts.sourceRepo, opts.generatedAt];
|
|
22
|
+
const given = rec.filter((v) => v !== undefined).length;
|
|
23
|
+
if (given !== 0 && given !== 4) {
|
|
24
|
+
process.stderr.write(`--source-commit, --source-branch, --source-repo and --generated-at go together (got ${given} of 4)\n`);
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
if (opts.generatedAt !== undefined && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(opts.generatedAt)) {
|
|
28
|
+
process.stderr.write(`--generated-at must be UTC, YYYY-MM-DDTHH:MM:SSZ (got '${opts.generatedAt}')\n`);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
17
31
|
const { exitCode } = await runPackBuild({
|
|
18
32
|
seedPath: opts.seed,
|
|
19
33
|
producerRepo: opts.producerRepo ?? process.cwd(),
|
|
@@ -23,6 +37,14 @@ export function registerPackBuild(pack) {
|
|
|
23
37
|
}),
|
|
24
38
|
...(opts.publishTransform && { publishTransform: true }),
|
|
25
39
|
...(opts.originPrefix && { originPrefix: true }),
|
|
40
|
+
...(given === 4 && {
|
|
41
|
+
provenance: {
|
|
42
|
+
commit: opts.sourceCommit,
|
|
43
|
+
branch: opts.sourceBranch,
|
|
44
|
+
repo: opts.sourceRepo,
|
|
45
|
+
generatedAt: opts.generatedAt,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
26
48
|
});
|
|
27
49
|
if (exitCode !== 0)
|
|
28
50
|
process.exit(exitCode);
|
package/dist/verbs/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ import { loadSyncConfig } from "../lib/sync/config.js";
|
|
|
3
3
|
import { materialiseBundle } from "../lib/sync/bundle-source.js";
|
|
4
4
|
import { runSync } from "../lib/sync/index.js";
|
|
5
5
|
import { CI_BINDINGS, CI_RUNNER_DEFAULT, isCiBinding, parseCiRunner } from "../lib/ci-binding/index.js";
|
|
6
|
+
import { CLI_SOURCES, DEFAULT_VERIFY_CLI_PACKAGE, NPM_PACKAGE_NAME } from "../lib/hooks-seed/index.js";
|
|
6
7
|
import { resolvePathOption } from "./path-option.js";
|
|
7
8
|
export function registerSync(program) {
|
|
8
9
|
program
|
|
@@ -19,6 +20,8 @@ export function registerSync(program) {
|
|
|
19
20
|
.option("--agent-harness <name>", `target agent harness for hooks + briefing. Default: claude-code. Supported: ${AGENT_HARNESSES.join(", ")}`)
|
|
20
21
|
.option("--ci-binding <host>", `CI host whose wrapper calls the Method Validation module (M-ADR 0219): ${CI_BINDINGS.join(", ")}. Default: the binding the lock records, else github; a new choice is recorded in the lock`)
|
|
21
22
|
.option("--ci-runner <labels>", `runner label(s) the CI wrapper targets, comma-separated (GitHub runs-on, GitLab tags, Jenkins agent label), e.g. a self-hosted runner. '${CI_RUNNER_DEFAULT}' returns to the host's default runner. Default: what the lock records; a new choice is recorded`)
|
|
23
|
+
.option("--cli-source <source>", `where the session-start hook finds the verify CLI: ${CLI_SOURCES.join(" (npx at the pinned version) or ")} (sdd-verify from PATH, for an org that installs the release's CLI tarballs; the hook names the version to install when it differs). Default: what the lock records, else registry; a new choice is recorded`)
|
|
24
|
+
.option("--cli-package <name>", `the verify CLI's npm package under the registry source, for a registry that rescopes it. 'default' returns to ${DEFAULT_VERIFY_CLI_PACKAGE}. Default: what the lock records; a new choice is recorded`)
|
|
22
25
|
.action(async (opts) => {
|
|
23
26
|
if (opts.ciBinding !== undefined && !isCiBinding(opts.ciBinding)) {
|
|
24
27
|
process.stderr.write(`[FAIL] --ci-binding must be one of ${CI_BINDINGS.join(", ")}; got '${opts.ciBinding}'\n`);
|
|
@@ -34,6 +37,16 @@ export function registerSync(program) {
|
|
|
34
37
|
}
|
|
35
38
|
ciRunner = parsed;
|
|
36
39
|
}
|
|
40
|
+
if (opts.cliSource !== undefined && !CLI_SOURCES.includes(opts.cliSource)) {
|
|
41
|
+
process.stderr.write(`[FAIL] --cli-source must be one of ${CLI_SOURCES.join(", ")}; got '${opts.cliSource}'\n`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const cliSource = opts.cliSource;
|
|
45
|
+
if (opts.cliPackage !== undefined && opts.cliPackage !== "default" && !NPM_PACKAGE_NAME.test(opts.cliPackage)) {
|
|
46
|
+
process.stderr.write(`[FAIL] --cli-package takes an npm package name, or 'default'; got '${opts.cliPackage}'\n`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const cliPackage = opts.cliPackage === undefined ? undefined : opts.cliPackage === "default" ? null : opts.cliPackage;
|
|
37
50
|
let agentHarness;
|
|
38
51
|
try {
|
|
39
52
|
agentHarness = resolveAgentHarness(opts.agentHarness);
|
|
@@ -61,6 +74,8 @@ export function registerSync(program) {
|
|
|
61
74
|
agentHarness,
|
|
62
75
|
...(ciBinding ? { ciBinding } : {}),
|
|
63
76
|
...(ciRunner ? { ciRunner } : {}),
|
|
77
|
+
...(cliSource ? { cliSource } : {}),
|
|
78
|
+
...(cliPackage !== undefined ? { cliPackage } : {}),
|
|
64
79
|
...(opts.replaceBundle ? { replaceBundle: opts.replaceBundle } : {}),
|
|
65
80
|
...(opts.yes === true ? { yes: true } : {}),
|
|
66
81
|
});
|
|
@@ -90,6 +105,8 @@ export function registerSync(program) {
|
|
|
90
105
|
agentHarness,
|
|
91
106
|
...(ciBinding ? { ciBinding } : {}),
|
|
92
107
|
...(ciRunner ? { ciRunner } : {}),
|
|
108
|
+
...(cliSource ? { cliSource } : {}),
|
|
109
|
+
...(cliPackage !== undefined ? { cliPackage } : {}),
|
|
93
110
|
});
|
|
94
111
|
if (exitCode !== 0) {
|
|
95
112
|
await resolved.cleanup();
|
package/dist/verbs/validate.js
CHANGED
|
@@ -14,7 +14,12 @@ export function registerValidate(program) {
|
|
|
14
14
|
.option("--strict", "treat warnings as failures")
|
|
15
15
|
.option("--list", "print the method-managed validation job for this repo's profile as JSON (the plan every CI executor runs, the Dagger module included) and run nothing")
|
|
16
16
|
.option("--profile <profile>", "with --list: the profile to list, instead of reading .sdd-repo-kind (platform | application | integration)")
|
|
17
|
+
.option("--catalogue-rebuild", "with --list: the pipeline rebuilt the catalogues on this run (M-ADR 0220 §4.4), so the catalogue freshness step gates instead of advising")
|
|
17
18
|
.action(async (opts) => {
|
|
19
|
+
if (opts.catalogueRebuild === true && opts.list !== true) {
|
|
20
|
+
process.stderr.write("--catalogue-rebuild only applies with --list\n");
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
18
23
|
if (opts.list === true) {
|
|
19
24
|
let profile;
|
|
20
25
|
if (opts.profile !== undefined) {
|
|
@@ -32,7 +37,7 @@ export function registerValidate(program) {
|
|
|
32
37
|
}
|
|
33
38
|
profile = profileFromRepoKind(kind);
|
|
34
39
|
}
|
|
35
|
-
process.stdout.write(JSON.stringify({ profile, steps: methodValidationPlan(profile) }, null, 2) + "\n");
|
|
40
|
+
process.stdout.write(JSON.stringify({ profile, steps: methodValidationPlan(profile, { catalogueRebuild: opts.catalogueRebuild === true }) }, null, 2) + "\n");
|
|
36
41
|
return;
|
|
37
42
|
}
|
|
38
43
|
const { exitCode } = await runValidate({
|
package/dist/verify-program.js
CHANGED
|
@@ -29,7 +29,7 @@ import { registerCatalogueBaselineCheck, registerCatalogueImpact, registerCatalo
|
|
|
29
29
|
import { registerList } from "./verbs/list.js";
|
|
30
30
|
import { registerPackValidate } from "./verbs/pack-validate.js";
|
|
31
31
|
import { registerValidate } from "./verbs/validate.js";
|
|
32
|
-
const SPEC_URL = "
|
|
32
|
+
const SPEC_URL = "docs/method/cli/sdd-cli-specification.md (in the method distribution)";
|
|
33
33
|
/** The full surface, as `<verb>` or `<verb> <subcommand>`. Asserted by test. */
|
|
34
34
|
export const VERIFY_SURFACE = [
|
|
35
35
|
"validate",
|
|
@@ -91,7 +91,7 @@ export function buildVerifyProgram() {
|
|
|
91
91
|
.name("sdd-verify")
|
|
92
92
|
.description("Verify an SDD repository without changing it — validate, list, and read-only catalogue and pack checks. Cannot write to the repository it is pointed at.")
|
|
93
93
|
.version(getPackageVersion(), "-v, --version")
|
|
94
|
-
.addHelpText("after", `\nSpec: ${SPEC_URL}\nAuthoring and maintenance verbs (sync, catalogue build, init, scaffold,
|
|
94
|
+
.addHelpText("after", `\nSpec: ${SPEC_URL}\nAuthoring and maintenance verbs (sync, catalogue build, init, scaffold, …) are in @sdd-method/sdd-cli.`)
|
|
95
95
|
.exitOverride()
|
|
96
96
|
.configureOutput({
|
|
97
97
|
writeOut: (str) => process.stdout.write(str),
|
package/dist/verify-version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdd-method/maintain-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"sddCliVersion": "1.
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"sddCliVersion": "1.44.0",
|
|
5
5
|
"description": "Bring an SDD repository current and rebuild its derived model — sync, briefing, catalogue build and aggregate, pack build, context pull — plus every read-only verify verb. Ships only the code those verbs reach; it cannot author specification content. Authoring is in @sdd-method/sdd-cli.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"type": "module",
|
|
@@ -17,10 +17,6 @@
|
|
|
17
17
|
"zod": "^4.3.6",
|
|
18
18
|
"@duckdb/node-api": "^1.5.5-r.3"
|
|
19
19
|
},
|
|
20
|
-
"repository": {
|
|
21
|
-
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/tomfosteruk/sdd-cli.git"
|
|
23
|
-
},
|
|
24
20
|
"publishConfig": {
|
|
25
21
|
"access": "public"
|
|
26
22
|
},
|
|
@@ -1,167 +0,0 @@
|
|
|
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
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Entry builders — produce the McpServerEntry blocks this plan owns.
|
|
3
|
-
*/
|
|
4
|
-
export function buildSddMcpEntry(opts = {}) {
|
|
5
|
-
const entry = {
|
|
6
|
-
command: "sdd-cli",
|
|
7
|
-
args: ["mcp", "serve"],
|
|
8
|
-
...(opts.sddRepoPath !== undefined
|
|
9
|
-
? { env: { SDD_REPO_PATH: opts.sddRepoPath } }
|
|
10
|
-
: {}),
|
|
11
|
-
};
|
|
12
|
-
return entry;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=entries.js.map
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public facade for the MCP settings-seed library. Consumers:
|
|
3
|
-
*
|
|
4
|
-
* - `sdd-cli init` scaffolds (platform/app/integration) call
|
|
5
|
-
* `buildMethodManagedServers()` + `upsertMethodManagedServers()`
|
|
6
|
-
* to produce a correctly-seeded `.claude/settings.json`.
|
|
7
|
-
* - `sdd-cli sync` routes the same file through the merge path to
|
|
8
|
-
* upsert method-owned entries without clobbering adopter content.
|
|
9
|
-
*
|
|
10
|
-
* Scope: this module owns `mcpServers.<method-id>`. Hooks and other
|
|
11
|
-
* top-level keys belong to the enforcement-automation plan; we pass
|
|
12
|
-
* them through untouched.
|
|
13
|
-
*/
|
|
14
|
-
export { METHOD_MANAGED_SERVER_IDS } from "./types.js";
|
|
15
|
-
export { buildSddMcpEntry } from "./entries.js";
|
|
16
|
-
export { detectManagedDrift, upsertMethodManagedServers, } from "./merge.js";
|
|
17
|
-
import { buildSddMcpEntry } from "./entries.js";
|
|
18
|
-
/**
|
|
19
|
-
* Produce the full owned-servers map. Adding a new method-managed
|
|
20
|
-
* server id is a one-place change: add it here and to
|
|
21
|
-
* METHOD_MANAGED_SERVER_IDS.
|
|
22
|
-
*/
|
|
23
|
-
export function buildMethodManagedServers(opts = {}) {
|
|
24
|
-
return {
|
|
25
|
-
"sdd-mcp": buildSddMcpEntry(opts.sddRepoPath !== undefined ? { sddRepoPath: opts.sddRepoPath } : {}),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Merge logic for the method-managed portion of `.claude/settings.json`.
|
|
3
|
-
*
|
|
4
|
-
* Ownership model: the method owns specific server ids (listed in
|
|
5
|
-
* `METHOD_MANAGED_SERVER_IDS`). On upsert, those ids are rewritten
|
|
6
|
-
* with the method-supplied entry; any other server ids the adopter
|
|
7
|
-
* has added under `mcpServers` survive untouched, as do all other
|
|
8
|
-
* top-level keys (hooks, permissions, env, …).
|
|
9
|
-
*
|
|
10
|
-
* The merge is conservative: if a method id exists with a value that
|
|
11
|
-
* is byte-identical to the method's current seed, we return the
|
|
12
|
-
* original object unmodified (stable output → no spurious writes).
|
|
13
|
-
*/
|
|
14
|
-
export function upsertMethodManagedServers(existing, owned) {
|
|
15
|
-
const added = [];
|
|
16
|
-
const updated = [];
|
|
17
|
-
const unchanged = [];
|
|
18
|
-
const currentServers = isPlainObject(existing["mcpServers"])
|
|
19
|
-
? existing["mcpServers"]
|
|
20
|
-
: {};
|
|
21
|
-
const nextServers = { ...currentServers };
|
|
22
|
-
for (const [id, entry] of Object.entries(owned)) {
|
|
23
|
-
const prior = currentServers[id];
|
|
24
|
-
if (prior === undefined) {
|
|
25
|
-
nextServers[id] = entry;
|
|
26
|
-
added.push(id);
|
|
27
|
-
}
|
|
28
|
-
else if (!entriesEqual(prior, entry)) {
|
|
29
|
-
nextServers[id] = entry;
|
|
30
|
-
updated.push(id);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
unchanged.push(id);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const changed = added.length > 0 || updated.length > 0;
|
|
37
|
-
if (!changed) {
|
|
38
|
-
return {
|
|
39
|
-
settings: existing,
|
|
40
|
-
added,
|
|
41
|
-
updated,
|
|
42
|
-
unchanged,
|
|
43
|
-
changed: false,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const nextSettings = {
|
|
47
|
-
...existing,
|
|
48
|
-
mcpServers: nextServers,
|
|
49
|
-
};
|
|
50
|
-
return {
|
|
51
|
-
settings: nextSettings,
|
|
52
|
-
added,
|
|
53
|
-
updated,
|
|
54
|
-
unchanged,
|
|
55
|
-
changed: true,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Detect adopter-edited method-managed entries. Returns the ids whose
|
|
60
|
-
* current value differs from the method-supplied seed — these are the
|
|
61
|
-
* ones that a refuse-or-overwrite sync should flag unless
|
|
62
|
-
* `--allow-overwrite` is passed.
|
|
63
|
-
*/
|
|
64
|
-
export function detectManagedDrift(existing, owned) {
|
|
65
|
-
const currentServers = isPlainObject(existing["mcpServers"])
|
|
66
|
-
? existing["mcpServers"]
|
|
67
|
-
: {};
|
|
68
|
-
const drifted = [];
|
|
69
|
-
for (const [id, entry] of Object.entries(owned)) {
|
|
70
|
-
const prior = currentServers[id];
|
|
71
|
-
if (prior !== undefined && !entriesEqual(prior, entry)) {
|
|
72
|
-
drifted.push(id);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return drifted;
|
|
76
|
-
}
|
|
77
|
-
function entriesEqual(a, b) {
|
|
78
|
-
// Stable-order JSON comparison. The entry shape is small and flat
|
|
79
|
-
// enough that stringifying is cheaper than writing a structural
|
|
80
|
-
// walker.
|
|
81
|
-
return stableStringify(a) === stableStringify(b);
|
|
82
|
-
}
|
|
83
|
-
function stableStringify(value) {
|
|
84
|
-
if (value === null || typeof value !== "object")
|
|
85
|
-
return JSON.stringify(value);
|
|
86
|
-
if (Array.isArray(value)) {
|
|
87
|
-
return "[" + value.map((v) => stableStringify(v)).join(",") + "]";
|
|
88
|
-
}
|
|
89
|
-
const keys = Object.keys(value).sort();
|
|
90
|
-
return ("{" +
|
|
91
|
-
keys
|
|
92
|
-
.map((k) => JSON.stringify(k) + ":" + stableStringify(value[k]))
|
|
93
|
-
.join(",") +
|
|
94
|
-
"}");
|
|
95
|
-
}
|
|
96
|
-
function isPlainObject(v) {
|
|
97
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
98
|
-
}
|
|
99
|
-
//# sourceMappingURL=merge.js.map
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for the MCP settings-seed library. Mirrors the shape Claude
|
|
3
|
-
* Code expects at `.claude/settings.json`, restricted to the fields
|
|
4
|
-
* we read or write.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Server ids owned by this method plan. Sync upserts these and leaves
|
|
8
|
-
* everything else alone. When Phases 3+4 ship adopter servers, extend
|
|
9
|
-
* the list — or better, let each plan contribute its own list and
|
|
10
|
-
* combine at merge time.
|
|
11
|
-
*/
|
|
12
|
-
export const METHOD_MANAGED_SERVER_IDS = ["sdd-mcp"];
|
|
13
|
-
//# sourceMappingURL=types.js.map
|