@sdd-method/maintain-cli 0.0.0-stage → 0.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +28 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +123 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/bundle.js +123 -0
- package/dist/lib/catalogue/aggregate.js +2404 -0
- package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
- package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/contract-path-resolution.js +149 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-item-coverage.js +142 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +69 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/graph-run.js +64 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/ci-binding/index.js +209 -0
- package/dist/lib/ci-binding/module.js +204 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/context-pull/index.js +151 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/hooks-seed/copilot.js +167 -0
- package/dist/lib/hooks-seed/entries.js +47 -0
- package/dist/lib/hooks-seed/index.js +17 -0
- package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
- package/dist/lib/hooks-seed/merge.js +128 -0
- package/dist/lib/hooks-seed/types.js +20 -0
- package/dist/lib/init/ci-workflow.js +89 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +432 -0
- package/dist/lib/mcp-seed/entries.js +14 -0
- package/dist/lib/mcp-seed/index.js +28 -0
- package/dist/lib/mcp-seed/merge.js +99 -0
- package/dist/lib/mcp-seed/types.js +13 -0
- package/dist/lib/pack/builder.js +303 -0
- package/dist/lib/pack/index.js +87 -0
- package/dist/lib/pack/leakage.js +144 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/publish-transform.js +233 -0
- package/dist/lib/pack/reference-closure.js +125 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/snapshot/catalogue-diff.js +219 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/sync/adr-transitions.js +250 -0
- package/dist/lib/sync/apply.js +91 -0
- package/dist/lib/sync/briefing-merge.js +90 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/bundle-source.js +61 -0
- package/dist/lib/sync/config.js +72 -0
- package/dist/lib/sync/copilot-hooks-merge.js +108 -0
- package/dist/lib/sync/diff.js +251 -0
- package/dist/lib/sync/index.js +809 -0
- package/dist/lib/sync/settings-merge.js +216 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-bin.js +8 -0
- package/dist/maintain-program.js +91 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/catalogue-maintain.js +110 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-build.js +31 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/sync.js +98 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// `pack validate` — reads and checks a context-pack seed; writes nothing.
|
|
2
|
+
// Kept free of any import that reaches `./builder.js`, so that importing it
|
|
3
|
+
// cannot drag pack-building code into a package that must not author
|
|
4
|
+
// (M-ADR 0212 §4.3b). `index.ts` re-exports it, so existing imports are unchanged.
|
|
5
|
+
import { validateSeed } from "./schema.js";
|
|
6
|
+
import { loadSeed } from "./seed.js";
|
|
7
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
8
|
+
import { fail, info, pass, section } from "./log.js";
|
|
9
|
+
export async function runPackValidate(opts, stdout = (s) => process.stdout.write(s)) {
|
|
10
|
+
stdout(section("Pack validate"));
|
|
11
|
+
let seed;
|
|
12
|
+
try {
|
|
13
|
+
seed = await loadSeed(opts.seedPath);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
stdout(fail(`Seed load failed: ${err.message}`));
|
|
17
|
+
return { exitCode: 1, errors: [{ field: "(load)", message: err.message }] };
|
|
18
|
+
}
|
|
19
|
+
// A seed key this build does not know is REPORTED rather than dropped in silence.
|
|
20
|
+
// Not fatal: an older sdd-cli must still build a seed that declares something newer
|
|
21
|
+
// than itself, exactly as for `.sdd/sdd-manifest.yaml`. Silence here is what let an
|
|
22
|
+
// adopter follow the schema and get a clean validation with no effect.
|
|
23
|
+
if (seed.unknownKeys && seed.unknownKeys.length > 0) {
|
|
24
|
+
stdout(info(`${seed.unknownKeys.length} seed key(s) not understood by sdd-cli ${getSddCliVersion()} ` +
|
|
25
|
+
`and therefore IGNORED: ${seed.unknownKeys.join(", ")}. If the seed follows a newer ` +
|
|
26
|
+
`context-pack-seed-schema than this build, upgrade sdd-cli; otherwise remove or correct the key.`));
|
|
27
|
+
}
|
|
28
|
+
const validation = validateSeed(seed);
|
|
29
|
+
if (!validation.ok) {
|
|
30
|
+
stdout(fail("Seed validation failed:"));
|
|
31
|
+
for (const e of validation.errors) {
|
|
32
|
+
stdout(fail(` ${e.field}: ${e.message}`));
|
|
33
|
+
}
|
|
34
|
+
return { exitCode: 1, errors: validation.errors };
|
|
35
|
+
}
|
|
36
|
+
stdout(pass(`Seed valid: ${seed.scope} v${seed.version}`));
|
|
37
|
+
stdout(info(seed.forwardBase
|
|
38
|
+
? `Forward base: ${seed.forwardBase.bundle} >= ${seed.forwardBase.minimumVersion}`
|
|
39
|
+
: "Forward base: none (profile-agnostic pack, ADR 0176 §4.6)"));
|
|
40
|
+
if (seed.scaffoldSourcePaths) {
|
|
41
|
+
stdout(info(`Scaffold-source prefixes: ${seed.scaffoldSourcePaths.join(", ")}`));
|
|
42
|
+
}
|
|
43
|
+
stdout(info(`Managed paths: ${seed.managedPaths.length}`));
|
|
44
|
+
if (seed.requiresPack) {
|
|
45
|
+
stdout(info(`requires_pack: ${seed.requiresPack.name} ${seed.requiresPack.version}`));
|
|
46
|
+
}
|
|
47
|
+
return { exitCode: 0, errors: [] };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* Read the package version from package.json at runtime so the various
|
|
6
|
+
* version-string consumers (CLI --version, MCP server identity,
|
|
7
|
+
* catalogue error messages) cannot drift from the published version.
|
|
8
|
+
*
|
|
9
|
+
* Resolves relative to this module's compiled location:
|
|
10
|
+
* - In source layout: src/lib/package-version.ts → package.json (../../)
|
|
11
|
+
* - In dist layout: dist/lib/package-version.js → package.json (../../)
|
|
12
|
+
*
|
|
13
|
+
* Cached on first call.
|
|
14
|
+
*/
|
|
15
|
+
let cached;
|
|
16
|
+
let cachedEngine;
|
|
17
|
+
/**
|
|
18
|
+
* The version of the sdd-cli ENGINE this code is. In @sdd-method/sdd-cli it is the package
|
|
19
|
+
* version. In a persona package (verify-cli, maintain-cli) the package version is the
|
|
20
|
+
* persona's own (0.1.0, 1.0.0), while every check and stamp that means "which sdd-cli is
|
|
21
|
+
* this" (a bundle's minimum_sdd_cli_version, the CLI version stamped into catalogues, the
|
|
22
|
+
* MCP server identity) must see the engine it was built from. The persona build writes that
|
|
23
|
+
* as `sddCliVersion` in the package's package.json. Found by maintain-cli's acceptance run:
|
|
24
|
+
* its sync refused every bundle because 0.1.0 < 1.41.0.
|
|
25
|
+
*/
|
|
26
|
+
export function getSddCliVersion() {
|
|
27
|
+
if (cachedEngine !== undefined)
|
|
28
|
+
return cachedEngine;
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const pkg = JSON.parse(readFileSync(resolve(here, "..", "..", "package.json"), "utf8"));
|
|
31
|
+
cachedEngine = pkg.sddCliVersion ?? pkg.version;
|
|
32
|
+
return cachedEngine;
|
|
33
|
+
}
|
|
34
|
+
/** The package's own version: what `--version` prints. For the engine version, use getSddCliVersion. */
|
|
35
|
+
export function getPackageVersion() {
|
|
36
|
+
if (cached !== undefined)
|
|
37
|
+
return cached;
|
|
38
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const pkgPath = resolve(here, "..", "..", "package.json");
|
|
40
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
41
|
+
cached = pkg.version;
|
|
42
|
+
return cached;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The @sdd-method/verify-cli version released WITH this sdd-cli (M-ADR 0212
|
|
46
|
+
* §6.1 Phase 1). The method-managed CI workflow installs exactly this version,
|
|
47
|
+
* so the CLI that syncs a repository decides which verifier judges it.
|
|
48
|
+
*
|
|
49
|
+
* Read from dist/verify-version.json, which the build copies from
|
|
50
|
+
* scripts/verify-version.json. Never from scripts/ in a built layout: the global
|
|
51
|
+
* `sdd-cli` on the method's machines is a link into the checkout, so reading the
|
|
52
|
+
* committed file live would render a version bumped in source but not yet
|
|
53
|
+
* published into every workflow synced that day, while dist/ still lagged.
|
|
54
|
+
* Source layout (tests) has no copy and reads scripts/ directly.
|
|
55
|
+
*
|
|
56
|
+
* Throws rather than defaults: a missing version must not render a floating install.
|
|
57
|
+
*/
|
|
58
|
+
const cachedPersona = {};
|
|
59
|
+
/**
|
|
60
|
+
* One exact npm version: X.Y.Z, optionally with a prerelease tag (1.1.0-rc.0), which a release
|
|
61
|
+
* candidate published under the `next` dist-tag needs so hosted CI can run it before the release.
|
|
62
|
+
* Never a range, a tag name or a wildcard.
|
|
63
|
+
*/
|
|
64
|
+
export const EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$/;
|
|
65
|
+
function personaVersion(persona) {
|
|
66
|
+
const hit = cachedPersona[persona];
|
|
67
|
+
if (hit !== undefined)
|
|
68
|
+
return hit;
|
|
69
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const fromSource = here.endsWith(`${sep}src${sep}lib`);
|
|
71
|
+
const file = fromSource
|
|
72
|
+
? resolve(here, "..", "..", "scripts", `${persona}-version.json`)
|
|
73
|
+
: resolve(here, "..", `${persona}-version.json`);
|
|
74
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
75
|
+
if (typeof parsed.version !== "string" || !EXACT_VERSION.test(parsed.version)) {
|
|
76
|
+
throw new Error(`${file} has no semver \`version\`; refusing to render an unpinned ${persona}-cli install`);
|
|
77
|
+
}
|
|
78
|
+
cachedPersona[persona] = parsed.version;
|
|
79
|
+
return parsed.version;
|
|
80
|
+
}
|
|
81
|
+
export function getVerifyCliVersion() {
|
|
82
|
+
return personaVersion("verify");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The @sdd-method/maintain-cli version released WITH this sdd-cli. The managed CI workflow's
|
|
86
|
+
* catalogue freshness step installs exactly this version (M-ADR 0218 §5; ruled 2026-10-15, sdm-jts.6).
|
|
87
|
+
* Read the same way as the verify version, for the same reason (dist/ copy, never scripts/ live).
|
|
88
|
+
*/
|
|
89
|
+
export function getMaintainCliVersion() {
|
|
90
|
+
return personaVersion("maintain");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=package-version.js.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Forward-base bundles set `.sdd-repo-kind` and qualify as the base
|
|
5
|
+
* layer that additive extensions can compose on top of (per ADR 0135).
|
|
6
|
+
* The legacy self-contained `reverse-baseline` is retained as a
|
|
7
|
+
* forward base for compat with adopters who haven't migrated to the
|
|
8
|
+
* `reverse-extension-baseline` additive shape yet.
|
|
9
|
+
*/
|
|
10
|
+
const FORWARD_BASE_BUNDLES = new Set([
|
|
11
|
+
"platform-baseline",
|
|
12
|
+
"app-baseline",
|
|
13
|
+
"integration-baseline",
|
|
14
|
+
"reverse-baseline",
|
|
15
|
+
]);
|
|
16
|
+
export const REPO_KIND_FILE = ".sdd-repo-kind";
|
|
17
|
+
/**
|
|
18
|
+
* Legacy marker filename from the pre-rebrand era. Read-only fallback during
|
|
19
|
+
* the rebrand transition: `readRepoKind` accepts either filename; `sync`
|
|
20
|
+
* auto-migrates legacy → canonical. New writes always use `REPO_KIND_FILE`.
|
|
21
|
+
* Drop after one minor-version overlap.
|
|
22
|
+
*/
|
|
23
|
+
export const LEGACY_REPO_KIND_FILE = ".iteraitive-repo-kind";
|
|
24
|
+
const PROFILE_BY_REPO_KIND = {
|
|
25
|
+
"platform-product": "platform",
|
|
26
|
+
"application-product": "application",
|
|
27
|
+
"integration-product": "integration",
|
|
28
|
+
"reverse-corpus": "reverse",
|
|
29
|
+
};
|
|
30
|
+
const REPO_KIND_BY_PROFILE = {
|
|
31
|
+
platform: "platform-product",
|
|
32
|
+
application: "application-product",
|
|
33
|
+
integration: "integration-product",
|
|
34
|
+
reverse: "reverse-corpus",
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Forward-base bundles map 1:1 to a profile. Additive bundles
|
|
38
|
+
* (`reverse-extension-baseline`) have no profile of their own — they
|
|
39
|
+
* compose on top of whatever forward base is already locked.
|
|
40
|
+
*/
|
|
41
|
+
const PROFILE_BY_BUNDLE_NAME = {
|
|
42
|
+
"platform-baseline": "platform",
|
|
43
|
+
"app-baseline": "application",
|
|
44
|
+
"integration-baseline": "integration",
|
|
45
|
+
"reverse-baseline": "reverse",
|
|
46
|
+
};
|
|
47
|
+
const BUNDLE_NAME_BY_PROFILE = {
|
|
48
|
+
platform: "platform-baseline",
|
|
49
|
+
application: "app-baseline",
|
|
50
|
+
integration: "integration-baseline",
|
|
51
|
+
reverse: "reverse-baseline",
|
|
52
|
+
};
|
|
53
|
+
export function profileFromRepoKind(kind) {
|
|
54
|
+
return PROFILE_BY_REPO_KIND[kind];
|
|
55
|
+
}
|
|
56
|
+
export function repoKindFromProfile(profile) {
|
|
57
|
+
return REPO_KIND_BY_PROFILE[profile];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns the profile a bundle authoritatively claims, or null for
|
|
61
|
+
* additive bundles that don't claim a profile of their own. Accepts
|
|
62
|
+
* arbitrary strings (adopter pack names per ADR 0151) and returns null
|
|
63
|
+
* for any name not in the closed sdd-method bundle set.
|
|
64
|
+
*/
|
|
65
|
+
export function profileFromBundleName(name) {
|
|
66
|
+
if (!isBundleName(name))
|
|
67
|
+
return null;
|
|
68
|
+
return PROFILE_BY_BUNDLE_NAME[name] ?? null;
|
|
69
|
+
}
|
|
70
|
+
export function bundleNameFromProfile(profile) {
|
|
71
|
+
return BUNDLE_NAME_BY_PROFILE[profile];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Forward-base bundles set `.sdd-repo-kind` and qualify as the base
|
|
75
|
+
* layer that additive extensions compose on top of. Additive bundles
|
|
76
|
+
* (sdd-method's `reverse-extension-baseline` and ADR 0151 adopter
|
|
77
|
+
* packs) return false. Accepts arbitrary strings; unknown names return
|
|
78
|
+
* false.
|
|
79
|
+
*/
|
|
80
|
+
export function isForwardBase(name) {
|
|
81
|
+
if (!isBundleName(name))
|
|
82
|
+
return false;
|
|
83
|
+
return FORWARD_BASE_BUNDLES.has(name);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Maps repo-kind → forward-base bundle name. Used when migrating a
|
|
87
|
+
* legacy v1 lock file (which doesn't record bundle name) to v2.
|
|
88
|
+
*/
|
|
89
|
+
export function bundleNameFromRepoKind(kind) {
|
|
90
|
+
return BUNDLE_NAME_BY_PROFILE[PROFILE_BY_REPO_KIND[kind]];
|
|
91
|
+
}
|
|
92
|
+
export function isRepoKind(value) {
|
|
93
|
+
return value in PROFILE_BY_REPO_KIND;
|
|
94
|
+
}
|
|
95
|
+
const ALL_BUNDLE_NAMES = new Set([
|
|
96
|
+
"platform-baseline",
|
|
97
|
+
"app-baseline",
|
|
98
|
+
"integration-baseline",
|
|
99
|
+
"reverse-baseline",
|
|
100
|
+
"reverse-extension-baseline",
|
|
101
|
+
]);
|
|
102
|
+
export function isBundleName(value) {
|
|
103
|
+
return ALL_BUNDLE_NAMES.has(value);
|
|
104
|
+
}
|
|
105
|
+
export async function readRepoKind(repoRoot) {
|
|
106
|
+
// Canonical first; fall back to legacy marker during the rebrand transition.
|
|
107
|
+
for (const filename of [REPO_KIND_FILE, LEGACY_REPO_KIND_FILE]) {
|
|
108
|
+
const path = join(repoRoot, filename);
|
|
109
|
+
let raw;
|
|
110
|
+
try {
|
|
111
|
+
raw = await readFile(path, "utf8");
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err.code === "ENOENT")
|
|
115
|
+
continue;
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
const value = raw.trim();
|
|
119
|
+
if (!isRepoKind(value)) {
|
|
120
|
+
throw new Error(`Unrecognised ${filename} value: ${JSON.stringify(value)} at ${path}`);
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
export async function writeRepoKind(repoRoot, kind) {
|
|
127
|
+
await writeFile(join(repoRoot, REPO_KIND_FILE), `${kind}\n`, "utf8");
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=repo-kind.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
export function resolveBundledScript(name) {
|
|
4
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
return resolve(here, "..", "..", "scripts", name);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=scripts.js.map
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// `sdd-cli catalogue diff` — the catalogue consequence of a snapshot update,
|
|
2
|
+
// attributed at FILE grain (reverse-snapshot-delta step 10).
|
|
3
|
+
//
|
|
4
|
+
// It BUILDS both sides itself, in this one process: BEFORE from `git archive
|
|
5
|
+
// <ref>` of the SDD, AFTER from the working tree, each into a temporary output
|
|
6
|
+
// directory. Committed catalogues are never read — they are as old as their last
|
|
7
|
+
// build — and one process means one CLI, so a CLI change between the builds
|
|
8
|
+
// cannot appear as changed rows. Nothing is written into the SDD, its catalogues
|
|
9
|
+
// or its .git.
|
|
10
|
+
//
|
|
11
|
+
// Attribution is at file grain because catalogue rows carry no source-file
|
|
12
|
+
// provenance, so "every row names a finding" is not checkable:
|
|
13
|
+
// - every SDD source file changed since <ref> must appear in some finding's
|
|
14
|
+
// targets (the pin bookkeeping `snapshot advance` writes is exempt), through
|
|
15
|
+
// a PRECISE target: grain `file`, grain `confirmed` (narrowed by hand), or a
|
|
16
|
+
// plain string written by hand. A file reached only through a `repo`-grain
|
|
17
|
+
// target is attributed BY MENTION ONLY, and fails: a repo-grain target names
|
|
18
|
+
// every artefact that mentions the member, so in a component-heavy SDD it
|
|
19
|
+
// covers almost any edit and the check would pass by construction. Narrowing
|
|
20
|
+
// is bounded by the files actually edited, not by the targets routed;
|
|
21
|
+
// - files the method bundle manages (the lock's managed_paths) are excluded — a
|
|
22
|
+
// method sync changes them, not the snapshot — and counted;
|
|
23
|
+
// - an attributed file edited by more than one commit is LISTED: attribution is by
|
|
24
|
+
// file, so its other edits ride on the finding unless a reader checks;
|
|
25
|
+
// - catalogue rows that changed while NO file changed (bookkeeping included)
|
|
26
|
+
// are unattributed by construction — the builder moved, not the snapshot.
|
|
27
|
+
// Exit 1 on any unattributed change.
|
|
28
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
29
|
+
import { mkdir, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
import { promisify } from "node:util";
|
|
33
|
+
import { parse } from "yaml";
|
|
34
|
+
import { runCatalogueBuild } from "../catalogue/build.js";
|
|
35
|
+
const execFile = promisify(execFileCb);
|
|
36
|
+
const EXEMPT = [/^catalogues\//, /^repos\/[^/]+\/reverse-(history|metadata)\.yaml$/];
|
|
37
|
+
/**
|
|
38
|
+
* Files the method bundle manages — the lock's managed_paths, at the ref and now
|
|
39
|
+
* (a sync adds and retires them), plus the lock itself. A run that spans a method
|
|
40
|
+
* sync changes them, and no snapshot finding caused that.
|
|
41
|
+
*/
|
|
42
|
+
export function managedPathsOf(lockText) {
|
|
43
|
+
const out = new Set();
|
|
44
|
+
if (!lockText)
|
|
45
|
+
return out;
|
|
46
|
+
const lock = (parse(lockText) ?? {});
|
|
47
|
+
for (const b of lock.bundles ?? []) {
|
|
48
|
+
for (const e of b.managed_paths ?? []) {
|
|
49
|
+
const m = String(e).match(/^[0-9a-f]{64}\s+(.+)$/);
|
|
50
|
+
out.add(m ? m[1] : String(e));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
async function git(cwd, args) {
|
|
56
|
+
// core.quotepath=false: git otherwise ESCAPES non-ASCII paths ("\342\200\224…",
|
|
57
|
+
// quoted), so an ADR titled with an em dash never equals its delta.yaml target —
|
|
58
|
+
// measured: 9 unattributed files on one SDD, 4 of them genuinely targeted.
|
|
59
|
+
return (await execFile("git", ["-c", "core.quotepath=false", "-C", cwd, ...args], { maxBuffer: 64 * 1024 * 1024 })).stdout;
|
|
60
|
+
}
|
|
61
|
+
/** Every file changed since `ref`, tracked or untracked. */
|
|
62
|
+
export async function changedFiles(sddPath, ref) {
|
|
63
|
+
const tracked = (await git(sddPath, ["diff", "--name-only", ref, "--"])).split("\n");
|
|
64
|
+
const untracked = (await git(sddPath, ["ls-files", "--others", "--exclude-standard"])).split("\n");
|
|
65
|
+
return [...new Set([...tracked, ...untracked].filter((p) => p !== ""))].sort();
|
|
66
|
+
}
|
|
67
|
+
/** The changed files that must be attributed: minus catalogues and advance's pin bookkeeping. */
|
|
68
|
+
export async function changedSourceFiles(sddPath, ref) {
|
|
69
|
+
return (await changedFiles(sddPath, ref)).filter((p) => !EXEMPT.some((re) => re.test(p)));
|
|
70
|
+
}
|
|
71
|
+
/** Every target, and whether it is precise (file / confirmed grain, or hand-written) or a repo-grain mention. */
|
|
72
|
+
export function targetSet(delta) {
|
|
73
|
+
const findings = delta?.findings ?? [];
|
|
74
|
+
const out = new Map();
|
|
75
|
+
for (const f of findings) {
|
|
76
|
+
for (const t of f.targets ?? []) {
|
|
77
|
+
let artefact = null;
|
|
78
|
+
let precise = true;
|
|
79
|
+
if (typeof t === "string")
|
|
80
|
+
artefact = t;
|
|
81
|
+
else if (t && typeof t === "object" && typeof t.artefact === "string") {
|
|
82
|
+
artefact = t.artefact;
|
|
83
|
+
precise = t.grain !== "repo";
|
|
84
|
+
}
|
|
85
|
+
if (artefact !== null)
|
|
86
|
+
out.set(artefact, (out.get(artefact) ?? false) || precise);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...out].map(([artefact, precise]) => ({ artefact, precise }));
|
|
90
|
+
}
|
|
91
|
+
/** "precise" | "mention" | null — how, if at all, a changed file is attributed. */
|
|
92
|
+
export function attributed(file, targets) {
|
|
93
|
+
const hits = targets.filter((t) => t.artefact === file || (t.artefact.endsWith("/") && file.startsWith(t.artefact)));
|
|
94
|
+
if (hits.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
return hits.some((t) => t.precise) ? "precise" : "mention";
|
|
97
|
+
}
|
|
98
|
+
async function rowSets(dir) {
|
|
99
|
+
const out = new Map();
|
|
100
|
+
for (const f of await readdir(dir)) {
|
|
101
|
+
if (!f.endsWith(".csv"))
|
|
102
|
+
continue;
|
|
103
|
+
const lines = (await readFile(join(dir, f), "utf8")).split("\n").filter((l) => l !== "");
|
|
104
|
+
out.set(f.replace(/\.csv$/, ""), lines.slice(1));
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
export function rowDelta(before, after) {
|
|
109
|
+
const count = new Map();
|
|
110
|
+
for (const r of before)
|
|
111
|
+
count.set(r, (count.get(r) ?? 0) + 1);
|
|
112
|
+
let added = 0;
|
|
113
|
+
for (const r of after) {
|
|
114
|
+
const c = count.get(r) ?? 0;
|
|
115
|
+
if (c > 0)
|
|
116
|
+
count.set(r, c - 1);
|
|
117
|
+
else
|
|
118
|
+
added++;
|
|
119
|
+
}
|
|
120
|
+
let removed = 0;
|
|
121
|
+
for (const c of count.values())
|
|
122
|
+
removed += c;
|
|
123
|
+
return { added, removed };
|
|
124
|
+
}
|
|
125
|
+
/** Attributed files edited by more than one commit in `from..HEAD`, one line each. */
|
|
126
|
+
export async function coEdits(sddPath, from, files) {
|
|
127
|
+
const out = [];
|
|
128
|
+
for (const f of files) {
|
|
129
|
+
const commits = (await git(sddPath, ["log", "--format=%h %s", `${from}..HEAD`, "--", f])).split("\n").filter((l) => l !== "");
|
|
130
|
+
if (commits.length > 1)
|
|
131
|
+
out.push(` ${f}: ${commits.length} commits — ${commits.map((c) => c.slice(0, 60)).join(" | ")}`);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
export async function runCatalogueDiff(inputs, write) {
|
|
136
|
+
const { sddPath, from } = inputs;
|
|
137
|
+
const delta = parse(await readFile(inputs.attribute, "utf8"));
|
|
138
|
+
const targets = targetSet(delta);
|
|
139
|
+
const work = await mkdtemp(join(tmpdir(), "sdd-cli-catalogue-diff-"));
|
|
140
|
+
try {
|
|
141
|
+
await git(sddPath, ["rev-parse", "--verify", `${from}^{commit}`]);
|
|
142
|
+
const beforeTree = join(work, "before-tree");
|
|
143
|
+
await mkdir(beforeTree);
|
|
144
|
+
await execFile("git", ["-C", sddPath, "archive", "-o", join(work, "before.tar"), from]);
|
|
145
|
+
await execFile("tar", ["-xf", join(work, "before.tar"), "-C", beforeTree]);
|
|
146
|
+
const sink = () => { };
|
|
147
|
+
const b = await runCatalogueBuild({ sddPath: beforeTree, outDir: join(work, "before") }, sink);
|
|
148
|
+
const a = await runCatalogueBuild({ sddPath, outDir: join(work, "after") }, sink);
|
|
149
|
+
write(`built both sides in one process (schema ${a.schemaVersion}); before exit ${b.exitCode}, after exit ${a.exitCode}\n`);
|
|
150
|
+
// A side that failed to build compared partial catalogues, so its row delta
|
|
151
|
+
// measures the failure, not the change (empty-set sweep, R2, ruled 2026-09-29).
|
|
152
|
+
if (b.exitCode !== 0 || a.exitCode !== 0) {
|
|
153
|
+
const which = [b.exitCode !== 0 ? `before (${from})` : "", a.exitCode !== 0 ? "after (working tree)" : ""]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(" and ");
|
|
156
|
+
write(`\nFAIL: the ${which} build failed — the comparison would measure the failure, not the change. Fix the build first\n`);
|
|
157
|
+
return { exitCode: 1 };
|
|
158
|
+
}
|
|
159
|
+
const before = await rowSets(join(work, "before"));
|
|
160
|
+
const after = await rowSets(join(work, "after"));
|
|
161
|
+
const changedCatalogues = [];
|
|
162
|
+
for (const name of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
163
|
+
const d = rowDelta(before.get(name) ?? [], after.get(name) ?? []);
|
|
164
|
+
if (d.added + d.removed > 0)
|
|
165
|
+
changedCatalogues.push([name, d]);
|
|
166
|
+
}
|
|
167
|
+
const lockNow = await readFile(join(sddPath, "method-baseline.lock"), "utf8").catch(() => null);
|
|
168
|
+
const lockThen = await git(sddPath, ["show", `${from}:method-baseline.lock`]).catch(() => null);
|
|
169
|
+
const managed = new Set([...managedPathsOf(lockNow), ...managedPathsOf(lockThen), "method-baseline.lock"]);
|
|
170
|
+
const all = (await changedFiles(sddPath, from)).filter((p) => !p.startsWith("catalogues/"));
|
|
171
|
+
const byBundle = all.filter((p) => managed.has(p));
|
|
172
|
+
const files = all.filter((p) => !EXEMPT.some((re) => re.test(p)) && !managed.has(p));
|
|
173
|
+
if (byBundle.length > 0)
|
|
174
|
+
write(`${byBundle.length} changed file(s) are managed by the method bundle (method-baseline.lock) — excluded from attribution\n`);
|
|
175
|
+
const unattributed = files.filter((f) => attributed(f, targets) === null);
|
|
176
|
+
const byMention = files.filter((f) => attributed(f, targets) === "mention");
|
|
177
|
+
write(`\nsource files changed since ${from}: ${files.length} — ${files.length - unattributed.length - byMention.length} attributed precisely, ` +
|
|
178
|
+
`${byMention.length} by mention only, ${unattributed.length} unattributed\n`);
|
|
179
|
+
for (const f of byMention)
|
|
180
|
+
write(` BY MENTION ONLY ${f} — narrow the target that names it (grain: confirmed) or record the finding it belongs to\n`);
|
|
181
|
+
for (const f of unattributed)
|
|
182
|
+
write(` UNATTRIBUTED ${f}\n`);
|
|
183
|
+
// Attribution is by FILE: a finding that targets a file covers every edit to it in
|
|
184
|
+
// the window, including edits the snapshot did not cause (a schema sync riding on a
|
|
185
|
+
// citation re-point). The tool cannot tell which commit is the snapshot's, so every
|
|
186
|
+
// attributed file edited by more than one commit is listed for a reader to check.
|
|
187
|
+
const coEdited = await coEdits(sddPath, from, files.filter((x) => attributed(x, targets) === "precise"));
|
|
188
|
+
if (coEdited.length > 0) {
|
|
189
|
+
write(`\n${coEdited.length} attributed file(s) were edited by more than one commit — their other edits are NOT attributed by this check; confirm each belongs to a finding:\n`);
|
|
190
|
+
for (const l of coEdited)
|
|
191
|
+
write(`${l}\n`);
|
|
192
|
+
}
|
|
193
|
+
write(`\ncatalogues with changed rows: ${changedCatalogues.length}\n`);
|
|
194
|
+
for (const [name, d] of changedCatalogues)
|
|
195
|
+
write(` ${name}: +${d.added} -${d.removed}\n`);
|
|
196
|
+
let failed = unattributed.length > 0 || byMention.length > 0;
|
|
197
|
+
// Exempt files (the pin bookkeeping) are excused from attribution, not from
|
|
198
|
+
// feeding catalogues: they count here, or advance's own edits look like drift.
|
|
199
|
+
if (all.length === 0 && changedCatalogues.length > 0) {
|
|
200
|
+
write("\nUNATTRIBUTED: catalogue rows changed while no file did — that is the builder, not the snapshot\n");
|
|
201
|
+
failed = true;
|
|
202
|
+
}
|
|
203
|
+
if (failed) {
|
|
204
|
+
write("\nFAIL: each unattributed change is an unrecorded finding or an unintended edit — resolve which\n");
|
|
205
|
+
return { exitCode: 1 };
|
|
206
|
+
}
|
|
207
|
+
if (files.length === 0) {
|
|
208
|
+
// Vacuously true of an empty set: say so rather than claim attribution.
|
|
209
|
+
write(`\nNOTHING TO ATTRIBUTE: 0 source files changed since ${from} — no finding was needed, and none was checked\n`);
|
|
210
|
+
return { exitCode: 0 };
|
|
211
|
+
}
|
|
212
|
+
write(`\nPASS: every changed source file (${files.length}) is a precise target of some finding\n`);
|
|
213
|
+
return { exitCode: 0 };
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
await rm(work, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=catalogue-diff.js.map
|