@sdd-method/maintain-cli 0.2.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/dist/lib/catalogue/aggregate.js +6 -6
- 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/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/hooks-seed/entries.js +43 -21
- package/dist/lib/hooks-seed/index.js +1 -1
- 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/index.js +26 -2
- package/dist/lib/sync/settings-merge.js +1 -1
- package/dist/lib/validate/plan.js +6 -3
- package/dist/maintain-program.js +1 -1
- package/dist/maintain-version.json +2 -2
- 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 +1 -1
- package/dist/verify-version.json +2 -2
- package/package.json +2 -6
package/NOTICE
CHANGED
|
@@ -2,7 +2,7 @@ sdd-cli
|
|
|
2
2
|
Copyright 2026 Tom Foster
|
|
3
3
|
|
|
4
4
|
This product includes software developed as part of the SDD method
|
|
5
|
-
distribution
|
|
5
|
+
distribution.
|
|
6
6
|
|
|
7
7
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
8
|
you may not use this file except in compliance with the License.
|
|
@@ -184,8 +184,8 @@ export async function runCatalogueAggregate(inputs, write) {
|
|
|
184
184
|
// The same roots, additionally reachable by DIRECTORY BASENAME. An author who
|
|
185
185
|
// writes a `platform/<name>/...` contract path is reading that name off the
|
|
186
186
|
// filesystem, so it is the directory — which is routinely not the sdd_id (one
|
|
187
|
-
// estate declares `
|
|
188
|
-
// `sdd-
|
|
187
|
+
// estate declares `customer-platform` in a directory named
|
|
188
|
+
// `sdd-customer-platform`). Kept separate from sddRootsById on purpose: the
|
|
189
189
|
// try-every-root pass must iterate each root ONCE, or a root reachable under two
|
|
190
190
|
// names would count as two hits and report a false ambiguity.
|
|
191
191
|
const sddRootAliases = new Map(sddRootsById);
|
|
@@ -1385,8 +1385,8 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
|
|
|
1385
1385
|
const containers = rowsByCatalogue.get("c4_containers") ?? [];
|
|
1386
1386
|
const deps = rowsByCatalogue.get("system_external_dependency") ?? [];
|
|
1387
1387
|
// Camel humps split BEFORE lowercasing (F11a): most SDD ids are
|
|
1388
|
-
// hyphenated, and `
|
|
1389
|
-
// `
|
|
1388
|
+
// hyphenated, and `posGatewayFramework` must be able to declare
|
|
1389
|
+
// `pos-gateway-framework`.
|
|
1390
1390
|
const norm = (s) => s
|
|
1391
1391
|
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
1392
1392
|
.toLowerCase()
|
|
@@ -1556,10 +1556,10 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
|
|
|
1556
1556
|
// the display name agrees with the target — diagram entry ids are
|
|
1557
1557
|
// conventionally local, and an old mnemonic that happens to equal an
|
|
1558
1558
|
// estate id must not convert silently. Comparison is TOKEN-level, not
|
|
1559
|
-
// substring (F12): "
|
|
1559
|
+
// substring (F12): "Stockroom Service" must not corroborate `stock`
|
|
1560
1560
|
// (shared prefix, different entity), while "Engine (telematics
|
|
1561
1561
|
// processor)" must corroborate a container named "Engine" (a richer
|
|
1562
|
-
// label must not defeat the match) and three-letter names like "
|
|
1562
|
+
// label must not defeat the match) and three-letter names like "OMS"
|
|
1563
1563
|
// must work (no length floor). Corroborated = compact equality with the
|
|
1564
1564
|
// target's name or id, or every token of either appearing among the
|
|
1565
1565
|
// label's tokens. A declared id that corroborates is an assertion the
|
|
@@ -1158,7 +1158,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
|
|
|
1158
1158
|
out.set("repo_dependency", await buildRepoDependencies({ sddPath, configs: reposResult.configs, knownRepoIds: knownRepoIdsFinal, findings }));
|
|
1159
1159
|
// 15. C4 systems + containers + components (L1 + L2 + L3). The L3 walker
|
|
1160
1160
|
// shipped per `coordination/c4-l3-walker-and-path-convention-coordination.md`
|
|
1161
|
-
// (
|
|
1161
|
+
// (method governance, 2026-05-29) — forward-method authoring path reading
|
|
1162
1162
|
// `c4-l3-*.mermaid.md` files alongside the L2 layout. The earlier Path-B
|
|
1163
1163
|
// deferral assumed reverse-method code-walking; this walker is the
|
|
1164
1164
|
// forward-method analogue, granularity-agnostic (per-container or
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Mirrors c4-containers.ts at one level down. Source convention per
|
|
8
8
|
// `coordination/c4-l3-walker-and-path-convention-coordination.md`
|
|
9
|
-
// (
|
|
9
|
+
// (method governance, 2026-05-29). The walker is granularity-agnostic — both
|
|
10
10
|
// per-container files (one `Container_Boundary` block per file) and
|
|
11
11
|
// per-domain rollup files (multiple `Container_Boundary` blocks per file)
|
|
12
12
|
// produce identical row output. Adopters pick the file shape that suits
|
|
@@ -219,7 +219,7 @@ function slugify(s) {
|
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
221
|
* F21: two technology strings flatly contradict when both are present and
|
|
222
|
-
* share no token — ".NET 6 (in
|
|
222
|
+
* share no token — ".NET 6 (in Kestrel)" vs "Java" needs no NLP. Absent or
|
|
223
223
|
* overlapping strings are NOT a contradiction (silence, not a guess).
|
|
224
224
|
*/
|
|
225
225
|
export function techContradicts(a, b) {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// personas instead of the sibling's. c4-external-systems UNIONS every
|
|
21
21
|
// existing L1 file rather than picking one, so candidate order is irrelevant
|
|
22
22
|
// there; it shares these paths only to avoid a divergent copy.
|
|
23
|
-
// See
|
|
23
|
+
// See the method's coordination record persona-c4system-l1-priority-bug.
|
|
24
24
|
import { readdir, stat } from "node:fs/promises";
|
|
25
25
|
import { join } from "node:path";
|
|
26
26
|
/** Own-L1 candidates usable by ANY SDD, in priority order. */
|
|
@@ -121,7 +121,7 @@ export async function buildContainerDependency(inputs) {
|
|
|
121
121
|
* SAME referent and fired. The predicate was inverted on the two
|
|
122
122
|
* name-shapes the estate actually uses.
|
|
123
123
|
*
|
|
124
|
-
* The independent channel is the technology string: ".NET 6 (in
|
|
124
|
+
* The independent channel is the technology string: ".NET 6 (in Kestrel)"
|
|
125
125
|
* against "Java" is a flat contradiction and needs no NLP. A dual-use
|
|
126
126
|
* entry-id (Container + System_Ext in one SDD) whose technologies
|
|
127
127
|
* contradict draws the finding REGARDLESS of what the names say — name
|
|
@@ -71,7 +71,7 @@ export function collectTableLines(lines, startIdx) {
|
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
73
73
|
// ADR 0164: map a Data Store Mapping `Store` cell (a physical engine such as
|
|
74
|
-
// "MySQL (
|
|
74
|
+
// "MySQL (orders-db)", "MongoDB", "DynamoDB", "Kinesis") to the canonical
|
|
75
75
|
// `entities.persistence_kind` classification. This mirrors the services
|
|
76
76
|
// builder's deriveLanguage (a normalised token derived from a raw cell) —
|
|
77
77
|
// faithful classification, not a verbatim engine. The raw engine string is
|
|
@@ -35,7 +35,7 @@ const STRING_RE = /"[^"]*"|'[^']*'/g;
|
|
|
35
35
|
* line-structure-preserving) so call-head lexing can never fire on quoted
|
|
36
36
|
* content — `Table("Odometer (km)")` must not mint a phantom `Odometer`
|
|
37
37
|
* component, and `"total (sum)"` must not false-fire the lowercase-call-head
|
|
38
|
-
* finding (
|
|
38
|
+
* finding (sdd-cli issue 1). Quote characters are kept; newlines inside
|
|
39
39
|
* a span survive. The grammar defines no escape sequences (a quote always
|
|
40
40
|
* closes the literal), matching STRING_RE and stripLineComment.
|
|
41
41
|
*
|
|
@@ -54,8 +54,8 @@ export function isPathShaped(value) {
|
|
|
54
54
|
* sdd_id OR by its directory basename, and the distinction is not cosmetic: an
|
|
55
55
|
* author writing this prefix is reading it off the filesystem, so it is the
|
|
56
56
|
* DIRECTORY they write. In one measured estate every such prefix is
|
|
57
|
-
* `platform/sdd-
|
|
58
|
-
* `
|
|
57
|
+
* `platform/sdd-customer-platform/` while that SDD's declared sdd_id is
|
|
58
|
+
* `customer-platform` — matching on sdd_id alone therefore split nothing and
|
|
59
59
|
* reported 13 existing contracts as missing. Both keys are accepted.
|
|
60
60
|
*
|
|
61
61
|
* That prefix
|
|
Binary file
|
|
@@ -171,7 +171,7 @@ export const RUBRIC_ELIGIBILITY = [
|
|
|
171
171
|
joinColumn: "id",
|
|
172
172
|
refTransform: null,
|
|
173
173
|
eligibilityFilter: null,
|
|
174
|
-
eligibilityNote: "NO local population, measured rather than assumed. Refs are '<domain>-feature-grounding-<slug>' and the tails are SIBLING SDD SLUGS (app-shell,
|
|
174
|
+
eligibilityNote: "NO local population, measured rather than assumed. Refs are '<domain>-feature-grounding-<slug>' and the tails are SIBLING SDD SLUGS (app-shell, gift-cards, storefront-express), not rows in this SDD: 0/15 against products, applications, features and repositories, and 1/15 against capabilities — a coincidence, not a join. A cross-SDD reference the member tier cannot resolve, so its coverage is declared unmeasurable rather than given a transform that would produce plausible wrong numbers.",
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
rubricId: "reverse-contract-quality",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* # services). Use when the federated SDD lacks a services.csv to
|
|
28
28
|
* # gate emission and consumes rows would otherwise dangle at the
|
|
29
29
|
* # aggregator with unresolved service_id.
|
|
30
|
-
* - postman-
|
|
30
|
+
* - postman-stock
|
|
31
31
|
* - smoke-tests
|
|
32
32
|
*
|
|
33
33
|
* same_product_inference:
|
|
@@ -14,7 +14,7 @@ import { createHash } from "node:crypto";
|
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { BLOCK_BEGIN, BLOCK_END, WORKFLOW_REL_PATH, renderMethodManagedWorkflowBlock, renderMethodValidationWorkflow, } from "../init/ci-workflow.js";
|
|
16
16
|
import { detectBlockDrift, writeMarkedBlockFile } from "../sync/yaml-block-merge.js";
|
|
17
|
-
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, moduleSha256, renderModuleFiles } from "./module.js";
|
|
17
|
+
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, REBUILD_FLAG, REBUILD_SCRIPT, moduleSha256, renderModuleFiles } from "./module.js";
|
|
18
18
|
export const CI_BINDINGS = ["github", "gitlab", "jenkins"];
|
|
19
19
|
/** M-ADR 0219 §8.2 (ruled 2026-10-16): what every SDD has before it chooses. */
|
|
20
20
|
export const DEFAULT_CI_BINDING = "github";
|
|
@@ -34,6 +34,18 @@ export function isCiBinding(s) {
|
|
|
34
34
|
const HASH = { begin: BLOCK_BEGIN, end: BLOCK_END };
|
|
35
35
|
// Groovy has no '#' comments.
|
|
36
36
|
const SLASH = { begin: "// method-managed: begin", end: "// method-managed: end" };
|
|
37
|
+
// How each host's adopter switches the catalogue rebuild on (M-ADR 0220 §4.4): the variable the method names.
|
|
38
|
+
const REBUILD_SETUP = {
|
|
39
|
+
"#": [
|
|
40
|
+
"set the masked (not protected) CI variable",
|
|
41
|
+
"SDD_CATALOGUE_PUSH_TOKEN to a project or group access token with write_repository.",
|
|
42
|
+
],
|
|
43
|
+
"//": [
|
|
44
|
+
"set the job's environment variable",
|
|
45
|
+
"SDD_CATALOGUE_PUSH_CREDENTIAL to the ID of a username-and-password credential (a token",
|
|
46
|
+
"with write) for the repository's https remote.",
|
|
47
|
+
],
|
|
48
|
+
};
|
|
37
49
|
function header(comment, profile) {
|
|
38
50
|
return [
|
|
39
51
|
`${comment} Method Validation per M-ADR 0129, run by the method's Dagger module (M-ADR 0219).`,
|
|
@@ -47,6 +59,11 @@ function header(comment, profile) {
|
|
|
47
59
|
comment,
|
|
48
60
|
`${comment} Run it locally:`,
|
|
49
61
|
`${comment} ${MODULE_CALL}`,
|
|
62
|
+
comment,
|
|
63
|
+
`${comment} Catalogue rebuild (M-ADR 0220 §4.4), off until you configure it:`,
|
|
64
|
+
...REBUILD_SETUP[comment].map((l) => `${comment} ${l}`),
|
|
65
|
+
`${comment} The job then rebuilds the catalogues and pushes them to the branch it is`,
|
|
66
|
+
`${comment} building (never a fork's), and the freshness step gates.`,
|
|
50
67
|
];
|
|
51
68
|
}
|
|
52
69
|
// GitLab: a Docker-in-Docker service, the shape GitLab documents for Dagger. The runner must allow
|
|
@@ -71,10 +88,16 @@ function gitlabBlock(profile, runner) {
|
|
|
71
88
|
' - if: $CI_PIPELINE_SOURCE == "merge_request_event"',
|
|
72
89
|
" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH",
|
|
73
90
|
" before_script:",
|
|
74
|
-
" - apk add --no-cache curl",
|
|
91
|
+
" - apk add --no-cache curl git",
|
|
75
92
|
` - curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR=/usr/local/bin DAGGER_VERSION=${DAGGER_VERSION} sh`,
|
|
76
93
|
" script:",
|
|
77
|
-
|
|
94
|
+
" - |",
|
|
95
|
+
' if [ -n "${SDD_CATALOGUE_PUSH_TOKEN:-}" ] && [ "${CI_MERGE_REQUEST_SOURCE_PROJECT_ID:-$CI_PROJECT_ID}" = "$CI_PROJECT_ID" ]; then',
|
|
96
|
+
' SDD_REBUILD_BRANCH="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-$CI_COMMIT_BRANCH}" \\',
|
|
97
|
+
' SDD_REBUILD_PUSH_URL="${CI_SERVER_PROTOCOL}://oauth2:${SDD_CATALOGUE_PUSH_TOKEN}@${CI_SERVER_URL#*://}/${CI_PROJECT_PATH}.git" \\',
|
|
98
|
+
` sh ${REBUILD_SCRIPT}`,
|
|
99
|
+
" fi",
|
|
100
|
+
` - ${MODULE_CALL} ${REBUILD_FLAG}`,
|
|
78
101
|
HASH.end,
|
|
79
102
|
].join("\n");
|
|
80
103
|
}
|
|
@@ -92,7 +115,18 @@ function jenkinsBlock(profile, runner) {
|
|
|
92
115
|
" stage('Method Validation') {",
|
|
93
116
|
" steps {",
|
|
94
117
|
` sh 'curl -fsSL https://dl.dagger.io/dagger/install.sh | BIN_DIR="$WORKSPACE@tmp/dagger" DAGGER_VERSION=${DAGGER_VERSION} sh'`,
|
|
95
|
-
|
|
118
|
+
" script {",
|
|
119
|
+
" if (env.SDD_CATALOGUE_PUSH_CREDENTIAL && !env.CHANGE_FORK) {",
|
|
120
|
+
" withCredentials([usernamePassword(credentialsId: env.SDD_CATALOGUE_PUSH_CREDENTIAL, usernameVariable: 'SDD_PUSH_USER', passwordVariable: 'SDD_PUSH_TOKEN')]) {",
|
|
121
|
+
" sh '''",
|
|
122
|
+
' export SDD_REBUILD_BRANCH="${CHANGE_BRANCH:-${BRANCH_NAME:-${GIT_BRANCH#origin/}}}"',
|
|
123
|
+
' export SDD_REBUILD_PUSH_URL="$(git config --get remote.origin.url | sed "s#^https://#https://${SDD_PUSH_USER}:${SDD_PUSH_TOKEN}@#")"',
|
|
124
|
+
` PATH="$WORKSPACE@tmp/dagger:$PATH" sh ${REBUILD_SCRIPT}`,
|
|
125
|
+
" '''",
|
|
126
|
+
" }",
|
|
127
|
+
" }",
|
|
128
|
+
" }",
|
|
129
|
+
` sh 'PATH="$WORKSPACE@tmp/dagger:$PATH" ${MODULE_CALL} ${REBUILD_FLAG}'`,
|
|
96
130
|
" }",
|
|
97
131
|
" }",
|
|
98
132
|
" }",
|
|
@@ -160,7 +194,12 @@ export async function syncCiBinding(o) {
|
|
|
160
194
|
onDisk.push([rel, await tryRead(join(o.targetRepo, MODULE_DIR, rel))]);
|
|
161
195
|
const present = onDisk.some(([, c]) => c !== null);
|
|
162
196
|
const matches = rendered.every(([rel, c], i) => onDisk[i][0] === rel && onDisk[i][1] === c);
|
|
163
|
-
|
|
197
|
+
// Pristine when the files on disk hash to the lock's digest. The render may list a file the last sync
|
|
198
|
+
// never wrote (the generator gained one): it is absent, not edited, so the digest over the files present
|
|
199
|
+
// decides. A file the adopter deleted still changes that digest, so it is still drift.
|
|
200
|
+
const lockSha = o.prevLock?.ciModuleSha256;
|
|
201
|
+
const modulePristine = lockSha !== undefined &&
|
|
202
|
+
(moduleSha256(onDisk) === lockSha || moduleSha256(onDisk.filter(([, c]) => c !== null)) === lockSha);
|
|
164
203
|
if (present && !matches && modulePristine) {
|
|
165
204
|
log.push(`${MODULE_DIR}/: unmodified since the last sync (digest matches the lock); re-rendering it for this sdd-cli.`);
|
|
166
205
|
}
|
|
@@ -22,6 +22,16 @@ export const MODULE_DIR = ".dagger/method-validation";
|
|
|
22
22
|
export const DAGGER_VERSION = "0.21.10";
|
|
23
23
|
/** The command every host wrapper runs. */
|
|
24
24
|
export const MODULE_CALL = `dagger call -m ${MODULE_DIR} method-validation --source .`;
|
|
25
|
+
/** The pipeline catalogue rebuild (M-ADR 0220 §4.4): a method-managed script in the module, run by every host wrapper. */
|
|
26
|
+
export const REBUILD_SCRIPT = `${MODULE_DIR}/catalogue-rebuild.sh`;
|
|
27
|
+
/**
|
|
28
|
+
* Left in the git directory by REBUILD_SCRIPT when the rebuild ran, whether or not it pushed. Every
|
|
29
|
+
* wrapper turns it into the module's --catalogue-rebuild, so the freshness step gates exactly where
|
|
30
|
+
* the rebuild ran (sdm-jts.9's ruling) and the wrapper holds no condition of its own.
|
|
31
|
+
*/
|
|
32
|
+
export const REBUILD_RAN_MARKER = "sdd-catalogue-rebuild-ran";
|
|
33
|
+
/** What every wrapper appends to MODULE_CALL: --catalogue-rebuild when, and only when, the rebuild ran. */
|
|
34
|
+
export const REBUILD_FLAG = `$(test -f "$(git rev-parse --git-dir)/${REBUILD_RAN_MARKER}" && echo --catalogue-rebuild)`;
|
|
25
35
|
const VERIFY_PIN = "@@VERIFY_CLI_VERSION@@";
|
|
26
36
|
// The module's SDK import, substituted at render: written literally, the persona builds' import-closure
|
|
27
37
|
// scan would read this text as an import of the CLI's own and refuse a dependency it does not declare.
|
|
@@ -39,7 +49,8 @@ const INDEX_TS = String.raw `/**
|
|
|
39
49
|
*
|
|
40
50
|
* The steps are the method's job plan, read at run time from "sdd-verify validate --list". Every
|
|
41
51
|
* step runs even after an earlier one fails, so one red step never hides the verdicts after it. The
|
|
42
|
-
* job fails only when a non-advisory step fails; the catalogue freshness step is advisory
|
|
52
|
+
* job fails only when a non-advisory step fails; the catalogue freshness step is advisory unless the
|
|
53
|
+
* pipeline rebuilt the catalogues on this run (catalogue-rebuild.sh, M-ADR 0220 §4.4).
|
|
43
54
|
*/
|
|
44
55
|
import { dag, Container, Directory, File, object, func, ReturnType } from ${DAGGER_SDK}
|
|
45
56
|
|
|
@@ -102,12 +113,14 @@ export class MethodValidation {
|
|
|
102
113
|
maintainVersion: string = "${MAINTAIN_PIN}",
|
|
103
114
|
verifyTarball?: File,
|
|
104
115
|
maintainTarball?: File,
|
|
116
|
+
catalogueRebuild: boolean = false,
|
|
105
117
|
): Promise<string> {
|
|
106
118
|
const base = this.toolchain(verifyVersion, maintainVersion, verifyTarball, maintainTarball)
|
|
107
119
|
.withMountedDirectory("/src", source)
|
|
108
120
|
.withWorkdir("/src")
|
|
109
121
|
const versions = (await base.withExec(["sh", "-c", "sdd-verify --version; sdd-maintain --version"]).stdout()).split("\n")
|
|
110
|
-
const
|
|
122
|
+
const listArgs = ["sdd-verify", "validate", "--list"]
|
|
123
|
+
const plan = JSON.parse(await base.withExec(catalogueRebuild ? [...listArgs, "--catalogue-rebuild"] : listArgs).stdout()) as {
|
|
111
124
|
profile: string
|
|
112
125
|
steps: PlanStep[]
|
|
113
126
|
}
|
|
@@ -130,8 +143,66 @@ export class MethodValidation {
|
|
|
130
143
|
if (failed > 0) throw new Error(report)
|
|
131
144
|
return report
|
|
132
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Rebuild the committed catalogues from the specifications (M-ADR 0220 §4.4) and return the whole
|
|
149
|
+
* new catalogues directory: every file sdd-maintain catalogue build produces, plus every file
|
|
150
|
+
* already there except a .csv the build no longer produces, the freshness check's own rule. The
|
|
151
|
+
* host wrapper's catalogue-rebuild.sh exports it over catalogues/ and pushes only if it changed.
|
|
152
|
+
*/
|
|
153
|
+
@func()
|
|
154
|
+
catalogueRebuild(source: Directory, maintainVersion: string = "${MAINTAIN_PIN}", maintainTarball?: File): Directory {
|
|
155
|
+
return this.toolchain(undefined, maintainVersion, undefined, maintainTarball)
|
|
156
|
+
.withMountedDirectory("/src", source)
|
|
157
|
+
.withWorkdir("/src")
|
|
158
|
+
.withExec(["sdd-maintain", "catalogue", "build", "--sdd-path", ".", "--out", "/rebuilt"])
|
|
159
|
+
.withExec([
|
|
160
|
+
"sh",
|
|
161
|
+
"-c",
|
|
162
|
+
"mkdir -p /next && if [ -d catalogues ]; then cp -a catalogues/. /next/; fi && " +
|
|
163
|
+
"for f in /next/*.csv; do [ -e \"$f\" ] || continue; [ -e \"/rebuilt/$(basename \"$f\")\" ] || rm \"$f\"; done && " +
|
|
164
|
+
"cp -a /rebuilt/. /next/",
|
|
165
|
+
])
|
|
166
|
+
.directory("/next")
|
|
167
|
+
}
|
|
133
168
|
}
|
|
134
169
|
`;
|
|
170
|
+
// Run by every host wrapper, and only where the adopter has provided the push credential. Plain
|
|
171
|
+
// strings, not a template: the shell's own \${...} must reach the file as written.
|
|
172
|
+
const REBUILD_SH = [
|
|
173
|
+
"#!/bin/sh",
|
|
174
|
+
"# Pipeline catalogue rebuild (M-ADR 0220 §4.4). Written by sdd-cli sync and method-managed, like the",
|
|
175
|
+
"# rest of this module. The host wrapper runs it only where the adopter has provided the push",
|
|
176
|
+
"# credential, and sets:",
|
|
177
|
+
"# SDD_REBUILD_BRANCH the branch this pipeline builds, which the rebuild is pushed to",
|
|
178
|
+
"# SDD_REBUILD_PUSH_URL the repository's URL, carrying the credential",
|
|
179
|
+
"#",
|
|
180
|
+
"# A rebuild that changes nothing pushes nothing, so the push cannot loop: the pipeline it starts",
|
|
181
|
+
"# rebuilds the same catalogues, finds no change, and runs Method Validation.",
|
|
182
|
+
"set -eu",
|
|
183
|
+
': "${SDD_REBUILD_BRANCH:?the host wrapper sets it}" "${SDD_REBUILD_PUSH_URL:?the host wrapper sets it}"',
|
|
184
|
+
"",
|
|
185
|
+
"# A merge the pipeline made (a merged-results or merge-with-target build) is not the branch's head,",
|
|
186
|
+
"# and pushing it would put that merge on the branch. The branch's own pipeline rebuilds instead.",
|
|
187
|
+
'if [ "$(git rev-list --parents -n 1 HEAD | wc -w)" -gt 2 ]; then',
|
|
188
|
+
' echo "Catalogue rebuild: skipped, HEAD is a merge commit this pipeline made; the branch pipeline rebuilds."',
|
|
189
|
+
" exit 0",
|
|
190
|
+
"fi",
|
|
191
|
+
"",
|
|
192
|
+
"dagger call -m " + MODULE_DIR + " catalogue-rebuild --source . export --path catalogues --wipe >/dev/null",
|
|
193
|
+
'touch "$(git rev-parse --git-dir)/' + REBUILD_RAN_MARKER + '"',
|
|
194
|
+
"git add -A catalogues",
|
|
195
|
+
"if git diff --cached --quiet; then",
|
|
196
|
+
' echo "Catalogue rebuild: the committed catalogues already match the specifications; nothing to push."',
|
|
197
|
+
" exit 0",
|
|
198
|
+
"fi",
|
|
199
|
+
"git diff --cached --stat",
|
|
200
|
+
'git -c user.name="sdd-method catalogue rebuild" -c user.email="catalogue-rebuild@sdd-method.invalid" \\',
|
|
201
|
+
' commit -q -m "Rebuild catalogues from the specifications (Method Validation, M-ADR 0220 §4.4)"',
|
|
202
|
+
'git push -q "$SDD_REBUILD_PUSH_URL" "HEAD:refs/heads/$SDD_REBUILD_BRANCH"',
|
|
203
|
+
'echo "Catalogue rebuild: pushed $(git rev-parse --short HEAD) to $SDD_REBUILD_BRANCH; Method Validation runs on it there too."',
|
|
204
|
+
"",
|
|
205
|
+
].join("\n");
|
|
135
206
|
const DAGGER_JSON = `{
|
|
136
207
|
"name": "method-validation",
|
|
137
208
|
"engineVersion": "v${DAGGER_VERSION}",
|
|
@@ -183,6 +254,7 @@ const GITIGNORE = `/sdk
|
|
|
183
254
|
export function renderModuleFiles(pins = { verify: getVerifyCliVersion(), maintain: getMaintainCliVersion() }) {
|
|
184
255
|
return [
|
|
185
256
|
[".gitignore", GITIGNORE],
|
|
257
|
+
["catalogue-rebuild.sh", REBUILD_SH],
|
|
186
258
|
["dagger.json", DAGGER_JSON],
|
|
187
259
|
["package.json", PACKAGE_JSON],
|
|
188
260
|
["src/index.ts", INDEX_TS.split(VERIFY_PIN).join(pins.verify).split(MAINTAIN_PIN).join(pins.maintain).split(DAGGER_SDK).join('"@dagger.io' + '/dagger"')],
|
|
@@ -24,40 +24,62 @@ export function buildMethodGuardPreToolUse() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
const SESSION_START_VALIDATE_ARGS = ' validate --target-repo "${CLAUDE_PROJECT_DIR:-$(pwd)}" --validator adr-structure 2>&1 | head -20';
|
|
27
|
-
|
|
27
|
+
/** The verify persona's npm package, unless the lock names a rescoped one (sync --cli-package). */
|
|
28
|
+
export const DEFAULT_VERIFY_CLI_PACKAGE = "@sdd-method/verify-cli";
|
|
29
|
+
/** One npm package name, scoped or not, the shape `npm pack` and `npx` accept. */
|
|
30
|
+
export const NPM_PACKAGE_NAME = /^(@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
31
|
+
export const CLI_SOURCES = ["registry", "installed"];
|
|
32
|
+
export const DEFAULT_CLI_SOURCE = { source: "registry" };
|
|
33
|
+
function registryLauncher(version, pkg) {
|
|
34
|
+
return `npx --yes ${pkg}@${version}${SESSION_START_VALIDATE_ARGS}`;
|
|
35
|
+
}
|
|
36
|
+
function installedLauncher(version) {
|
|
37
|
+
return (`v=$(sdd-verify --version 2>/dev/null); [ "$v" = "${version}" ] || ` +
|
|
38
|
+
`echo "[method] sdd-verify \${v:-is not installed}; this SDD pins verify-cli ${version}: ` +
|
|
39
|
+
`pull your CLI release repository and run ./install.sh ${version}"; ` +
|
|
40
|
+
`if [ -n "$v" ]; then sdd-verify${SESSION_START_VALIDATE_ARGS}; fi`);
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* The session-start check runs the VERIFY persona's CLI (M-ADR 0218, 0220), not sdd-cli, which is
|
|
30
|
-
* the administrator's: validating is routine author work. It runs
|
|
31
|
-
*
|
|
32
|
-
*
|
|
44
|
+
* the administrator's: validating is routine author work. It runs at the exact verify-cli version
|
|
45
|
+
* this sdd-cli releases with, the version the managed CI workflow installs, so a local pass comes
|
|
46
|
+
* from the same verifier CI uses.
|
|
33
47
|
*/
|
|
34
|
-
export function sessionStartValidateCommand(verifyVersion = getVerifyCliVersion()) {
|
|
35
|
-
|
|
48
|
+
export function sessionStartValidateCommand(verifyVersion = getVerifyCliVersion(), cli = DEFAULT_CLI_SOURCE) {
|
|
49
|
+
const body = cli.source === "installed"
|
|
50
|
+
? installedLauncher(verifyVersion)
|
|
51
|
+
: registryLauncher(verifyVersion, cli.package ?? DEFAULT_VERIFY_CLI_PACKAGE);
|
|
52
|
+
return withSentinel(body);
|
|
36
53
|
}
|
|
54
|
+
const REGISTRY_FORM = /^npx --yes ((?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*)@(\S+) validate /;
|
|
55
|
+
const INSTALLED_FORM = /^v=\$\(sdd-verify --version 2>\/dev\/null\); \[ "\$v" = "([^"]+)" \]/;
|
|
37
56
|
/**
|
|
38
|
-
* Whether a session-start command is one this tool rendered, at any version
|
|
39
|
-
* wrote until the hook moved to verify-cli,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the adopter owns.
|
|
57
|
+
* Whether a session-start command is one this tool rendered, at any exact version and from either
|
|
58
|
+
* source: the sdd-cli form it wrote until the hook moved to verify-cli, the npx form under any
|
|
59
|
+
* package name, or the installed form. Sync replaces its own earlier render without counting it as
|
|
60
|
+
* drift, so a release that moves the pin, or a switch of source, needs no --allow-overwrite (the
|
|
61
|
+
* flag that also rewrites keys the adopter owns). The candidate is re-rendered from the version it
|
|
62
|
+
* names and must match exactly, so anything edited under the sentinel is still drift.
|
|
43
63
|
*/
|
|
44
64
|
export function isOwnSessionStartRender(command) {
|
|
45
65
|
const body = command.replace(METHOD_MANAGED_SENTINEL, "").trimEnd();
|
|
46
|
-
if (
|
|
47
|
-
return false;
|
|
48
|
-
const launcher = body.slice(0, body.length - SESSION_START_VALIDATE_ARGS.length);
|
|
49
|
-
if (launcher === "sdd-cli")
|
|
66
|
+
if (body === `sdd-cli${SESSION_START_VALIDATE_ARGS}`)
|
|
50
67
|
return true;
|
|
51
|
-
const npx =
|
|
52
|
-
|
|
68
|
+
const npx = REGISTRY_FORM.exec(body);
|
|
69
|
+
if (npx && EXACT_VERSION.test(npx[2]))
|
|
70
|
+
return body === registryLauncher(npx[2], npx[1]);
|
|
71
|
+
const installed = INSTALLED_FORM.exec(body);
|
|
72
|
+
if (installed && EXACT_VERSION.test(installed[1]))
|
|
73
|
+
return body === installedLauncher(installed[1]);
|
|
74
|
+
return false;
|
|
53
75
|
}
|
|
54
|
-
export function buildSessionStartValidate() {
|
|
76
|
+
export function buildSessionStartValidate(cli = DEFAULT_CLI_SOURCE) {
|
|
55
77
|
return {
|
|
56
78
|
matcher: "startup",
|
|
57
79
|
hooks: [
|
|
58
80
|
{
|
|
59
81
|
type: "command",
|
|
60
|
-
command: sessionStartValidateCommand(),
|
|
82
|
+
command: sessionStartValidateCommand(getVerifyCliVersion(), cli),
|
|
61
83
|
},
|
|
62
84
|
],
|
|
63
85
|
};
|
|
@@ -66,10 +88,10 @@ export function buildSessionStartValidate() {
|
|
|
66
88
|
* Full method-managed hooks block. When extending, add a new event
|
|
67
89
|
* name to the returned object and a corresponding builder above.
|
|
68
90
|
*/
|
|
69
|
-
export function buildMethodManagedHooks() {
|
|
91
|
+
export function buildMethodManagedHooks(cli = DEFAULT_CLI_SOURCE) {
|
|
70
92
|
const hooks = {
|
|
71
93
|
PreToolUse: [buildMethodGuardPreToolUse()],
|
|
72
|
-
SessionStart: [buildSessionStartValidate()],
|
|
94
|
+
SessionStart: [buildSessionStartValidate(cli)],
|
|
73
95
|
};
|
|
74
96
|
return hooks;
|
|
75
97
|
}
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
* when the repo runs the claude-code harness (the default).
|
|
10
10
|
*/
|
|
11
11
|
export { METHOD_MANAGED_SENTINEL, isMethodManagedMatcher } from "./types.js";
|
|
12
|
-
export { buildMethodManagedHooks, isOwnSessionStartRender, sessionStartValidateCommand, } from "./entries.js";
|
|
12
|
+
export { buildMethodManagedHooks, CLI_SOURCES, DEFAULT_CLI_SOURCE, DEFAULT_VERIFY_CLI_PACKAGE, isOwnSessionStartRender, NPM_PACKAGE_NAME, sessionStartValidateCommand, } from "./entries.js";
|
|
13
13
|
export { detectHooksDrift, normaliseOwnHookRenders, upsertMethodManagedHooks, } from "./merge.js";
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* block-marker pair so the sync path can preserve adopter-authored
|
|
9
9
|
* content outside the block.
|
|
10
10
|
*/
|
|
11
|
-
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR } from "../ci-binding/module.js";
|
|
11
|
+
import { DAGGER_VERSION, MODULE_CALL, MODULE_DIR, REBUILD_FLAG, REBUILD_SCRIPT } from "../ci-binding/module.js";
|
|
12
12
|
export const WORKFLOW_REL_PATH = ".github/workflows/method-validation.yml";
|
|
13
13
|
export const BLOCK_BEGIN = "# method-managed: begin";
|
|
14
14
|
export const BLOCK_END = "# method-managed: end";
|
|
@@ -56,6 +56,13 @@ function buildMethodManagedBlock(profile, runner) {
|
|
|
56
56
|
"# released with the sdd-cli that last synced this repository. The catalogue",
|
|
57
57
|
"# freshness step (@sdd-method/maintain-cli, pinned the same way) is advisory.",
|
|
58
58
|
"#",
|
|
59
|
+
"# Catalogue rebuild (M-ADR 0220 §4.4), off until you configure it: set the",
|
|
60
|
+
"# repository variable SDD_CATALOGUE_APP_ID and the secret",
|
|
61
|
+
"# SDD_CATALOGUE_APP_PRIVATE_KEY for a GitHub App that can write contents, and",
|
|
62
|
+
"# the job rebuilds the catalogues and pushes them to the branch it is building",
|
|
63
|
+
"# (a pull request's own branch, never a fork's); the freshness step then gates.",
|
|
64
|
+
"# An App token, because pushes made with the default token start no workflow.",
|
|
65
|
+
"#",
|
|
59
66
|
"# Run it locally (needs a container engine):",
|
|
60
67
|
`# ${MODULE_CALL}`,
|
|
61
68
|
"",
|
|
@@ -74,11 +81,28 @@ function buildMethodManagedBlock(profile, runner) {
|
|
|
74
81
|
" name: Method Validation",
|
|
75
82
|
` runs-on: ${runsOn(runner)}`,
|
|
76
83
|
" steps:",
|
|
84
|
+
" - name: Catalogue rebuild token",
|
|
85
|
+
" id: catalogue-token",
|
|
86
|
+
" if: vars.SDD_CATALOGUE_APP_ID != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)",
|
|
87
|
+
" uses: actions/create-github-app-token@v1",
|
|
88
|
+
" with:",
|
|
89
|
+
" app-id: ${{ vars.SDD_CATALOGUE_APP_ID }}",
|
|
90
|
+
" private-key: ${{ secrets.SDD_CATALOGUE_APP_PRIVATE_KEY }}",
|
|
77
91
|
" - uses: actions/checkout@v4",
|
|
92
|
+
" with:",
|
|
93
|
+
// The branch itself when rebuilding, so the rebuild lands on it; otherwise the event's own ref.
|
|
94
|
+
" ref: ${{ steps.catalogue-token.outputs.token != '' && (github.head_ref || github.ref_name) || '' }}",
|
|
95
|
+
" persist-credentials: false",
|
|
78
96
|
" - name: Install Dagger",
|
|
79
97
|
` 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"`,
|
|
98
|
+
" - name: Rebuild catalogues",
|
|
99
|
+
" if: steps.catalogue-token.outputs.token != ''",
|
|
100
|
+
" env:",
|
|
101
|
+
" SDD_REBUILD_TOKEN: ${{ steps.catalogue-token.outputs.token }}",
|
|
102
|
+
" SDD_REBUILD_BRANCH: ${{ github.head_ref || github.ref_name }}",
|
|
103
|
+
` run: SDD_REBUILD_PUSH_URL="https://x-access-token:$SDD_REBUILD_TOKEN@\${GITHUB_SERVER_URL#https://}/$GITHUB_REPOSITORY.git" sh ${REBUILD_SCRIPT}`,
|
|
80
104
|
" - name: Method Validation",
|
|
81
|
-
` run: ${MODULE_CALL}`,
|
|
105
|
+
` run: ${MODULE_CALL} ${REBUILD_FLAG}`,
|
|
82
106
|
BLOCK_END,
|
|
83
107
|
].join("\n");
|
|
84
108
|
}
|
package/dist/lib/lock-file.js
CHANGED
|
@@ -66,6 +66,8 @@ export function serialiseLockFile(lock) {
|
|
|
66
66
|
...(lock.ciRunner !== undefined ? [`ci_runner: "${lock.ciRunner}"`] : []),
|
|
67
67
|
...(lock.ciWorkflowBlockSha256 !== undefined ? [`ci_workflow_block_sha256: "${lock.ciWorkflowBlockSha256}"`] : []),
|
|
68
68
|
...(lock.ciModuleSha256 !== undefined ? [`ci_module_sha256: "${lock.ciModuleSha256}"`] : []),
|
|
69
|
+
...(lock.cliSource !== undefined ? [`cli_source: ${lock.cliSource}`] : []),
|
|
70
|
+
...(lock.cliPackage !== undefined ? [`cli_package: "${lock.cliPackage}"`] : []),
|
|
69
71
|
"bundles:",
|
|
70
72
|
];
|
|
71
73
|
for (const b of lock.bundles) {
|
|
@@ -117,6 +119,8 @@ function parseVersioned(raw) {
|
|
|
117
119
|
let ciBinding;
|
|
118
120
|
let ciRunner;
|
|
119
121
|
let ciModuleSha256;
|
|
122
|
+
let cliSource;
|
|
123
|
+
let cliPackage;
|
|
120
124
|
const bundles = [];
|
|
121
125
|
let inBundles = false;
|
|
122
126
|
let inPaths = false;
|
|
@@ -186,6 +190,10 @@ function parseVersioned(raw) {
|
|
|
186
190
|
ciRunner = stripQuotes(value);
|
|
187
191
|
else if (key === "ci_module_sha256")
|
|
188
192
|
ciModuleSha256 = stripQuotes(value);
|
|
193
|
+
else if (key === "cli_source")
|
|
194
|
+
cliSource = stripQuotes(value);
|
|
195
|
+
else if (key === "cli_package")
|
|
196
|
+
cliPackage = stripQuotes(value);
|
|
189
197
|
}
|
|
190
198
|
continue;
|
|
191
199
|
}
|
|
@@ -313,6 +321,8 @@ function parseVersioned(raw) {
|
|
|
313
321
|
...(ciRunner !== undefined ? { ciRunner } : {}),
|
|
314
322
|
...(ciWorkflowBlockSha256 !== undefined ? { ciWorkflowBlockSha256 } : {}),
|
|
315
323
|
...(ciModuleSha256 !== undefined ? { ciModuleSha256 } : {}),
|
|
324
|
+
...(cliSource !== undefined ? { cliSource } : {}),
|
|
325
|
+
...(cliPackage !== undefined ? { cliPackage } : {}),
|
|
316
326
|
};
|
|
317
327
|
}
|
|
318
328
|
function migrateV1(raw, legacyBundleName) {
|
package/dist/lib/pack/builder.js
CHANGED
|
@@ -69,7 +69,9 @@ export async function buildPack(opts) {
|
|
|
69
69
|
// 4. Checksums (over the published payload)
|
|
70
70
|
const checksums = await computeChecksums(payload, publishedPaths);
|
|
71
71
|
// 5. Manifest + managed_paths + checksums files
|
|
72
|
-
const gitInfo =
|
|
72
|
+
const gitInfo = opts.provenance
|
|
73
|
+
? { commit: opts.provenance.commit, branch: opts.provenance.branch }
|
|
74
|
+
: await readGitInfo(opts.producerRepo);
|
|
73
75
|
await writeFile(join(root, "MANAGED_PATHS.txt"), publishedPaths.join("\n") + "\n");
|
|
74
76
|
await writeFile(join(root, "CHECKSUMS.sha256"), checksums);
|
|
75
77
|
await writeFile(join(root, "BUNDLE_MANIFEST.yaml"), renderManifest({
|
|
@@ -79,12 +81,13 @@ export async function buildPack(opts) {
|
|
|
79
81
|
managedPathCount: publishedPaths.length,
|
|
80
82
|
sourceCommit: gitInfo.commit,
|
|
81
83
|
sourceBranch: gitInfo.branch,
|
|
84
|
+
...(opts.provenance && { sourceRepo: opts.provenance.repo, generatedAt: opts.provenance.generatedAt }),
|
|
82
85
|
}));
|
|
83
86
|
// 6. tar -czf
|
|
84
87
|
await mkdir(outDir, { recursive: true });
|
|
85
88
|
const tarballName = `${rootName}.tar.gz`;
|
|
86
89
|
const tarballPath = join(outDir, tarballName);
|
|
87
|
-
await
|
|
90
|
+
await writeArchive(tarballPath, stage, rootName, await archiveEpoch(opts));
|
|
88
91
|
// Also emit a sidecar checksum file for the tarball itself
|
|
89
92
|
const tarballHash = await sha256File(tarballPath);
|
|
90
93
|
const sidecar = `${tarballHash} ${tarballName}\n`;
|
|
@@ -210,8 +213,8 @@ function renderManifest(ctx) {
|
|
|
210
213
|
// sdd-method/orchestration/templates/bundle/BUNDLE_MANIFEST.yaml.hbs.
|
|
211
214
|
// We don't go through a Handlebars engine because that would be the
|
|
212
215
|
// only template-rendered file in sdd-cli; everything else is direct.
|
|
213
|
-
const generatedAt = new Date().toISOString();
|
|
214
|
-
const sourceRepo = deriveSourceRepoName(ctx.producerRepo);
|
|
216
|
+
const generatedAt = ctx.generatedAt ?? new Date().toISOString();
|
|
217
|
+
const sourceRepo = ctx.sourceRepo ?? deriveSourceRepoName(ctx.producerRepo);
|
|
215
218
|
const lines = [];
|
|
216
219
|
lines.push(`bundle_name: "${ctx.packName}"`);
|
|
217
220
|
lines.push(`bundle_version: "v${ctx.seed.version}"`);
|
|
@@ -256,6 +259,55 @@ function renderManifest(ctx) {
|
|
|
256
259
|
}
|
|
257
260
|
return lines.join("\n") + "\n";
|
|
258
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Member file times: the release record's time, else SOURCE_DATE_EPOCH, else the producer's HEAD
|
|
264
|
+
* commit time, else now (not a git tree, no record). Two cuts of one commit then agree.
|
|
265
|
+
*/
|
|
266
|
+
async function archiveEpoch(opts) {
|
|
267
|
+
if (opts.provenance)
|
|
268
|
+
return Math.floor(Date.parse(opts.provenance.generatedAt) / 1000);
|
|
269
|
+
const env = process.env.SOURCE_DATE_EPOCH;
|
|
270
|
+
if (env && /^\d+$/.test(env))
|
|
271
|
+
return Number(env);
|
|
272
|
+
try {
|
|
273
|
+
const { stdout } = await execFile("git", ["-C", opts.producerRepo, "log", "-1", "--format=%ct", "HEAD"]);
|
|
274
|
+
if (/^\d+$/.test(stdout.trim()))
|
|
275
|
+
return Number(stdout.trim());
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// Not a git working tree.
|
|
279
|
+
}
|
|
280
|
+
return Math.floor(Date.now() / 1000);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Write the tarball deterministically with GNU tar: sorted members, owner and group zeroed, modes
|
|
284
|
+
* normalised, fixed file times; gzip -n, so the header carries no name or time. Plain `tar -czf` stamped all of these from the machine, so a rebuild never matched.
|
|
285
|
+
* Without GNU tar (e.g. macOS's bsdtar) the archive is written the old way and is not reproducible.
|
|
286
|
+
*/
|
|
287
|
+
async function writeArchive(tarballPath, stage, rootName, epoch) {
|
|
288
|
+
let gnu = false;
|
|
289
|
+
try {
|
|
290
|
+
gnu = (await execFile("tar", ["--version"])).stdout.includes("GNU tar");
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
gnu = false;
|
|
294
|
+
}
|
|
295
|
+
if (!gnu) {
|
|
296
|
+
await execFile("tar", ["-czf", tarballPath, "-C", stage, rootName]);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const tarPath = `${tarballPath}.tar`;
|
|
300
|
+
try {
|
|
301
|
+
await execFile("tar", ["--sort=name", "--format=gnu", `--mtime=@${epoch}`, "--owner=0", "--group=0", "--numeric-owner",
|
|
302
|
+
"--mode=u+rwX,go+rX,go-w", "-cf", tarPath, "-C", stage, rootName], { env: { ...process.env, LC_ALL: "C" } });
|
|
303
|
+
// GNU gzip, not Node's zlib: Node bundles a zlib fork whose output can depend on the CPU, and
|
|
304
|
+
// the method's own bundle builder compresses with gzip too.
|
|
305
|
+
await execFile("sh", ["-c", 'gzip -n -9 -c "$1" > "$2"', "sh", tarPath, tarballPath]);
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
await rm(tarPath, { force: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
259
311
|
/**
|
|
260
312
|
* Read git provenance from the producer working tree. Mirrors the
|
|
261
313
|
* behaviour of sdd-method/orchestration/scripts/build-method-baseline-
|
package/dist/lib/pack/index.js
CHANGED
|
@@ -53,6 +53,7 @@ export async function runPackBuild(opts, stdout = (s) => process.stdout.write(s)
|
|
|
53
53
|
}),
|
|
54
54
|
...(publishTransform && { publishTransform: true }),
|
|
55
55
|
...(opts.originPrefix === true && { mAdrPrefix: true }),
|
|
56
|
+
...(opts.provenance && { provenance: opts.provenance }),
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
catch (err) {
|
package/dist/lib/sync/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { cacheBundle, ensureCacheGitignored, splitConsumptionModes, } from "./bu
|
|
|
11
11
|
import { PROTECTED_PREFIXES, allPristine, classifyConflicts, computeRemovals, detectShadowCollisions, digestManagedPaths, findConflicts, findMissingPayloadFiles, findProtectedPaths, } from "./diff.js";
|
|
12
12
|
import { applyAdrRenames, findStrandedCitations, planAdrTransitions, } from "./adr-transitions.js";
|
|
13
13
|
import { syncMethodManagedSettings } from "./settings-merge.js";
|
|
14
|
+
import { CLI_SOURCES, NPM_PACKAGE_NAME } from "../hooks-seed/index.js";
|
|
14
15
|
import { syncBriefingFiles } from "./briefing-merge.js";
|
|
15
16
|
import { DEFAULT_AGENT_HARNESS } from "../agent-harness.js";
|
|
16
17
|
import { BLOCK_BEGIN, BLOCK_END } from "../init/ci-workflow.js";
|
|
@@ -102,6 +103,24 @@ export async function runSync(opts, stdout = (line) => process.stdout.write(line
|
|
|
102
103
|
return { exitCode: 1 };
|
|
103
104
|
}
|
|
104
105
|
const ciRunner = opts.ciRunner ?? lockRunner;
|
|
106
|
+
const lockSource = prevLock?.cliSource;
|
|
107
|
+
if (lockSource !== undefined && !CLI_SOURCES.includes(lockSource)) {
|
|
108
|
+
stdout(fail(`method-baseline.lock names cli_source '${lockSource}', which is not one of ${CLI_SOURCES.join(", ")}. Re-run with --cli-source <source> to choose one.`));
|
|
109
|
+
return { exitCode: 1 };
|
|
110
|
+
}
|
|
111
|
+
const lockPackage = prevLock?.cliPackage;
|
|
112
|
+
if (lockPackage !== undefined && !NPM_PACKAGE_NAME.test(lockPackage)) {
|
|
113
|
+
stdout(fail(`method-baseline.lock names cli_package '${lockPackage}', which is not an npm package name. Re-run with --cli-package <name> or --cli-package default.`));
|
|
114
|
+
return { exitCode: 1 };
|
|
115
|
+
}
|
|
116
|
+
const source = opts.cliSource ?? lockSource ?? "registry";
|
|
117
|
+
if (source === "installed" && typeof opts.cliPackage === "string") {
|
|
118
|
+
stdout(fail("--cli-package names an npm package for the registry source; the installed source runs sdd-verify from PATH. Drop --cli-package or choose --cli-source registry."));
|
|
119
|
+
return { exitCode: 1 };
|
|
120
|
+
}
|
|
121
|
+
// The installed source has no package; switching to it drops a recorded name.
|
|
122
|
+
const cliPackage = source === "installed" ? undefined : opts.cliPackage === undefined ? lockPackage : (opts.cliPackage ?? undefined);
|
|
123
|
+
const cliSource = source === "installed" ? { source } : { source, ...(cliPackage ? { package: cliPackage } : {}) };
|
|
105
124
|
const classification = await classifyConflicts(conflicts, opts.targetRepo, prevEntry?.pathDigests);
|
|
106
125
|
if (conflicts.length > 0) {
|
|
107
126
|
reportClassification(stdout, conflicts.length, classification, prevEntry?.bundleVersion);
|
|
@@ -336,6 +355,7 @@ export async function runSync(opts, stdout = (line) => process.stdout.write(line
|
|
|
336
355
|
targetRepo: opts.targetRepo,
|
|
337
356
|
allowOverwrite: false,
|
|
338
357
|
checkOnly: true,
|
|
358
|
+
cliSource,
|
|
339
359
|
...(opts.agentHarness ? { agentHarness: opts.agentHarness } : {}),
|
|
340
360
|
});
|
|
341
361
|
if (probe.refused) {
|
|
@@ -500,6 +520,7 @@ export async function runSync(opts, stdout = (line) => process.stdout.write(line
|
|
|
500
520
|
const settingsResult = await syncMethodManagedSettings({
|
|
501
521
|
targetRepo: opts.targetRepo,
|
|
502
522
|
allowOverwrite: opts.allowOverwrite || migrateFrom !== null,
|
|
523
|
+
cliSource,
|
|
503
524
|
...(opts.agentHarness ? { agentHarness: opts.agentHarness } : {}),
|
|
504
525
|
});
|
|
505
526
|
if (settingsResult.refused) {
|
|
@@ -552,15 +573,18 @@ export async function runSync(opts, stdout = (line) => process.stdout.write(line
|
|
|
552
573
|
for (const line of briefingResult.log)
|
|
553
574
|
stdout(info(line));
|
|
554
575
|
// Record the wrapper block and the module exactly as they now stand on disk, so the next sync
|
|
555
|
-
// can tell them untouched from edited (M-ADR 0218 §4.7), and the binding
|
|
576
|
+
// can tell them untouched from edited (M-ADR 0218 §4.7), and the binding and the CLI source
|
|
577
|
+
// when they are not the defaults.
|
|
556
578
|
const nextLock = upsertBundle(migrateFrom ? removeBundle(prevLock, migrateFrom.name) : prevLock, newEntry);
|
|
557
|
-
const { ciBinding: _previousBinding, ciRunner: _previousRunner, ciWorkflowBlockSha256: _previousSha, ...lockBase } = nextLock;
|
|
579
|
+
const { ciBinding: _previousBinding, ciRunner: _previousRunner, ciWorkflowBlockSha256: _previousSha, cliSource: _previousSource, cliPackage: _previousPackage, ...lockBase } = nextLock;
|
|
558
580
|
await writeLockFile(opts.targetRepo, {
|
|
559
581
|
...lockBase,
|
|
560
582
|
...(ciBinding !== DEFAULT_CI_BINDING ? { ciBinding } : {}),
|
|
561
583
|
...(ciRunner !== undefined && ciRunner.length > 0 ? { ciRunner: ciRunner.join(",") } : {}),
|
|
562
584
|
...(ci.wrapperSha !== null ? { ciWorkflowBlockSha256: ci.wrapperSha } : {}),
|
|
563
585
|
ciModuleSha256: ci.moduleSha,
|
|
586
|
+
...(cliSource.source !== "registry" ? { cliSource: cliSource.source } : {}),
|
|
587
|
+
...(cliPackage !== undefined ? { cliPackage } : {}),
|
|
564
588
|
});
|
|
565
589
|
stdout(pass(`Method baseline synced to: ${opts.targetRepo}`));
|
|
566
590
|
stdout(info(`Updated lock file: method-baseline.lock (lock_version: ${LOCK_VERSION})`));
|
|
@@ -67,7 +67,7 @@ export async function syncMethodManagedSettings(opts) {
|
|
|
67
67
|
const fileExisted = await fileExists(absPath);
|
|
68
68
|
const existing = await readSettings(absPath);
|
|
69
69
|
const hooksActive = opts.agentHarness === "claude-code";
|
|
70
|
-
const ownedHooks = hooksActive ? buildMethodManagedHooks() : undefined;
|
|
70
|
+
const ownedHooks = hooksActive ? buildMethodManagedHooks(opts.cliSource) : undefined;
|
|
71
71
|
const hooksDrifted = hooksActive && ownedHooks
|
|
72
72
|
? detectHooksDrift(normaliseOwnHookRenders(existing, ownedHooks), ownedHooks)
|
|
73
73
|
: [];
|
|
@@ -25,7 +25,7 @@ const TITLES = {
|
|
|
25
25
|
// CI has no platform checkout, so this step runs the app-only checks; the met gate needs the platform beside it.
|
|
26
26
|
"app-dependencies": "Validate app dependencies (app-only checks; no platform checkout in CI)",
|
|
27
27
|
};
|
|
28
|
-
export function methodValidationPlan(profile) {
|
|
28
|
+
export function methodValidationPlan(profile, opts = {}) {
|
|
29
29
|
const steps = validatorsForProfile(profile)
|
|
30
30
|
.filter((v) => v.optional !== true)
|
|
31
31
|
.map((v) => ({
|
|
@@ -38,12 +38,15 @@ export function methodValidationPlan(profile) {
|
|
|
38
38
|
if (profile === "platform") {
|
|
39
39
|
steps.push({ id: "cascade-completeness", title: "Validate cascade completeness", tool: "bash", args: ["orchestration/scripts/validate-cascade-completeness.sh", "all"], advisory: false }, { id: "architecture-alignment", title: "Validate architecture alignment", tool: "bash", args: ["orchestration/scripts/validate-architecture-alignment.sh"], advisory: false });
|
|
40
40
|
}
|
|
41
|
+
const gating = opts.catalogueRebuild === true;
|
|
41
42
|
steps.push({
|
|
42
43
|
id: "catalogue-freshness",
|
|
43
|
-
title:
|
|
44
|
+
title: gating
|
|
45
|
+
? "Check catalogues reproduce from the specs (gating: the pipeline rebuilt them)"
|
|
46
|
+
: "Check catalogues reproduce from the specs (advisory)",
|
|
44
47
|
tool: "sdd-maintain",
|
|
45
48
|
args: ["catalogue", "build", "--check", "--sdd-path", "."],
|
|
46
|
-
advisory:
|
|
49
|
+
advisory: !gating,
|
|
47
50
|
});
|
|
48
51
|
return steps;
|
|
49
52
|
}
|
package/dist/maintain-program.js
CHANGED
|
@@ -24,7 +24,7 @@ import { registerGenerateBriefing } from "./verbs/generate-briefing.js";
|
|
|
24
24
|
import { registerPackBuild } from "./verbs/pack-build.js";
|
|
25
25
|
import { registerRender } from "./verbs/render.js";
|
|
26
26
|
import { registerSync } from "./verbs/sync.js";
|
|
27
|
-
const SPEC_URL = "
|
|
27
|
+
const SPEC_URL = "docs/method/cli/sdd-cli-specification.md (in the method distribution)";
|
|
28
28
|
/** The verbs maintain adds to verify, as `<verb>` or `<verb> <subcommand>`. Asserted by test. */
|
|
29
29
|
export const MAINTAIN_ONLY_SURFACE = [
|
|
30
30
|
"sync",
|
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",
|
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
|
},
|