@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,25 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
export function detectStack(files, signals = { hasSpringController: false }) {
|
|
3
|
+
const names = files.map((f) => basename(f).toLowerCase());
|
|
4
|
+
const has = (pred) => names.some(pred);
|
|
5
|
+
const hasJavaBuild = has((n) => n === "pom.xml" || n === "build.gradle");
|
|
6
|
+
// (1) A Spring service ships as a Spring service even with a bundled IaC/serverless wrapper.
|
|
7
|
+
if (hasJavaBuild && signals.hasSpringController)
|
|
8
|
+
return "java-spring";
|
|
9
|
+
const hasServerless = has((n) => n === "serverless.yml" || n === "serverless.yaml");
|
|
10
|
+
if (hasServerless)
|
|
11
|
+
return "node-serverless";
|
|
12
|
+
const hasTerraform = files.some((f) => f.toLowerCase().endsWith(".tf"));
|
|
13
|
+
if (hasTerraform)
|
|
14
|
+
return "terraform";
|
|
15
|
+
const hasManifest = hasJavaBuild || has((n) => n === "package.json");
|
|
16
|
+
if (hasManifest)
|
|
17
|
+
return "library";
|
|
18
|
+
return "unknown";
|
|
19
|
+
}
|
|
20
|
+
/** Class/type-level annotation that marks a Spring web controller. */
|
|
21
|
+
const SPRING_CONTROLLER_RE = /@(Rest)?Controller\b|@RequestMapping\b/;
|
|
22
|
+
export function fileLooksLikeSpringController(source) {
|
|
23
|
+
return SPRING_CONTROLLER_RE.test(source);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
2
|
+
// Serialisers for an ApiSurface. `toYaml` produces the contracts/api-surface.yaml document (keys in a
|
|
3
|
+
// stable, human-readable order; empty blocks omitted so non-endpoint stacks don't carry noise).
|
|
4
|
+
// `toMarkdownBlock` produces the `## External Interfaces` + `## Dependency Graph` documentation block
|
|
5
|
+
// the reverse rubric reads.
|
|
6
|
+
export function toYaml(surface) {
|
|
7
|
+
// Build an ordered plain object so the YAML key order is deterministic and reads top-down.
|
|
8
|
+
const doc = {
|
|
9
|
+
repo: surface.repo,
|
|
10
|
+
profile: surface.profile,
|
|
11
|
+
component_type: surface.component_type,
|
|
12
|
+
domain: surface.domain,
|
|
13
|
+
stack: surface.stack,
|
|
14
|
+
};
|
|
15
|
+
if (surface.source_snapshot)
|
|
16
|
+
doc["source_snapshot"] = surface.source_snapshot;
|
|
17
|
+
doc["generated_by"] = surface.generated_by;
|
|
18
|
+
if (surface.endpoints.length > 0) {
|
|
19
|
+
doc["endpoints"] = surface.endpoints.map((e) => ({
|
|
20
|
+
method: e.method,
|
|
21
|
+
path: e.path,
|
|
22
|
+
auth: e.auth,
|
|
23
|
+
handler: e.handler,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
doc["dependencies"] = {
|
|
27
|
+
downstream: surface.dependencies.downstream.map((d) => ({
|
|
28
|
+
target: d.target,
|
|
29
|
+
kind: d.kind,
|
|
30
|
+
detail: d.detail,
|
|
31
|
+
load_bearing: d.load_bearing,
|
|
32
|
+
})),
|
|
33
|
+
upstream: surface.dependencies.upstream,
|
|
34
|
+
};
|
|
35
|
+
if (surface.external_contracts.length > 0) {
|
|
36
|
+
doc["external_contracts"] = surface.external_contracts;
|
|
37
|
+
}
|
|
38
|
+
const deployment = {
|
|
39
|
+
runtime: surface.deployment.runtime,
|
|
40
|
+
shared_infra: surface.deployment.shared_infra,
|
|
41
|
+
};
|
|
42
|
+
if (surface.deployment.provisioned_resources && surface.deployment.provisioned_resources.length > 0) {
|
|
43
|
+
deployment["provisioned_resources"] = surface.deployment.provisioned_resources;
|
|
44
|
+
}
|
|
45
|
+
if (surface.deployment.notes && surface.deployment.notes.length > 0) {
|
|
46
|
+
deployment["notes"] = surface.deployment.notes;
|
|
47
|
+
}
|
|
48
|
+
doc["deployment"] = deployment;
|
|
49
|
+
if (surface.public_api) {
|
|
50
|
+
const pub = {};
|
|
51
|
+
if (surface.public_api.package_name)
|
|
52
|
+
pub["package_name"] = surface.public_api.package_name;
|
|
53
|
+
pub["exports"] = surface.public_api.exports;
|
|
54
|
+
doc["public_api"] = pub;
|
|
55
|
+
}
|
|
56
|
+
doc["consumed_by"] = surface.consumed_by;
|
|
57
|
+
return stringifyYaml(doc, { lineWidth: 0 });
|
|
58
|
+
}
|
|
59
|
+
export function toMarkdownBlock(surface) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
lines.push("## External Interfaces");
|
|
62
|
+
lines.push("");
|
|
63
|
+
if (surface.endpoints.length > 0) {
|
|
64
|
+
lines.push("| Method | Path | Auth | Handler |");
|
|
65
|
+
lines.push("| --- | --- | --- | --- |");
|
|
66
|
+
for (const e of surface.endpoints) {
|
|
67
|
+
lines.push(`| ${e.method} | ${e.path} | ${e.auth} | ${e.handler} |`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lines.push(`_No HTTP endpoints (${surface.stack} stack)._`);
|
|
72
|
+
}
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push("## Dependency Graph");
|
|
75
|
+
lines.push("");
|
|
76
|
+
const down = surface.dependencies.downstream;
|
|
77
|
+
if (down.length > 0) {
|
|
78
|
+
lines.push("**Downstream:**");
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push("| Target | Kind | Detail | Load-bearing |");
|
|
81
|
+
lines.push("| --- | --- | --- | --- |");
|
|
82
|
+
for (const d of down) {
|
|
83
|
+
lines.push(`| ${d.target} | ${d.kind} | ${d.detail} | ${d.load_bearing ? "yes" : "no"} |`);
|
|
84
|
+
}
|
|
85
|
+
lines.push("");
|
|
86
|
+
}
|
|
87
|
+
const up = surface.dependencies.upstream;
|
|
88
|
+
lines.push(`**Upstream:** ${up.length > 0 ? up.join(", ") : "_unknown_"}`);
|
|
89
|
+
lines.push("");
|
|
90
|
+
if (surface.external_contracts.length > 0) {
|
|
91
|
+
lines.push("**External contracts:** " + surface.external_contracts.join(", "));
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
94
|
+
lines.push("## Deployment");
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push(`- Runtime: ${surface.deployment.runtime || "_unknown_"}`);
|
|
97
|
+
if (surface.deployment.shared_infra.length > 0) {
|
|
98
|
+
lines.push(`- Shared infra: ${surface.deployment.shared_infra.join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
if (surface.deployment.provisioned_resources && surface.deployment.provisioned_resources.length > 0) {
|
|
101
|
+
const summary = surface.deployment.provisioned_resources
|
|
102
|
+
.map((r) => `${r.type} ×${r.count}`)
|
|
103
|
+
.join(", ");
|
|
104
|
+
lines.push(`- Provisioned: ${summary}`);
|
|
105
|
+
}
|
|
106
|
+
lines.push("");
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export function diskFileSet(root, files) {
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
return {
|
|
6
|
+
root,
|
|
7
|
+
files,
|
|
8
|
+
async read(relPath) {
|
|
9
|
+
const cached = cache.get(relPath);
|
|
10
|
+
if (cached !== undefined)
|
|
11
|
+
return cached;
|
|
12
|
+
let content = "";
|
|
13
|
+
try {
|
|
14
|
+
content = await readFile(join(root, relPath), "utf-8");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
content = "";
|
|
18
|
+
}
|
|
19
|
+
cache.set(relPath, content);
|
|
20
|
+
return content;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Build a FileSet from an in-memory `{ path: content }` map — for tests. */
|
|
25
|
+
export function inMemoryFileSet(entries, root = "/virtual") {
|
|
26
|
+
return {
|
|
27
|
+
root,
|
|
28
|
+
files: Object.keys(entries),
|
|
29
|
+
async read(relPath) {
|
|
30
|
+
return entries[relPath] ?? "";
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** First file (by suffix match, case-insensitive) or null. */
|
|
35
|
+
export function findBySuffix(files, suffix) {
|
|
36
|
+
const lower = suffix.toLowerCase();
|
|
37
|
+
return files.find((f) => f.toLowerCase().endsWith(lower)) ?? null;
|
|
38
|
+
}
|
|
39
|
+
export function allBySuffix(files, suffix) {
|
|
40
|
+
const lower = suffix.toLowerCase();
|
|
41
|
+
return files.filter((f) => f.toLowerCase().endsWith(lower));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=file-set.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { basename, join, relative } from "node:path";
|
|
3
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
4
|
+
import { analyzeLibrary } from "./analyzers/library.js";
|
|
5
|
+
import { analyzeServerless } from "./analyzers/serverless.js";
|
|
6
|
+
import { analyzeSpring } from "./analyzers/spring.js";
|
|
7
|
+
import { analyzeTerraform } from "./analyzers/terraform.js";
|
|
8
|
+
import { detectStack, fileLooksLikeSpringController } from "./detect.js";
|
|
9
|
+
import { diskFileSet } from "./file-set.js";
|
|
10
|
+
import { emptyDependencies, emptyDeployment, } from "./types.js";
|
|
11
|
+
// Directories never worth walking — build output, vcs, deps.
|
|
12
|
+
const SKIP_DIRS = new Set([
|
|
13
|
+
".git",
|
|
14
|
+
"node_modules",
|
|
15
|
+
"target",
|
|
16
|
+
"dist",
|
|
17
|
+
"build",
|
|
18
|
+
".terraform",
|
|
19
|
+
".gradle",
|
|
20
|
+
".idea",
|
|
21
|
+
".mvn",
|
|
22
|
+
]);
|
|
23
|
+
export async function analyzeRepo(opts) {
|
|
24
|
+
const files = await walkRepo(opts.source);
|
|
25
|
+
const fileSet = diskFileSet(opts.source, files);
|
|
26
|
+
const stack = await detect(fileSet);
|
|
27
|
+
const analyzed = await dispatch(stack, fileSet);
|
|
28
|
+
const { repo, snapshot } = resolveRepoIdentity(opts.source, opts.repo);
|
|
29
|
+
const result = {
|
|
30
|
+
repo,
|
|
31
|
+
profile: opts.profile ?? "reverse",
|
|
32
|
+
component_type: analyzed.component_type,
|
|
33
|
+
domain: opts.domain ?? "",
|
|
34
|
+
stack: analyzed.stack,
|
|
35
|
+
...(snapshot ? { source_snapshot: snapshot } : {}),
|
|
36
|
+
generated_by: `sdd-cli api-surface v${getSddCliVersion()}`,
|
|
37
|
+
endpoints: analyzed.endpoints,
|
|
38
|
+
dependencies: analyzed.dependencies,
|
|
39
|
+
external_contracts: analyzed.external_contracts,
|
|
40
|
+
deployment: analyzed.deployment,
|
|
41
|
+
...(analyzed.public_api ? { public_api: analyzed.public_api } : {}),
|
|
42
|
+
consumed_by: analyzed.consumed_by ?? [],
|
|
43
|
+
};
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
async function detect(fileSet) {
|
|
47
|
+
// A Java build may be wrapped in a deployment layer — a bundled `terraform/` subdir or a
|
|
48
|
+
// `serverless.yml` for Lambda packaging. Scan for a Spring controller whenever a Java build
|
|
49
|
+
// is present (not only when path-only detection already says "library"), so the wrapper does
|
|
50
|
+
// not mask a real Spring service. detectStack then gives java-spring precedence (W1.1 fix).
|
|
51
|
+
const hasJavaBuild = fileSet.files.some((f) => {
|
|
52
|
+
const b = f.split("/").pop()?.toLowerCase();
|
|
53
|
+
return b === "pom.xml" || b === "build.gradle";
|
|
54
|
+
});
|
|
55
|
+
if (!hasJavaBuild)
|
|
56
|
+
return detectStack(fileSet.files);
|
|
57
|
+
// A Java build is present — decide spring-vs-library by looking for a controller.
|
|
58
|
+
const javaFiles = fileSet.files.filter((f) => f.toLowerCase().endsWith(".java") && !/(^|\/)(target|build)\//.test(f));
|
|
59
|
+
let hasSpringController = false;
|
|
60
|
+
for (const f of javaFiles) {
|
|
61
|
+
const src = await fileSet.read(f);
|
|
62
|
+
if (fileLooksLikeSpringController(src) && /@(Rest)?Controller\b/.test(src)) {
|
|
63
|
+
hasSpringController = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return detectStack(fileSet.files, { hasSpringController });
|
|
68
|
+
}
|
|
69
|
+
async function dispatch(stack, fileSet) {
|
|
70
|
+
switch (stack) {
|
|
71
|
+
case "node-serverless":
|
|
72
|
+
return analyzeServerless(fileSet);
|
|
73
|
+
case "java-spring":
|
|
74
|
+
return analyzeSpring(fileSet);
|
|
75
|
+
case "terraform":
|
|
76
|
+
return analyzeTerraform(fileSet);
|
|
77
|
+
case "library":
|
|
78
|
+
return analyzeLibrary(fileSet);
|
|
79
|
+
case "unknown":
|
|
80
|
+
return {
|
|
81
|
+
stack: "unknown",
|
|
82
|
+
component_type: "unknown",
|
|
83
|
+
endpoints: [],
|
|
84
|
+
dependencies: emptyDependencies(),
|
|
85
|
+
external_contracts: [],
|
|
86
|
+
deployment: emptyDeployment(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Derive `{ repo, snapshot }` from the source dir name. A `<repo>-master@<sha>` directory yields the
|
|
92
|
+
* repo slug and the full `<repo>-master@<sha>` snapshot string. An explicit `--repo` overrides the slug
|
|
93
|
+
* but the snapshot is still taken from the dir name when it matches the pattern.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveRepoIdentity(source, repoOverride) {
|
|
96
|
+
const dir = basename(source.replace(/\/+$/, ""));
|
|
97
|
+
const m = dir.match(/^(.+?)-master@([0-9a-f]+)$/i);
|
|
98
|
+
if (m) {
|
|
99
|
+
return { repo: repoOverride ?? m[1], snapshot: dir };
|
|
100
|
+
}
|
|
101
|
+
return { repo: repoOverride ?? dir };
|
|
102
|
+
}
|
|
103
|
+
async function walkRepo(root) {
|
|
104
|
+
const out = [];
|
|
105
|
+
async function walk(dir) {
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (entry.name.startsWith(".") && SKIP_DIRS.has(entry.name))
|
|
115
|
+
continue;
|
|
116
|
+
const full = join(dir, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
if (SKIP_DIRS.has(entry.name))
|
|
119
|
+
continue;
|
|
120
|
+
await walk(full);
|
|
121
|
+
}
|
|
122
|
+
else if (entry.isFile()) {
|
|
123
|
+
out.push(toPosix(relative(root, full)));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
await walk(root);
|
|
128
|
+
out.sort();
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function toPosix(p) {
|
|
132
|
+
return p.split("\\").join("/");
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Result interfaces for `sdd-cli api-surface` (W1). The shape EXTENDS the existing
|
|
2
|
+
// `contracts/api-surface.yaml` keys (repo, profile, component_type, domain, public_api.exports,
|
|
3
|
+
// consumed_by) so emitted files round-trip with hand-authored ones — it never drops those keys.
|
|
4
|
+
//
|
|
5
|
+
// The four blocks below map 1:1 to the reverse `repo-doc-quality` rubric requirement for
|
|
6
|
+
// `interface_and_dependency_coverage = 2`:
|
|
7
|
+
// endpoints → item 1 (all endpoints with method+path+auth)
|
|
8
|
+
// dependencies → item 2 (explicit upstream/downstream graph)
|
|
9
|
+
// external_contracts → item 3 (external contracts referenced)
|
|
10
|
+
// deployment → item 4 (deployment coupling: shared infra/db/queue)
|
|
11
|
+
export function emptyDependencies() {
|
|
12
|
+
return { downstream: [], upstream: [] };
|
|
13
|
+
}
|
|
14
|
+
export function emptyDeployment() {
|
|
15
|
+
return { runtime: "", shared_infra: [] };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public facade for the briefing layer (ADR 0148).
|
|
3
|
+
*
|
|
4
|
+
* `agent-instructions.md` is the harness-neutral canonical briefing
|
|
5
|
+
* (fully method-managed; sync overwrites unconditionally). Harness
|
|
6
|
+
* wrappers (`CLAUDE.md`, `AGENTS.md`, …) are thin pointers with a
|
|
7
|
+
* `<!-- method-managed: begin/end -->` block at the top; adopter
|
|
8
|
+
* content below survives sync via the existing `writeMarkedBlockFile`
|
|
9
|
+
* machinery.
|
|
10
|
+
*/
|
|
11
|
+
export { AGENT_INSTRUCTIONS_REL_PATH, BRIEFING_TEMPLATE_ROOT, WRAPPER_BLOCK_BEGIN, WRAPPER_BLOCK_END, extractWrapperBlock, findUnresolvedPlaceholders, AGENTS_POINTER, briefingWrappers, harnessWrapperOutputPath, renderAgentInstructions, renderHarnessWrapper, renderWrapper, splitWrapperTemplate, substitute, } from "./render.js";
|
|
12
|
+
export { resolveBriefingVariables } from "./variables.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer for the briefing-layer templates per ADR 0148 + Phase 4 of
|
|
3
|
+
* `adopt-agent-instructions-briefing-layer.md`.
|
|
4
|
+
*
|
|
5
|
+
* Templates ship via the method baseline bundle and land at
|
|
6
|
+
* `<target>/orchestration/templates/briefing/...` after sync's
|
|
7
|
+
* `copyManagedFiles` step. Both `sdd-cli init` and `sdd-cli sync` read
|
|
8
|
+
* the on-disk templates and substitute the four canonical variables.
|
|
9
|
+
*
|
|
10
|
+
* Substitution is a deliberate Handlebars *subset* — `{{var}}` only,
|
|
11
|
+
* no helpers / conditionals / partials. Keeps the runtime-dep
|
|
12
|
+
* allowlist clean (no Handlebars package) and matches the template
|
|
13
|
+
* shapes shipped upstream.
|
|
14
|
+
*/
|
|
15
|
+
import { access, readFile } from "node:fs/promises";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
export const BRIEFING_TEMPLATE_ROOT = "orchestration/templates/briefing";
|
|
18
|
+
export const AGENT_INSTRUCTIONS_REL_PATH = "agent-instructions.md";
|
|
19
|
+
const HARNESS_WRAPPER_TEMPLATE = {
|
|
20
|
+
"claude-code": {
|
|
21
|
+
templateRel: "claude-code/CLAUDE.md.hbs",
|
|
22
|
+
outputRel: "CLAUDE.md",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/** Output filename per harness — `CLAUDE.md`. */
|
|
26
|
+
export function harnessWrapperOutputPath(harness) {
|
|
27
|
+
return HARNESS_WRAPPER_TEMPLATE[harness].outputRel;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The AGENTS.md pointer (M-ADR 0220 §4.6): a few lines directing any agent tool that reads a root
|
|
31
|
+
* AGENTS.md to agent-instructions.md. Written for every SDD and code repository with no flag, and
|
|
32
|
+
* rendered and refreshed exactly as the CLAUDE.md wrapper is. A pointer, not support for another
|
|
33
|
+
* harness. Relative to the briefing template root; code repositories read it under code-repo/.
|
|
34
|
+
*/
|
|
35
|
+
export const AGENTS_POINTER = { templateRel: "AGENTS.md.hbs", outputRel: "AGENTS.md" };
|
|
36
|
+
/** Every marker-merged wrapper a repository gets: the harness's own, then the AGENTS.md pointer. */
|
|
37
|
+
export function briefingWrappers(harness) {
|
|
38
|
+
return [HARNESS_WRAPPER_TEMPLATE[harness], AGENTS_POINTER];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Marker pair the wrapper templates use to delimit the method-managed
|
|
42
|
+
* region. The wrapper sync path slices this region out of the freshly
|
|
43
|
+
* rendered template and hands it to `writeMarkedBlockFile`.
|
|
44
|
+
*/
|
|
45
|
+
export const WRAPPER_BLOCK_BEGIN = "<!-- method-managed: begin -->";
|
|
46
|
+
export const WRAPPER_BLOCK_END = "<!-- method-managed: end -->";
|
|
47
|
+
/**
|
|
48
|
+
* Substitute the four canonical variables into a template string. Any
|
|
49
|
+
* `{{var}}` placeholder whose name is not in the variable set is left
|
|
50
|
+
* verbatim — callers may pre-check for leftovers if strict rendering
|
|
51
|
+
* is desired.
|
|
52
|
+
*/
|
|
53
|
+
export function substitute(template, vars) {
|
|
54
|
+
const map = {
|
|
55
|
+
sdd_name: vars.sdd_name,
|
|
56
|
+
repo_kind: vars.repo_kind,
|
|
57
|
+
baseline_version: vars.baseline_version,
|
|
58
|
+
sync_date: vars.sync_date,
|
|
59
|
+
};
|
|
60
|
+
return template.replace(/\{\{\s*([a-z_][a-z0-9_]*)\s*\}\}/g, (full, key) => {
|
|
61
|
+
return key in map ? map[key] : full;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find any unresolved `{{var}}` placeholders. Returns the set of
|
|
66
|
+
* placeholder names (de-duplicated). Callers use this to decide
|
|
67
|
+
* whether to warn or hard-fail on a stale template.
|
|
68
|
+
*/
|
|
69
|
+
export function findUnresolvedPlaceholders(rendered) {
|
|
70
|
+
const found = new Set();
|
|
71
|
+
for (const m of rendered.matchAll(/\{\{\s*([a-z_][a-z0-9_]*)\s*\}\}/g)) {
|
|
72
|
+
found.add(m[1]);
|
|
73
|
+
}
|
|
74
|
+
return [...found];
|
|
75
|
+
}
|
|
76
|
+
export async function renderAgentInstructions(targetRepo, vars) {
|
|
77
|
+
const templatePath = join(targetRepo, BRIEFING_TEMPLATE_ROOT, "agent-instructions.md.hbs");
|
|
78
|
+
return renderFromPath(templatePath, vars);
|
|
79
|
+
}
|
|
80
|
+
export async function renderHarnessWrapper(targetRepo, harness, vars) {
|
|
81
|
+
return renderWrapper(targetRepo, HARNESS_WRAPPER_TEMPLATE[harness].templateRel, vars);
|
|
82
|
+
}
|
|
83
|
+
/** Render one wrapper template from the briefing root; null when the baseline does not ship it. */
|
|
84
|
+
export async function renderWrapper(targetRepo, templateRel, vars) {
|
|
85
|
+
return renderFromPath(join(targetRepo, BRIEFING_TEMPLATE_ROOT, templateRel), vars);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Split a fully-rendered wrapper template at the method-managed
|
|
89
|
+
* markers. The sync flow merges `block` into the existing wrapper and
|
|
90
|
+
* uses `pre`/`post` as scaffold-fill for blank regions around it.
|
|
91
|
+
*
|
|
92
|
+
* Throws when the markers aren't both found — that's a template-
|
|
93
|
+
* authoring bug, not an adopter-side issue.
|
|
94
|
+
*/
|
|
95
|
+
export function splitWrapperTemplate(rendered) {
|
|
96
|
+
const beginIdx = rendered.indexOf(WRAPPER_BLOCK_BEGIN);
|
|
97
|
+
const endIdx = rendered.indexOf(WRAPPER_BLOCK_END, beginIdx);
|
|
98
|
+
if (beginIdx === -1 || endIdx === -1) {
|
|
99
|
+
throw new Error("Wrapper template missing method-managed markers: " +
|
|
100
|
+
`expected ${WRAPPER_BLOCK_BEGIN} ... ${WRAPPER_BLOCK_END} (template-authoring bug)`);
|
|
101
|
+
}
|
|
102
|
+
const blockEnd = endIdx + WRAPPER_BLOCK_END.length;
|
|
103
|
+
return {
|
|
104
|
+
pre: rendered.slice(0, beginIdx),
|
|
105
|
+
block: rendered.slice(beginIdx, blockEnd),
|
|
106
|
+
post: rendered.slice(blockEnd),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Extract the method-managed slice (begin marker through end marker,
|
|
111
|
+
* inclusive) from a fully-rendered wrapper template.
|
|
112
|
+
*/
|
|
113
|
+
export function extractWrapperBlock(rendered) {
|
|
114
|
+
return splitWrapperTemplate(rendered).block;
|
|
115
|
+
}
|
|
116
|
+
async function renderFromPath(templatePath, vars) {
|
|
117
|
+
if (!(await fileExists(templatePath)))
|
|
118
|
+
return null;
|
|
119
|
+
const raw = await readFile(templatePath, "utf8");
|
|
120
|
+
const content = substitute(raw, vars);
|
|
121
|
+
return { content, unresolved: findUnresolvedPlaceholders(content) };
|
|
122
|
+
}
|
|
123
|
+
async function fileExists(path) {
|
|
124
|
+
try {
|
|
125
|
+
await access(path);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable resolution for the briefing layer per ADR 0148 §4.1 + the
|
|
3
|
+
* Phase 4 setup notes in `adopt-agent-instructions-briefing-layer.md`.
|
|
4
|
+
*
|
|
5
|
+
* The four variables are sourced from target-repo state:
|
|
6
|
+
*
|
|
7
|
+
* sdd_name basename(targetRepo) — overridden by
|
|
8
|
+
* `.sdd/sdd-manifest.yaml::sdd_id` when present.
|
|
9
|
+
* repo_kind `.sdd-repo-kind` content (platform-product /
|
|
10
|
+
* application-product / integration-product /
|
|
11
|
+
* reverse-corpus).
|
|
12
|
+
* baseline_version forward-base bundle's `bundle_version` from
|
|
13
|
+
* `method-baseline.lock`.
|
|
14
|
+
* sync_date caller-supplied ISO date (today for init/sync).
|
|
15
|
+
*
|
|
16
|
+
* No new YAML parser added — the manifest is a single optional scalar
|
|
17
|
+
* read; existing scalar-line parser in src/lib/bundle.ts is the
|
|
18
|
+
* precedent for this style.
|
|
19
|
+
*/
|
|
20
|
+
import { access, readFile } from "node:fs/promises";
|
|
21
|
+
import { basename, join } from "node:path";
|
|
22
|
+
import { readLockFile } from "../lock-file.js";
|
|
23
|
+
import { readRepoKind } from "../repo-kind.js";
|
|
24
|
+
export async function resolveBriefingVariables(opts) {
|
|
25
|
+
const sdd_name = await resolveSddName(opts.targetRepo);
|
|
26
|
+
const repo_kind = opts.repoKindOverride ?? (await requireRepoKind(opts.targetRepo));
|
|
27
|
+
const baseline_version = opts.baselineVersionOverride ??
|
|
28
|
+
(await resolveBaselineVersion(opts.targetRepo));
|
|
29
|
+
const sync_date = opts.syncDate ?? todayIso();
|
|
30
|
+
return { sdd_name, repo_kind, baseline_version, sync_date };
|
|
31
|
+
}
|
|
32
|
+
async function resolveSddName(targetRepo) {
|
|
33
|
+
const fromManifest = await tryReadSddManifestId(targetRepo);
|
|
34
|
+
if (fromManifest !== null)
|
|
35
|
+
return fromManifest;
|
|
36
|
+
return basename(targetRepo);
|
|
37
|
+
}
|
|
38
|
+
async function tryReadSddManifestId(targetRepo) {
|
|
39
|
+
const path = join(targetRepo, ".sdd", "sdd-manifest.yaml");
|
|
40
|
+
if (!(await fileExists(path)))
|
|
41
|
+
return null;
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = await readFile(path, "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// Single-scalar line parse, matching bundle.ts style. The catalogue
|
|
50
|
+
// surface uses a full YAML loader elsewhere; here we only want one
|
|
51
|
+
// optional field, so this stays dependency-light.
|
|
52
|
+
for (const line of raw.split("\n")) {
|
|
53
|
+
const m = /^sdd_id: (.+)$/.exec(line.trim());
|
|
54
|
+
if (m) {
|
|
55
|
+
const value = m[1].trim().replace(/^["']|["']$/g, "");
|
|
56
|
+
if (value.length > 0)
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
async function requireRepoKind(targetRepo) {
|
|
63
|
+
const kind = await readRepoKind(targetRepo);
|
|
64
|
+
if (!kind) {
|
|
65
|
+
throw new Error(`Cannot resolve briefing variables: .sdd-repo-kind missing at ${targetRepo}`);
|
|
66
|
+
}
|
|
67
|
+
return kind;
|
|
68
|
+
}
|
|
69
|
+
async function resolveBaselineVersion(targetRepo) {
|
|
70
|
+
const lock = await readLockFile(targetRepo);
|
|
71
|
+
if (!lock) {
|
|
72
|
+
throw new Error(`Cannot resolve baseline_version: method-baseline.lock missing at ${targetRepo}`);
|
|
73
|
+
}
|
|
74
|
+
const forwardBase = lock.bundles.find((b) => b.isForwardBase);
|
|
75
|
+
if (!forwardBase) {
|
|
76
|
+
throw new Error(`Cannot resolve baseline_version: no forward-base bundle in method-baseline.lock at ${targetRepo}`);
|
|
77
|
+
}
|
|
78
|
+
return forwardBase.bundleVersion;
|
|
79
|
+
}
|
|
80
|
+
function todayIso() {
|
|
81
|
+
return new Date().toISOString().slice(0, 10);
|
|
82
|
+
}
|
|
83
|
+
async function fileExists(path) {
|
|
84
|
+
try {
|
|
85
|
+
await access(path);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=variables.js.map
|