@sdd-method/author-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/author-bin.js +8 -0
- package/dist/author-program.js +99 -0
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +37 -0
- package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
- package/dist/lib/api-surface/analyzers/library.js +95 -0
- package/dist/lib/api-surface/analyzers/serverless.js +336 -0
- package/dist/lib/api-surface/analyzers/spring.js +237 -0
- package/dist/lib/api-surface/analyzers/terraform.js +109 -0
- package/dist/lib/api-surface/detect.js +25 -0
- package/dist/lib/api-surface/emit.js +109 -0
- package/dist/lib/api-surface/file-set.js +43 -0
- package/dist/lib/api-surface/index.js +134 -0
- package/dist/lib/api-surface/types.js +17 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +132 -0
- package/dist/lib/briefing/variables.js +92 -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/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-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 +70 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -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/conformance/binding.js +70 -0
- package/dist/lib/conformance/finding.js +69 -0
- package/dist/lib/conformance/index.js +578 -0
- package/dist/lib/context-pull/index.js +191 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
- package/dist/lib/contracts-availability/index.js +267 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/pack/log.js +18 -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/plan/index.js +69 -0
- package/dist/lib/plan/read-sdd.js +102 -0
- package/dist/lib/plan/render-template.js +84 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scaffold/domain.js +234 -0
- package/dist/lib/scaffold/registry.js +136 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/sdd-index/repo-config.js +245 -0
- package/dist/lib/sdd-reader.js +91 -0
- package/dist/lib/snapshot/advance.js +454 -0
- package/dist/lib/snapshot/diff.js +190 -0
- package/dist/lib/snapshot/fold.js +208 -0
- package/dist/lib/snapshot/land.js +178 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/snapshot/targets.js +169 -0
- package/dist/lib/snapshot/verify.js +91 -0
- package/dist/lib/sync/briefing-merge.js +106 -0
- package/dist/lib/sync/bundle-cache.js +109 -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-version.json +4 -0
- package/dist/verbs/api-surface.js +71 -0
- package/dist/verbs/catalogue-check.js +28 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/conformance.js +34 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/contracts.js +34 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/plan.js +42 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/scaffold.js +83 -0
- package/dist/verbs/snapshot.js +134 -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,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for adopter pack seeds. Mirrors
|
|
4
|
+
* docs/method/foundations/schemas/context-pack-seed-schema.yaml.
|
|
5
|
+
*
|
|
6
|
+
* Used by `sdd-cli pack validate` for fast feedback during authoring.
|
|
7
|
+
* The structural parse in seed.ts already enforces presence of required
|
|
8
|
+
* fields; this schema layer adds value-shape checks (semver patterns,
|
|
9
|
+
* known forward-base bundle names, non-empty managed_paths).
|
|
10
|
+
*/
|
|
11
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
12
|
+
const VERSIONED_RANGE_PATTERN = /^(\^|~|>=)?v?\d+\.\d+\.\d+$/;
|
|
13
|
+
// Exported so the canonical-schema conformance test can assert this vocabulary
|
|
14
|
+
// matches context-pack-seed-schema.yaml's `forward_base.bundle` enum. The two had
|
|
15
|
+
// drifted (canonical carried three values, this four) with nothing to detect it.
|
|
16
|
+
export const KNOWN_FORWARD_BASES = [
|
|
17
|
+
"app-baseline",
|
|
18
|
+
"integration-baseline",
|
|
19
|
+
"platform-baseline",
|
|
20
|
+
"reverse-baseline",
|
|
21
|
+
];
|
|
22
|
+
export const PackSeedSchema = z.object({
|
|
23
|
+
version: z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(SEMVER_PATTERN, "version must be semver (X.Y.Z)"),
|
|
26
|
+
scope: z
|
|
27
|
+
.string()
|
|
28
|
+
.regex(/^[a-z0-9-]+$/, "scope must be lower-kebab-case slug"),
|
|
29
|
+
// Optional per ADR 0176 §4.6: absent = profile-agnostic pack.
|
|
30
|
+
forwardBase: z
|
|
31
|
+
.object({
|
|
32
|
+
bundle: z.enum(KNOWN_FORWARD_BASES),
|
|
33
|
+
minimumVersion: z
|
|
34
|
+
.string()
|
|
35
|
+
.regex(/^v\d+\.\d+\.\d+$/, "forward_base.minimum_version must be vX.Y.Z"),
|
|
36
|
+
sourceRepo: z.string().min(1),
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
// Vendor-base pack pin (M-ADR 0175, seed schema 1.2.0). Present only on a
|
|
40
|
+
// vendor-base pack: names the vendor and the vendor version whose documented
|
|
41
|
+
// truth this pack's content encodes.
|
|
42
|
+
//
|
|
43
|
+
// The point of a PACK-LEVEL pin is that a consuming SDD does not restate it. A
|
|
44
|
+
// consumer pins the pack exactly (its bundles-list entry + sha256) and the
|
|
45
|
+
// vendor version it inherits is DERIVED from here. Recording it consumer-side
|
|
46
|
+
// as well would be a second source of truth that can silently disagree, and the
|
|
47
|
+
// pack is authoritative for its own vendor pin.
|
|
48
|
+
//
|
|
49
|
+
// `vendorVersion` is free-form by design: it carries either a formal vendor
|
|
50
|
+
// version or the anchor schema's `<vendor>-docs@YYYY-MM-DD` snapshot form, and
|
|
51
|
+
// vendors' own version strings are not the method's to constrain.
|
|
52
|
+
vendorBase: z
|
|
53
|
+
.object({
|
|
54
|
+
vendor: z
|
|
55
|
+
.string()
|
|
56
|
+
.regex(/^[a-z][a-z0-9-]*$/, "vendor_base.vendor must be a lower-kebab-case slug"),
|
|
57
|
+
vendorVersion: z.string().min(1, "vendor_base.vendor_version must be non-empty"),
|
|
58
|
+
})
|
|
59
|
+
.optional(),
|
|
60
|
+
minimumSddCliVersion: z
|
|
61
|
+
.string()
|
|
62
|
+
.regex(SEMVER_PATTERN, "minimum_sdd_cli_version must be semver (X.Y.Z)"),
|
|
63
|
+
description: z.string().min(1),
|
|
64
|
+
include: z.array(z.string()).optional(),
|
|
65
|
+
managedPaths: z
|
|
66
|
+
.array(z.string().min(1))
|
|
67
|
+
.min(1, "managed_paths must have at least one entry"),
|
|
68
|
+
forbiddenPaths: z.array(z.string()).optional(),
|
|
69
|
+
// ADR 0176 §4.3: scaffold-source path prefixes; each must cover at
|
|
70
|
+
// least one managed_paths entry (checked in the superRefine below).
|
|
71
|
+
scaffoldSourcePaths: z.array(z.string().min(1)).optional(),
|
|
72
|
+
requiresPack: z
|
|
73
|
+
.object({
|
|
74
|
+
name: z.string().min(1),
|
|
75
|
+
version: z
|
|
76
|
+
.string()
|
|
77
|
+
.regex(VERSIONED_RANGE_PATTERN, "requires_pack.version must be a semver range (e.g. ^v1.2.0)"),
|
|
78
|
+
})
|
|
79
|
+
.optional(),
|
|
80
|
+
sourcePath: z.string(),
|
|
81
|
+
});
|
|
82
|
+
const PackSeedSchemaRefined = PackSeedSchema.superRefine((seed, ctx) => {
|
|
83
|
+
// A vendor-base pack is PROFILE-AGNOSTIC (M-ADR 0176 §4.6, M-ADR 0175): its
|
|
84
|
+
// content is vendor-defined, so it layers over any baseline and must not pin
|
|
85
|
+
// one. Declaring both is a contradiction the producer should hear at validate
|
|
86
|
+
// time rather than discover when a consumer on a different baseline cannot
|
|
87
|
+
// apply it.
|
|
88
|
+
if (seed.vendorBase && seed.forwardBase) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: z.ZodIssueCode.custom,
|
|
91
|
+
path: ["forwardBase"],
|
|
92
|
+
message: "a vendor_base pack is profile-agnostic and must omit forward_base " +
|
|
93
|
+
"(M-ADR 0176 §4.6); remove forward_base, or remove vendor_base if this " +
|
|
94
|
+
"is not a vendor-base pack",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (!seed.scaffoldSourcePaths)
|
|
98
|
+
return;
|
|
99
|
+
for (const [i, prefix] of seed.scaffoldSourcePaths.entries()) {
|
|
100
|
+
const covers = seed.managedPaths.some((mp) => mp.startsWith(prefix));
|
|
101
|
+
if (!covers) {
|
|
102
|
+
ctx.addIssue({
|
|
103
|
+
code: z.ZodIssueCode.custom,
|
|
104
|
+
path: ["scaffoldSourcePaths", i],
|
|
105
|
+
message: `scaffold_source_paths entry "${prefix}" matches no managed_paths entry`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
export function validateSeed(seed) {
|
|
111
|
+
const result = PackSeedSchemaRefined.safeParse(seed);
|
|
112
|
+
if (result.success) {
|
|
113
|
+
return { ok: true, errors: [] };
|
|
114
|
+
}
|
|
115
|
+
const errors = result.error.issues.map((issue) => ({
|
|
116
|
+
field: issue.path.join("."),
|
|
117
|
+
message: issue.message,
|
|
118
|
+
}));
|
|
119
|
+
return { ok: false, errors };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
/**
|
|
5
|
+
* Adopter pack seed file. Per ADR 0151 + context-pack-seed-schema.yaml.
|
|
6
|
+
*
|
|
7
|
+
* Loaded by `sdd-cli pack build` and `sdd-cli pack validate` from
|
|
8
|
+
* `orchestration/distribution/{pack-name}-seed.yaml` in adopter repos.
|
|
9
|
+
*
|
|
10
|
+
* Includes are resolved before validation: the merged seed presents
|
|
11
|
+
* all `include`'d managed_paths + forbidden_paths concatenated with
|
|
12
|
+
* the host seed's own.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Authored seed keys, snake_case → the camelCase field they map to.
|
|
16
|
+
*
|
|
17
|
+
* THE SINGLE SOURCE OF TRUTH for "what may appear in a seed file". It lived only
|
|
18
|
+
* inside the canonical-conformance test, which meant the loader had no idea what a
|
|
19
|
+
* valid key set was — so an unrecognised key was dropped by the hand-mapping below
|
|
20
|
+
* without a word. An adopter following a schema that documents a field their sdd-cli
|
|
21
|
+
* predates got a clean validation and a silent no-op: the same failure M-ADR 0175's
|
|
22
|
+
* history records for `packaging`, and the same one fixed for `.sdd/sdd-manifest.yaml`
|
|
23
|
+
* in 0.110.1 — but by a different mechanism, which is why that fix did not cover this.
|
|
24
|
+
*
|
|
25
|
+
* The conformance test now imports this rather than declaring its own copy. Two
|
|
26
|
+
* hand-maintained lists of the same facts is how the lifecycle expectation drifted.
|
|
27
|
+
*/
|
|
28
|
+
export const SEED_KEY_TO_FIELD = {
|
|
29
|
+
version: "version",
|
|
30
|
+
scope: "scope",
|
|
31
|
+
forward_base: "forwardBase",
|
|
32
|
+
vendor_base: "vendorBase",
|
|
33
|
+
minimum_sdd_cli_version: "minimumSddCliVersion",
|
|
34
|
+
description: "description",
|
|
35
|
+
include: "include",
|
|
36
|
+
managed_paths: "managedPaths",
|
|
37
|
+
forbidden_paths: "forbiddenPaths",
|
|
38
|
+
scaffold_source_paths: "scaffoldSourcePaths",
|
|
39
|
+
requires_pack: "requiresPack",
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Top-level keys in a seed file that this build does not recognise.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately TOP-LEVEL only, stated as a limit rather than left implied: nested
|
|
45
|
+
* blocks (`forward_base`, `vendor_base`, `requires_pack`) are hand-mapped field by
|
|
46
|
+
* field too, so an unknown key inside one is still dropped silently today.
|
|
47
|
+
*/
|
|
48
|
+
export function unknownSeedKeys(raw) {
|
|
49
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
50
|
+
return [];
|
|
51
|
+
return Object.keys(raw)
|
|
52
|
+
.filter((k) => !(k in SEED_KEY_TO_FIELD))
|
|
53
|
+
.sort();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load a seed file from disk, resolve its `include:` directives,
|
|
57
|
+
* and return a merged PackSeed. Throws if any include cannot be read
|
|
58
|
+
* or if circular includes are detected.
|
|
59
|
+
*
|
|
60
|
+
* NOTE: This does not validate the seed structure — only parses YAML
|
|
61
|
+
* and assembles the typed shape. Use `validateSeed` (separate module)
|
|
62
|
+
* for schema enforcement.
|
|
63
|
+
*/
|
|
64
|
+
export async function loadSeed(seedPath) {
|
|
65
|
+
const absPath = isAbsolute(seedPath) ? seedPath : resolve(seedPath);
|
|
66
|
+
const visited = new Set();
|
|
67
|
+
return loadSeedRecursive(absPath, visited);
|
|
68
|
+
}
|
|
69
|
+
async function loadSeedRecursive(absPath, visited) {
|
|
70
|
+
if (visited.has(absPath)) {
|
|
71
|
+
throw new Error(`Circular include detected at ${absPath}`);
|
|
72
|
+
}
|
|
73
|
+
visited.add(absPath);
|
|
74
|
+
let raw;
|
|
75
|
+
try {
|
|
76
|
+
raw = await readFile(absPath, "utf8");
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new Error(`Cannot read seed file ${absPath}: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
const parsed = parseYaml(raw);
|
|
82
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
83
|
+
throw new Error(`Seed file ${absPath} is not a YAML object`);
|
|
84
|
+
}
|
|
85
|
+
const baseDir = dirname(absPath);
|
|
86
|
+
const includes = Array.isArray(parsed.include) ? parsed.include : [];
|
|
87
|
+
const includedManaged = [];
|
|
88
|
+
const includedForbidden = [];
|
|
89
|
+
const includedUnknown = [];
|
|
90
|
+
for (const inc of includes) {
|
|
91
|
+
if (typeof inc !== "string") {
|
|
92
|
+
throw new Error(`Non-string include entry in ${absPath}: ${String(inc)}`);
|
|
93
|
+
}
|
|
94
|
+
const incPath = isAbsolute(inc) ? inc : resolve(baseDir, inc);
|
|
95
|
+
const includedSeed = await loadSeedRecursive(incPath, visited);
|
|
96
|
+
includedManaged.push(...includedSeed.managedPaths);
|
|
97
|
+
if (includedSeed.forbiddenPaths) {
|
|
98
|
+
includedForbidden.push(...includedSeed.forbiddenPaths);
|
|
99
|
+
}
|
|
100
|
+
if (includedSeed.unknownKeys)
|
|
101
|
+
includedUnknown.push(...includedSeed.unknownKeys);
|
|
102
|
+
}
|
|
103
|
+
// Unknown keys from this file, plus everything its includes reported. An included
|
|
104
|
+
// seed's stray key is as silently dropped as the root's, so the queue is unioned.
|
|
105
|
+
const ownUnknown = unknownSeedKeys(parsed);
|
|
106
|
+
const own = parsed;
|
|
107
|
+
const managedPaths = [
|
|
108
|
+
...includedManaged,
|
|
109
|
+
...asStringArray(own.managed_paths, "managed_paths", absPath),
|
|
110
|
+
];
|
|
111
|
+
const forbiddenPaths = includedForbidden.length > 0 || Array.isArray(own.forbidden_paths)
|
|
112
|
+
? [
|
|
113
|
+
...includedForbidden,
|
|
114
|
+
...asStringArray(own.forbidden_paths, "forbidden_paths", absPath, true),
|
|
115
|
+
]
|
|
116
|
+
: undefined;
|
|
117
|
+
const scaffoldSourcePaths = Array.isArray(own.scaffold_source_paths)
|
|
118
|
+
? asStringArray(own.scaffold_source_paths, "scaffold_source_paths", absPath, true)
|
|
119
|
+
: undefined;
|
|
120
|
+
const seed = {
|
|
121
|
+
version: asString(own.version, "version", absPath),
|
|
122
|
+
scope: asString(own.scope, "scope", absPath),
|
|
123
|
+
// forward_base optional per ADR 0176 §4.6 (profile-agnostic packs omit it)
|
|
124
|
+
...(own.forward_base && {
|
|
125
|
+
forwardBase: {
|
|
126
|
+
bundle: asString(own.forward_base.bundle, "forward_base.bundle", absPath),
|
|
127
|
+
minimumVersion: asString(own.forward_base.minimum_version, "forward_base.minimum_version", absPath),
|
|
128
|
+
sourceRepo: own.forward_base.source_repo
|
|
129
|
+
? asString(own.forward_base.source_repo, "forward_base.source_repo", absPath)
|
|
130
|
+
: "sdd-method",
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
// vendor_base present only on a vendor-base pack (M-ADR 0175); the
|
|
134
|
+
// profile-agnostic/forward_base contradiction is caught in validateSeed.
|
|
135
|
+
...(own.vendor_base && {
|
|
136
|
+
vendorBase: {
|
|
137
|
+
vendor: asString(own.vendor_base.vendor, "vendor_base.vendor", absPath),
|
|
138
|
+
vendorVersion: asString(own.vendor_base.vendor_version, "vendor_base.vendor_version", absPath),
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
minimumSddCliVersion: asString(own.minimum_sdd_cli_version, "minimum_sdd_cli_version", absPath),
|
|
142
|
+
description: asString(own.description, "description", absPath),
|
|
143
|
+
managedPaths,
|
|
144
|
+
sourcePath: absPath,
|
|
145
|
+
...(ownUnknown.length + includedUnknown.length > 0
|
|
146
|
+
? { unknownKeys: [...new Set([...includedUnknown, ...ownUnknown])].sort() }
|
|
147
|
+
: {}),
|
|
148
|
+
...(includes.length > 0 && { include: includes }),
|
|
149
|
+
...(forbiddenPaths && { forbiddenPaths }),
|
|
150
|
+
...(scaffoldSourcePaths && scaffoldSourcePaths.length > 0 && { scaffoldSourcePaths }),
|
|
151
|
+
...(own.requires_pack && {
|
|
152
|
+
requiresPack: {
|
|
153
|
+
name: asString(own.requires_pack.name, "requires_pack.name", absPath),
|
|
154
|
+
version: asString(own.requires_pack.version, "requires_pack.version", absPath),
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
return seed;
|
|
159
|
+
}
|
|
160
|
+
function asString(value, field, file) {
|
|
161
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
162
|
+
throw new Error(`Seed field "${field}" must be a non-empty string in ${file}`);
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
function asStringArray(value, field, file, allowEmpty = false) {
|
|
167
|
+
if (value === undefined || value === null) {
|
|
168
|
+
if (allowEmpty)
|
|
169
|
+
return [];
|
|
170
|
+
throw new Error(`Seed field "${field}" is required in ${file}`);
|
|
171
|
+
}
|
|
172
|
+
if (!Array.isArray(value)) {
|
|
173
|
+
throw new Error(`Seed field "${field}" must be a list in ${file}`);
|
|
174
|
+
}
|
|
175
|
+
for (const entry of value) {
|
|
176
|
+
if (typeof entry !== "string") {
|
|
177
|
+
throw new Error(`Seed field "${field}" entries must be strings in ${file}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -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,69 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { readSdd } from "./read-sdd.js";
|
|
4
|
+
import { PILOT_TEMPLATE_REL_PATHS, renderPilotKickoff, resolvePilotTemplate } from "./render-template.js";
|
|
5
|
+
export async function runPlanPilot(inputs, write) {
|
|
6
|
+
const templatePath = await resolvePilotTemplate(inputs.sddRoot);
|
|
7
|
+
if (templatePath === undefined) {
|
|
8
|
+
const msg = `[FAIL] pilot-kickoff template not found at ${join(inputs.sddRoot, PILOT_TEMPLATE_REL_PATHS[0])}. Sync the method baseline first.\n`;
|
|
9
|
+
write(msg);
|
|
10
|
+
return {
|
|
11
|
+
exitCode: 1,
|
|
12
|
+
outputPath: inputs.outputPath ?? defaultOutputPath(inputs),
|
|
13
|
+
rendered: "",
|
|
14
|
+
dryRun: inputs.dryRun === true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const snapshot = await readSdd({
|
|
18
|
+
sddRoot: inputs.sddRoot,
|
|
19
|
+
service: inputs.service,
|
|
20
|
+
capability: inputs.capability,
|
|
21
|
+
domain: inputs.domain,
|
|
22
|
+
});
|
|
23
|
+
const rendered = await renderPilotKickoff({
|
|
24
|
+
snapshot,
|
|
25
|
+
platformCliVersionHint: inputs.platformCliVersionHint ?? "0.5.0",
|
|
26
|
+
today: inputs.today ?? new Date().toISOString().slice(0, 10),
|
|
27
|
+
});
|
|
28
|
+
const outputPath = inputs.outputPath ?? defaultOutputPath(inputs);
|
|
29
|
+
const dryRun = inputs.dryRun === true;
|
|
30
|
+
if (dryRun) {
|
|
31
|
+
write(rendered);
|
|
32
|
+
write(`\n[INFO] dry-run: would write to ${outputPath}\n`);
|
|
33
|
+
return { exitCode: 0, outputPath, rendered, dryRun: true };
|
|
34
|
+
}
|
|
35
|
+
const outputAbs = resolveOutputAbs(inputs.sddRoot, outputPath);
|
|
36
|
+
if (await fileExists(outputAbs)) {
|
|
37
|
+
write(`[FAIL] refusing to overwrite existing file at ${outputAbs}. Move or rename, or re-run with --dry-run.\n`);
|
|
38
|
+
return { exitCode: 1, outputPath: outputAbs, rendered, dryRun: false };
|
|
39
|
+
}
|
|
40
|
+
await mkdir(dirname(outputAbs), { recursive: true });
|
|
41
|
+
await writeFile(outputAbs, rendered, "utf8");
|
|
42
|
+
write(`[PASS] pilot-kickoff scaffolded at ${outputAbs}\n` +
|
|
43
|
+
` aggregates: ${snapshot.repoConfig.aggregates.length}\n` +
|
|
44
|
+
` feature specs: ${snapshot.featureSpecCount}\n` +
|
|
45
|
+
` capability spec: ${snapshot.capabilitySpecPresent ? "present" : "MISSING"}\n` +
|
|
46
|
+
` traceability manifest: ${snapshot.traceabilityManifestPresent ? "present" : "MISSING"}\n`);
|
|
47
|
+
return { exitCode: 0, outputPath: outputAbs, rendered, dryRun: false };
|
|
48
|
+
}
|
|
49
|
+
function defaultOutputPath(inputs) {
|
|
50
|
+
return join("content/product/plans", `${inputs.service}-pilot-kickoff.md`);
|
|
51
|
+
}
|
|
52
|
+
function resolveOutputAbs(sddRoot, outputPath) {
|
|
53
|
+
if (outputPath.startsWith("/"))
|
|
54
|
+
return outputPath;
|
|
55
|
+
return join(sddRoot, outputPath);
|
|
56
|
+
}
|
|
57
|
+
async function fileExists(path) {
|
|
58
|
+
try {
|
|
59
|
+
await access(path);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Keep readFile import in use; some test harness entry points trigger module
|
|
67
|
+
// tree-shake warnings otherwise.
|
|
68
|
+
void readFile;
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { access, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { liveReader } from "../sdd-reader.js";
|
|
4
|
+
import { parseRepoConfigFile } from "../sdd-index/repo-config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read everything `plan --pilot` needs from the SDD filesystem: the service's
|
|
7
|
+
* repo-config manifest (domain, service_type, aggregates, dependencies,
|
|
8
|
+
* contracts), the count of feature spec files under the capability, and
|
|
9
|
+
* presence of the capability spec + traceability manifest.
|
|
10
|
+
*
|
|
11
|
+
* Repo-config parsing delegates to the shared sdd-index module so the
|
|
12
|
+
* `plan --pilot` path and the MCP dependency-graph tools see the same
|
|
13
|
+
* view. The richer ADR 0110 shape (package/version/stability) is
|
|
14
|
+
* flattened back to path-strings here to preserve the legacy
|
|
15
|
+
* `RepoConfig` contract.
|
|
16
|
+
*/
|
|
17
|
+
export async function readSdd(inputs) {
|
|
18
|
+
const { sddRoot, service, capability, domain } = inputs;
|
|
19
|
+
const manifestAbsPath = join(sddRoot, "orchestration/repo-config", domain, `${service}.yaml`);
|
|
20
|
+
const repoConfig = await readRepoConfigFor(sddRoot, manifestAbsPath);
|
|
21
|
+
const featuresDir = join(sddRoot, "docs/domains", domain, "product/features", capability);
|
|
22
|
+
const featureSpecs = await listFeatureSpecs(featuresDir);
|
|
23
|
+
const featureSpecRelativePaths = featureSpecs.map((p) => join("docs/domains", domain, "product/features", capability, p));
|
|
24
|
+
const capabilitySpecPath = join("docs/domains", domain, "product", `${capability}.md`);
|
|
25
|
+
const capabilitySpecPresent = await fileExists(join(sddRoot, capabilitySpecPath));
|
|
26
|
+
const traceabilityManifestPath = join("docs/domains", domain, "product", `${capability}.traceability.yaml`);
|
|
27
|
+
const traceabilityManifestPresent = await fileExists(join(sddRoot, traceabilityManifestPath));
|
|
28
|
+
return {
|
|
29
|
+
sddRoot,
|
|
30
|
+
service,
|
|
31
|
+
capability,
|
|
32
|
+
domain,
|
|
33
|
+
repoConfig,
|
|
34
|
+
featureSpecCount: featureSpecs.length,
|
|
35
|
+
featureSpecRelativePaths,
|
|
36
|
+
capabilitySpecPresent,
|
|
37
|
+
capabilitySpecPath,
|
|
38
|
+
traceabilityManifestPresent,
|
|
39
|
+
traceabilityManifestPath,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function readRepoConfigFor(sddRoot, manifestAbsPath) {
|
|
43
|
+
const empty = {
|
|
44
|
+
path: manifestAbsPath,
|
|
45
|
+
aggregates: [],
|
|
46
|
+
internalDependencies: [],
|
|
47
|
+
contracts: { openapi: [], proto: [], avro: [], spicedb: [], graphql: [] },
|
|
48
|
+
};
|
|
49
|
+
if (!(await fileExists(manifestAbsPath)))
|
|
50
|
+
return empty;
|
|
51
|
+
const reader = liveReader(sddRoot);
|
|
52
|
+
const relPath = relative(sddRoot, manifestAbsPath);
|
|
53
|
+
const warnings = [];
|
|
54
|
+
const node = parseRepoConfigFile(reader, relPath, warnings);
|
|
55
|
+
if (!node)
|
|
56
|
+
return empty;
|
|
57
|
+
return legacyShape(node, manifestAbsPath);
|
|
58
|
+
}
|
|
59
|
+
function legacyShape(node, sourcePath) {
|
|
60
|
+
const byKind = {
|
|
61
|
+
openapi: [],
|
|
62
|
+
proto: [],
|
|
63
|
+
avro: [],
|
|
64
|
+
spicedb: [],
|
|
65
|
+
};
|
|
66
|
+
for (const ref of node.contracts)
|
|
67
|
+
byKind[ref.kind].push(ref.path);
|
|
68
|
+
return {
|
|
69
|
+
path: sourcePath,
|
|
70
|
+
...(node.description !== undefined ? { description: node.description } : {}),
|
|
71
|
+
...(node.serviceType !== undefined ? { serviceType: node.serviceType } : {}),
|
|
72
|
+
aggregates: Array.from(node.aggregates),
|
|
73
|
+
internalDependencies: Array.from(node.internalDependencies),
|
|
74
|
+
contracts: byKind,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function listFeatureSpecs(featuresDir) {
|
|
78
|
+
if (!(await fileExists(featuresDir)))
|
|
79
|
+
return [];
|
|
80
|
+
const entries = await readdir(featuresDir);
|
|
81
|
+
const specs = [];
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (!entry.endsWith(".md"))
|
|
84
|
+
continue;
|
|
85
|
+
const full = join(featuresDir, entry);
|
|
86
|
+
const s = await stat(full);
|
|
87
|
+
if (s.isFile())
|
|
88
|
+
specs.push(entry);
|
|
89
|
+
}
|
|
90
|
+
specs.sort();
|
|
91
|
+
return specs;
|
|
92
|
+
}
|
|
93
|
+
async function fileExists(path) {
|
|
94
|
+
try {
|
|
95
|
+
await access(path);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=read-sdd.js.map
|