archctx 0.9.1 → 0.9.2
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/assets/catalog.yaml +2 -2
- package/bin/archctx.mjs +639 -119
- package/package.json +1 -1
- package/schemas/repo/architecture-node.schema.json +33 -1
- package/schemas/repo/architecture-relation.schema.json +1 -1
- package/schemas/runtime/capability-dependencies.schema.json +1 -1
- package/schemas/runtime/projection-manifest.schema.json +2 -0
- package/schemas/runtime/projection-target.schema.json +1 -1
package/bin/archctx.mjs
CHANGED
|
@@ -2377,8 +2377,9 @@ var init_failure = __esm(() => {
|
|
|
2377
2377
|
"model-missing": { code: "AC_MODEL_ADOPTION_REQUIRED", message: "Architecture debt requires an adopted model.", fields: ["checks"] },
|
|
2378
2378
|
"changed-path-invalid": { code: "AC_SCHEMA_INVALID", message: "Changed paths must be an array of repository-relative POSIX paths.", fields: ["checks"] },
|
|
2379
2379
|
"debt-report-invalid": { code: "AC_SCHEMA_INVALID", message: "Architecture debt evidence violates its contract.", fields: ["checks"] },
|
|
2380
|
-
"projection-manifest-upgrade-required": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest requires supported re-baselining." },
|
|
2380
|
+
"projection-manifest-upgrade-required": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest requires supported re-baselining. First restore the original model if missing; the manifest digest cannot recover it. With the reviewed model available, follow docs/runbooks/architecture-documentation-projections.md before running archctx docs apply --approved --profile <default|repo-harness/v1>." },
|
|
2381
2381
|
"projection-manifest-invalid": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest violates its contract." },
|
|
2382
|
+
"architecture-model-ignored": { code: "AC_PRECONDITION_FAILED", action: "commit-architecture-model", message: "Git ignore rules exclude architecture model paths, so the model cannot be committed and a manifest digest cannot rebuild it. Remove or narrow the rule, commit the model, then retry adoption. The diagnostic command prints each deciding rule.", facts: { pathCount: "uint", paths: "optional:diagnosticPaths", commands: "optional:diagnosticCommands", omittedPathCount: "uint" } },
|
|
2382
2383
|
"projection-footprint-uncovered": { code: "AC_PRECONDITION_FAILED", message: "Declared source footprints lack a projection target." },
|
|
2383
2384
|
"index-missing": { code: "AC_CODE_FACTS_UNAVAILABLE", message: "Required code facts have no available index. Run codegraph init." },
|
|
2384
2385
|
"code-facts-not-ready": { code: "AC_CODE_FACTS_NOT_READY", message: "Repository code facts are not ready. Run archctx doctor --json and follow the reported action.", facts: { issues: "codeFactsIssues" } },
|
|
@@ -2816,6 +2817,89 @@ var init_failure = __esm(() => {
|
|
|
2816
2817
|
};
|
|
2817
2818
|
});
|
|
2818
2819
|
|
|
2820
|
+
// packages/contracts/src/runtime-topology.ts
|
|
2821
|
+
function isRuntimeTopologyNodeKind(kind) {
|
|
2822
|
+
return typeof kind === "string" && RUNTIME_TOPOLOGY_NODE_KINDS.includes(kind);
|
|
2823
|
+
}
|
|
2824
|
+
function runtimeTopologyModelIssues(model) {
|
|
2825
|
+
const issues = [];
|
|
2826
|
+
const nodesById = new Map;
|
|
2827
|
+
for (const node of model.nodes)
|
|
2828
|
+
if (typeof node.id === "string")
|
|
2829
|
+
nodesById.set(node.id, node);
|
|
2830
|
+
const namespacesByProcess = new Map;
|
|
2831
|
+
const mountersByMount = new Map;
|
|
2832
|
+
for (const relation of model.relations) {
|
|
2833
|
+
if (typeof relation.id !== "string" || typeof relation.kind !== "string" || !Object.hasOwn(RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS, relation.kind))
|
|
2834
|
+
continue;
|
|
2835
|
+
const rule = RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS[relation.kind];
|
|
2836
|
+
let endpointsValid = true;
|
|
2837
|
+
for (const field of ["source", "target"]) {
|
|
2838
|
+
const endpoint = relation[field];
|
|
2839
|
+
const node = typeof endpoint === "string" ? nodesById.get(endpoint) : undefined;
|
|
2840
|
+
if (!node) {
|
|
2841
|
+
issues.push({ entityId: relation.id, field, invariant: "topology-relation-endpoint-missing" });
|
|
2842
|
+
endpointsValid = false;
|
|
2843
|
+
} else if (!rule[field].includes(node.kind)) {
|
|
2844
|
+
issues.push({ entityId: relation.id, field, invariant: field === "source" ? "topology-relation-source-kind-invalid" : "topology-relation-target-kind-invalid" });
|
|
2845
|
+
endpointsValid = false;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
if (!endpointsValid)
|
|
2849
|
+
continue;
|
|
2850
|
+
const source = relation.source;
|
|
2851
|
+
const target = relation.target;
|
|
2852
|
+
if (relation.kind === "runs-in")
|
|
2853
|
+
namespacesByProcess.set(source, [...namespacesByProcess.get(source) ?? [], target]);
|
|
2854
|
+
if (relation.kind === "mounts")
|
|
2855
|
+
mountersByMount.set(target, [...mountersByMount.get(target) ?? [], source]);
|
|
2856
|
+
}
|
|
2857
|
+
for (const [processId, namespaces] of namespacesByProcess) {
|
|
2858
|
+
if (new Set(namespaces).size > 1)
|
|
2859
|
+
issues.push({ entityId: processId, field: "runs-in", invariant: "topology-process-namespace-ambiguous" });
|
|
2860
|
+
}
|
|
2861
|
+
for (const [mountId, mounters] of mountersByMount) {
|
|
2862
|
+
if (new Set(mounters).size > 1)
|
|
2863
|
+
issues.push({ entityId: mountId, field: "mounts", invariant: "topology-mount-mounter-ambiguous" });
|
|
2864
|
+
}
|
|
2865
|
+
for (const [id, node] of nodesById) {
|
|
2866
|
+
if (node.kind !== "namespace" || node.parent === undefined)
|
|
2867
|
+
continue;
|
|
2868
|
+
const parent = typeof node.parent === "string" ? nodesById.get(node.parent) : undefined;
|
|
2869
|
+
if (!parent) {
|
|
2870
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-missing" });
|
|
2871
|
+
continue;
|
|
2872
|
+
}
|
|
2873
|
+
if (parent.kind !== "namespace") {
|
|
2874
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-kind-invalid" });
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
const seen = new Set([id]);
|
|
2878
|
+
let cursor = parent;
|
|
2879
|
+
while (cursor && cursor.kind === "namespace" && typeof cursor.id === "string") {
|
|
2880
|
+
if (seen.has(cursor.id)) {
|
|
2881
|
+
issues.push({ entityId: id, field: "parent", invariant: "topology-namespace-parent-cycle" });
|
|
2882
|
+
break;
|
|
2883
|
+
}
|
|
2884
|
+
seen.add(cursor.id);
|
|
2885
|
+
cursor = typeof cursor.parent === "string" ? nodesById.get(cursor.parent) : undefined;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return issues.sort((left, right) => compare(left.entityId, right.entityId) || compare(left.field, right.field) || compare(left.invariant, right.invariant));
|
|
2889
|
+
}
|
|
2890
|
+
function compare(left, right) {
|
|
2891
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
2892
|
+
}
|
|
2893
|
+
var RUNTIME_TOPOLOGY_NODE_KINDS, RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS;
|
|
2894
|
+
var init_runtime_topology = __esm(() => {
|
|
2895
|
+
RUNTIME_TOPOLOGY_NODE_KINDS = ["process", "namespace", "mount"];
|
|
2896
|
+
RUNTIME_TOPOLOGY_RELATION_ENDPOINT_KINDS = {
|
|
2897
|
+
spawns: { source: ["process"], target: ["process"] },
|
|
2898
|
+
"runs-in": { source: ["process"], target: ["namespace"] },
|
|
2899
|
+
mounts: { source: ["process", "namespace"], target: ["mount"] }
|
|
2900
|
+
};
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2819
2903
|
// packages/contracts/src/validator.ts
|
|
2820
2904
|
function jsonSchemaResolver(documents) {
|
|
2821
2905
|
const byId = new Map;
|
|
@@ -3159,6 +3243,10 @@ function nativeContextModelIssues(model, schemas, recordPaths) {
|
|
|
3159
3243
|
if (relation[field] !== undefined && !nodes.has(relation[field]))
|
|
3160
3244
|
add(relation, field, "relation-node-missing");
|
|
3161
3245
|
}
|
|
3246
|
+
const relations = new Map(model.relations.map((relation) => [relation.id, relation]));
|
|
3247
|
+
for (const issue of runtimeTopologyModelIssues(model)) {
|
|
3248
|
+
add(relations.get(issue.entityId) ?? nodes.get(issue.entityId) ?? issue.entityId, issue.field, issue.invariant);
|
|
3249
|
+
}
|
|
3162
3250
|
for (const flow of model.flows) {
|
|
3163
3251
|
if (nodes.get(flow.capabilityId)?.kind !== "capability")
|
|
3164
3252
|
add(flow, "capabilityId", "flow-capability-missing");
|
|
@@ -3201,6 +3289,7 @@ var NATIVE_CONTEXT_SCHEMA_VERSION = "archcontext.native-context-export/v1", NATI
|
|
|
3201
3289
|
var init_native_context = __esm(() => {
|
|
3202
3290
|
init_failure();
|
|
3203
3291
|
init_schema();
|
|
3292
|
+
init_runtime_topology();
|
|
3204
3293
|
init_validator();
|
|
3205
3294
|
NATIVE_CONTEXT_LIMITS = Object.freeze({
|
|
3206
3295
|
inputFiles: 1e4,
|
|
@@ -3784,6 +3873,8 @@ var init_projection_manifest = __esm(() => {
|
|
|
3784
3873
|
"diagram-mermaid": true,
|
|
3785
3874
|
"diagram-structurizr": true,
|
|
3786
3875
|
"diagram-likec4": true,
|
|
3876
|
+
"diagram-runtime-topology": true,
|
|
3877
|
+
"runtime-topology-summary": true,
|
|
3787
3878
|
"agent-context": true
|
|
3788
3879
|
};
|
|
3789
3880
|
SCOPE_KINDS = { repository: true, entity: true, relation: true, decision: true, diagram: true, changelog: true };
|
|
@@ -3876,7 +3967,7 @@ function compareVersionTriples(left, right) {
|
|
|
3876
3967
|
}
|
|
3877
3968
|
return 0;
|
|
3878
3969
|
}
|
|
3879
|
-
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.
|
|
3970
|
+
var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.2", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v2", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
|
|
3880
3971
|
|
|
3881
3972
|
// packages/contracts/src/refactor.ts
|
|
3882
3973
|
function refactorNamespaceIssues(value, label = "product.id") {
|
|
@@ -4981,6 +5072,7 @@ var init_src = __esm(() => {
|
|
|
4981
5072
|
init_projection_writer();
|
|
4982
5073
|
init_projection_manifest();
|
|
4983
5074
|
init_refactor();
|
|
5075
|
+
init_runtime_topology();
|
|
4984
5076
|
init_schema();
|
|
4985
5077
|
init_failure();
|
|
4986
5078
|
init_validator();
|
|
@@ -17694,6 +17786,15 @@ var FIXED_TARGETS = [
|
|
|
17694
17786
|
layoutTarget("projection_target.diagram.structurizr", "diagram-structurizr", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.structurizr.json", "generated", "structurizr-json"),
|
|
17695
17787
|
layoutTarget("projection_target.diagram.likec4", "diagram-likec4", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.likec4", "generated", "likec4")
|
|
17696
17788
|
];
|
|
17789
|
+
var RUNTIME_TOPOLOGY_DIAGRAM_PATH = "docs/architecture/diagrams/runtime-topology.mmd";
|
|
17790
|
+
var RUNTIME_TOPOLOGY_SUMMARY_PATH = "docs/architecture/runtime-topology.md";
|
|
17791
|
+
var RUNTIME_TOPOLOGY_TARGETS = [
|
|
17792
|
+
layoutTarget("projection_target.diagram.runtime-topology", "diagram-runtime-topology", { kind: "diagram", id: "runtime-topology" }, RUNTIME_TOPOLOGY_DIAGRAM_PATH, "generated", "mermaid"),
|
|
17793
|
+
layoutTarget("projection_target.runtime-topology.summary", "runtime-topology-summary", { kind: "diagram", id: "runtime-topology" }, RUNTIME_TOPOLOGY_SUMMARY_PATH, "generated", "markdown")
|
|
17794
|
+
];
|
|
17795
|
+
function hasRuntimeTopologyNodes(nodes) {
|
|
17796
|
+
return nodes.some((node) => isRuntimeTopologyNodeKind(node.kind));
|
|
17797
|
+
}
|
|
17697
17798
|
function parseRepoHarnessNodeProfile(node) {
|
|
17698
17799
|
const match = /^capability\.([a-z0-9]+(?:-[a-z0-9]+)*)\.([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(node.id);
|
|
17699
17800
|
if (!match || node.kind !== "capability") {
|
|
@@ -17721,7 +17822,7 @@ function parseRepoHarnessNodeProfile(node) {
|
|
|
17721
17822
|
};
|
|
17722
17823
|
}
|
|
17723
17824
|
function entitySummaryTargetNodes(nodes, profile) {
|
|
17724
|
-
return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") :
|
|
17825
|
+
return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") : nodes.filter((node) => !isRuntimeTopologyNodeKind(node.kind));
|
|
17725
17826
|
}
|
|
17726
17827
|
function resolveArchitectureDocumentationLayout(input) {
|
|
17727
17828
|
const profile = input.profile ?? "default";
|
|
@@ -17731,7 +17832,8 @@ function resolveArchitectureDocumentationLayout(input) {
|
|
|
17731
17832
|
return layoutTarget(`projection_target.entity.${stableId(node.id)}`, "entity-summary", { kind: "entity", id: node.id, entityKind: node.kind }, path, "mixed", "markdown");
|
|
17732
17833
|
});
|
|
17733
17834
|
const relationTargets = (profile === REPO_HARNESS_PROJECTION_PROFILE ? [] : input.relations).map((relation) => layoutTarget(`projection_target.relation.${stableId(relation.id)}`, "relation-summary", { kind: "relation", id: relation.id }, `docs/architecture/relations/${pathSegment(relation.id)}.md`, "mixed", "markdown"));
|
|
17734
|
-
const
|
|
17835
|
+
const topologyTargets = hasRuntimeTopologyNodes(input.nodes) ? RUNTIME_TOPOLOGY_TARGETS : [];
|
|
17836
|
+
const targets = [FIXED_TARGETS[0], ...entityTargets, ...relationTargets, ...FIXED_TARGETS.slice(1), ...topologyTargets].map((target) => ({ ...target, path: requiredPath(target.path, target.targetId) }));
|
|
17735
17837
|
assertUniqueTargets(targets);
|
|
17736
17838
|
return {
|
|
17737
17839
|
profile,
|
|
@@ -20103,12 +20205,52 @@ function normalizeNativeModel(model) {
|
|
|
20103
20205
|
flows: [...model.flows ?? []].sort((a, b) => a.id.localeCompare(b.id))
|
|
20104
20206
|
};
|
|
20105
20207
|
}
|
|
20208
|
+
var ARCHITECTURE_NODE_LAYER_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
20209
|
+
function nativeNodeLayer(node) {
|
|
20210
|
+
const layer = node.layer;
|
|
20211
|
+
if (layer === undefined)
|
|
20212
|
+
return;
|
|
20213
|
+
if (typeof layer !== "string" || !ARCHITECTURE_NODE_LAYER_PATTERN.test(layer)) {
|
|
20214
|
+
throw new Error(`architecture-node-layer-invalid: ${node.id} (${JSON.stringify(layer)})`);
|
|
20215
|
+
}
|
|
20216
|
+
return layer;
|
|
20217
|
+
}
|
|
20218
|
+
function architectureLayerGroups(model) {
|
|
20219
|
+
const nodesByLayer = new Map;
|
|
20220
|
+
const ungroupedNodes = [];
|
|
20221
|
+
const layerByNodeId = new Map;
|
|
20222
|
+
for (const node of [...model.nodes].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
20223
|
+
const layer = nativeNodeLayer(node);
|
|
20224
|
+
if (layer === undefined) {
|
|
20225
|
+
ungroupedNodes.push(node);
|
|
20226
|
+
continue;
|
|
20227
|
+
}
|
|
20228
|
+
const members = nodesByLayer.get(layer);
|
|
20229
|
+
if (members)
|
|
20230
|
+
members.push(node);
|
|
20231
|
+
else
|
|
20232
|
+
nodesByLayer.set(layer, [node]);
|
|
20233
|
+
layerByNodeId.set(node.id, layer);
|
|
20234
|
+
}
|
|
20235
|
+
const layers = [...nodesByLayer.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([layer, nodes]) => ({ layer, nodes }));
|
|
20236
|
+
return { layers, ungroupedNodes, layerByNodeId };
|
|
20237
|
+
}
|
|
20238
|
+
function mermaidLayerId(layer) {
|
|
20239
|
+
return `layer__${layer.replace(/-/g, "_")}`;
|
|
20240
|
+
}
|
|
20106
20241
|
function exportMermaidModel(model) {
|
|
20107
20242
|
const normalized = normalizeNativeModel(model);
|
|
20243
|
+
const grouping = architectureLayerGroups(normalized);
|
|
20244
|
+
const nodeLine = (indent, node) => `${indent}${mermaidId(node.id)}["${escapeMermaid(node.name)}"]`;
|
|
20108
20245
|
const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart LR"];
|
|
20109
|
-
for (const
|
|
20110
|
-
lines.push(` ${
|
|
20246
|
+
for (const group of grouping.layers) {
|
|
20247
|
+
lines.push(` subgraph ${mermaidLayerId(group.layer)}["${escapeMermaid(group.layer)}"]`);
|
|
20248
|
+
for (const node of group.nodes)
|
|
20249
|
+
lines.push(nodeLine(" ", node));
|
|
20250
|
+
lines.push(" end");
|
|
20111
20251
|
}
|
|
20252
|
+
for (const node of grouping.ungroupedNodes)
|
|
20253
|
+
lines.push(nodeLine(" ", node));
|
|
20112
20254
|
for (const relation of normalized.relations) {
|
|
20113
20255
|
lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
|
|
20114
20256
|
}
|
|
@@ -20125,22 +20267,15 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20125
20267
|
const normalized = normalizeNativeModel(model);
|
|
20126
20268
|
const lines = [
|
|
20127
20269
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
20128
|
-
|
|
20129
|
-
" element capability",
|
|
20130
|
-
" element module",
|
|
20131
|
-
" element component",
|
|
20132
|
-
" element interface",
|
|
20133
|
-
" element datastore",
|
|
20134
|
-
" element external_system",
|
|
20135
|
-
"}",
|
|
20270
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
20136
20271
|
"",
|
|
20137
20272
|
"model {"
|
|
20138
20273
|
];
|
|
20139
20274
|
for (const node of normalized.nodes)
|
|
20140
|
-
lines.push(` ${mermaidId(node.id)} = ${
|
|
20275
|
+
lines.push(` ${mermaidId(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl(node.name)}"`);
|
|
20141
20276
|
for (const relation of normalized.relations)
|
|
20142
20277
|
lines.push(` ${mermaidId(relation.source)} -> ${mermaidId(relation.target)} "${escapeDsl(relation.kind)}"`);
|
|
20143
|
-
lines.push("}", "",
|
|
20278
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), mermaidId));
|
|
20144
20279
|
const content = `${lines.join(`
|
|
20145
20280
|
`)}
|
|
20146
20281
|
`;
|
|
@@ -20150,18 +20285,73 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20150
20285
|
files: [{ path: "docs/architecture/diagrams/architecture.likec4", content }]
|
|
20151
20286
|
};
|
|
20152
20287
|
}
|
|
20288
|
+
function likeC4SpecificationLines(nodes) {
|
|
20289
|
+
const topologyKinds = RUNTIME_TOPOLOGY_NODE_KINDS.filter((kind) => nodes.some((node) => node.kind === kind));
|
|
20290
|
+
return [
|
|
20291
|
+
"specification {",
|
|
20292
|
+
" element capability",
|
|
20293
|
+
" element module",
|
|
20294
|
+
" element component",
|
|
20295
|
+
" element interface",
|
|
20296
|
+
" element datastore",
|
|
20297
|
+
" element external_system",
|
|
20298
|
+
...topologyKinds.map((kind) => ` element ${kind}`),
|
|
20299
|
+
"}"
|
|
20300
|
+
];
|
|
20301
|
+
}
|
|
20302
|
+
function likeC4ElementKind(kind) {
|
|
20303
|
+
if (kind === "external-system")
|
|
20304
|
+
return "external_system";
|
|
20305
|
+
if (kind === "datastore")
|
|
20306
|
+
return "datastore";
|
|
20307
|
+
if (["capability", "module", "component", "interface"].includes(kind))
|
|
20308
|
+
return kind;
|
|
20309
|
+
if (isRuntimeTopologyNodeKind(kind))
|
|
20310
|
+
return kind;
|
|
20311
|
+
return "component";
|
|
20312
|
+
}
|
|
20313
|
+
function architectureStructurizrElementType(kind) {
|
|
20314
|
+
if (kind === "capability")
|
|
20315
|
+
return "Software System";
|
|
20316
|
+
if (kind === "module")
|
|
20317
|
+
return "Container";
|
|
20318
|
+
if (kind === "datastore")
|
|
20319
|
+
return "Database";
|
|
20320
|
+
if (kind === "external-system")
|
|
20321
|
+
return "External System";
|
|
20322
|
+
if (kind === "namespace")
|
|
20323
|
+
return "Deployment Node";
|
|
20324
|
+
if (kind === "process")
|
|
20325
|
+
return "Container Instance";
|
|
20326
|
+
if (kind === "mount")
|
|
20327
|
+
return "Infrastructure Node";
|
|
20328
|
+
return "Component";
|
|
20329
|
+
}
|
|
20330
|
+
function likeC4IndexViewLines(grouping, elementId) {
|
|
20331
|
+
const groupLines = grouping.layers.flatMap((group) => [
|
|
20332
|
+
` group "${escapeDsl(group.layer)}" {`,
|
|
20333
|
+
...group.nodes.map((node) => ` include ${elementId(node.id)}`),
|
|
20334
|
+
" }"
|
|
20335
|
+
]);
|
|
20336
|
+
return ["views {", " view index {", ...groupLines, " include *", " autoLayout TopBottom", " }", "}"];
|
|
20337
|
+
}
|
|
20153
20338
|
function exportDocumentationStructurizrWorkspace(model) {
|
|
20154
20339
|
const normalized = normalizeNativeModel(model);
|
|
20340
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
20155
20341
|
const workspace = {
|
|
20156
20342
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
20157
20343
|
name: "ArchContext Architecture",
|
|
20158
20344
|
model: {
|
|
20159
|
-
elements: normalized.nodes.map((node) =>
|
|
20160
|
-
|
|
20161
|
-
|
|
20162
|
-
|
|
20163
|
-
|
|
20164
|
-
|
|
20345
|
+
elements: normalized.nodes.map((node) => {
|
|
20346
|
+
const layer = layerByNodeId.get(node.id);
|
|
20347
|
+
return {
|
|
20348
|
+
id: node.id,
|
|
20349
|
+
name: node.name,
|
|
20350
|
+
type: architectureStructurizrElementType(node.kind),
|
|
20351
|
+
tags: [node.kind],
|
|
20352
|
+
...layer === undefined ? {} : { group: layer }
|
|
20353
|
+
};
|
|
20354
|
+
}),
|
|
20165
20355
|
relationships: normalized.relations.map((relation) => ({
|
|
20166
20356
|
id: relation.id,
|
|
20167
20357
|
sourceId: relation.source,
|
|
@@ -20184,6 +20374,209 @@ function exportDocumentationStructurizrWorkspace(model) {
|
|
|
20184
20374
|
files: [{ path: "docs/architecture/diagrams/architecture.structurizr.json", content }]
|
|
20185
20375
|
};
|
|
20186
20376
|
}
|
|
20377
|
+
var RUNTIME_TOPOLOGY_NAMESPACE_TYPES = ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"];
|
|
20378
|
+
var RUNTIME_TOPOLOGY_MOUNT_TARGET_PATTERN = /^\/([^/\u0000]+(\/[^/\u0000]+)*)?$/;
|
|
20379
|
+
function runtimeTopologyView(model) {
|
|
20380
|
+
const issues = runtimeTopologyModelIssues(model);
|
|
20381
|
+
if (issues.length > 0) {
|
|
20382
|
+
throw new Error(`runtime-topology-model-invalid: ${issues.map((issue) => `${issue.entityId} ${issue.field} ${issue.invariant}`).join("; ")}`);
|
|
20383
|
+
}
|
|
20384
|
+
const byId = (left, right) => compareStrings3(left.id, right.id);
|
|
20385
|
+
const nodes = [...model.nodes].sort(byId);
|
|
20386
|
+
const relations = [...model.relations].sort(byId);
|
|
20387
|
+
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
|
20388
|
+
const namespaces = nodes.filter((node) => node.kind === "namespace").map((node) => ({
|
|
20389
|
+
node,
|
|
20390
|
+
type: runtimeTopologyNamespaceType(node),
|
|
20391
|
+
...typeof node.parent === "string" ? { parentId: node.parent } : {},
|
|
20392
|
+
namespaceIds: [],
|
|
20393
|
+
processIds: []
|
|
20394
|
+
}));
|
|
20395
|
+
const namespaceById = new Map(namespaces.map((namespace) => [namespace.node.id, namespace]));
|
|
20396
|
+
for (const namespace of namespaces) {
|
|
20397
|
+
if (namespace.parentId !== undefined)
|
|
20398
|
+
namespaceById.get(namespace.parentId).namespaceIds.push(namespace.node.id);
|
|
20399
|
+
}
|
|
20400
|
+
const namespaceByProcessId = new Map(relations.filter((relation) => relation.kind === "runs-in").map((relation) => [relation.source, relation.target]));
|
|
20401
|
+
const processes = nodes.filter((node) => node.kind === "process").map((node) => {
|
|
20402
|
+
const namespaceId = namespaceByProcessId.get(node.id);
|
|
20403
|
+
return namespaceId === undefined ? { node } : { node, namespaceId };
|
|
20404
|
+
});
|
|
20405
|
+
for (const topologyProcess of processes) {
|
|
20406
|
+
if (topologyProcess.namespaceId !== undefined)
|
|
20407
|
+
namespaceById.get(topologyProcess.namespaceId).processIds.push(topologyProcess.node.id);
|
|
20408
|
+
}
|
|
20409
|
+
const mounts = nodes.filter((node) => node.kind === "mount").map(runtimeTopologyMount);
|
|
20410
|
+
const mounterByMountId = new Map(relations.filter((relation) => relation.kind === "mounts").map((relation) => [relation.target, relation.source]));
|
|
20411
|
+
const stacksByMounterId = new Map;
|
|
20412
|
+
for (const mount of mounts) {
|
|
20413
|
+
const mounterId = mounterByMountId.get(mount.node.id);
|
|
20414
|
+
if (mounterId === undefined)
|
|
20415
|
+
continue;
|
|
20416
|
+
stacksByMounterId.set(mounterId, [...stacksByMounterId.get(mounterId) ?? [], mount]);
|
|
20417
|
+
}
|
|
20418
|
+
const mountStacks = [...stacksByMounterId.entries()].sort(([left], [right]) => compareStrings3(left, right)).map(([mounterId, stack]) => ({
|
|
20419
|
+
mounterId,
|
|
20420
|
+
mounts: stack.sort((left, right) => left.order - right.order || compareStrings3(left.node.id, right.node.id))
|
|
20421
|
+
}));
|
|
20422
|
+
const deployments = relations.filter((relation) => relation.kind === "deployed-on" && isRuntimeTopologyNodeKind(nodesById.get(relation.target)?.kind));
|
|
20423
|
+
const linkedNodeIds = new Set;
|
|
20424
|
+
for (const relation of deployments) {
|
|
20425
|
+
const source = nodesById.get(relation.source);
|
|
20426
|
+
if (!source)
|
|
20427
|
+
throw new Error(`runtime-topology-relation-endpoint-missing: ${relation.id} (${relation.source})`);
|
|
20428
|
+
if (!isRuntimeTopologyNodeKind(source.kind))
|
|
20429
|
+
linkedNodeIds.add(source.id);
|
|
20430
|
+
}
|
|
20431
|
+
return {
|
|
20432
|
+
nodesById,
|
|
20433
|
+
namespaces,
|
|
20434
|
+
rootNamespaceIds: namespaces.filter((namespace) => namespace.parentId === undefined).map((namespace) => namespace.node.id),
|
|
20435
|
+
processes,
|
|
20436
|
+
mountStacks,
|
|
20437
|
+
unmountedMounts: mounts.filter((mount) => !mounterByMountId.has(mount.node.id)),
|
|
20438
|
+
spawns: relations.filter((relation) => relation.kind === "spawns"),
|
|
20439
|
+
mounts: relations.filter((relation) => relation.kind === "mounts"),
|
|
20440
|
+
deployments,
|
|
20441
|
+
linkedNodes: nodes.filter((node) => linkedNodeIds.has(node.id))
|
|
20442
|
+
};
|
|
20443
|
+
}
|
|
20444
|
+
function runtimeTopologyNamespaceType(node) {
|
|
20445
|
+
const value = node.namespace;
|
|
20446
|
+
const type = value !== null && typeof value === "object" && !Array.isArray(value) ? value.type : undefined;
|
|
20447
|
+
if (typeof type !== "string" || !RUNTIME_TOPOLOGY_NAMESPACE_TYPES.includes(type)) {
|
|
20448
|
+
throw new Error(`runtime-topology-namespace-invalid: ${node.id}`);
|
|
20449
|
+
}
|
|
20450
|
+
return type;
|
|
20451
|
+
}
|
|
20452
|
+
function runtimeTopologyMount(node) {
|
|
20453
|
+
const value = node.mount;
|
|
20454
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
20455
|
+
throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
|
|
20456
|
+
const { source, target, mode, order } = value;
|
|
20457
|
+
if (source !== undefined && (typeof source !== "string" || source === "") || typeof target !== "string" || !RUNTIME_TOPOLOGY_MOUNT_TARGET_PATTERN.test(target) || mode !== "ro" && mode !== "rw" || typeof order !== "number" || !Number.isInteger(order) || order < 0) {
|
|
20458
|
+
throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
|
|
20459
|
+
}
|
|
20460
|
+
return { node, ...source === undefined ? {} : { source }, target, mode, order };
|
|
20461
|
+
}
|
|
20462
|
+
function runtimeTopologyMountStackId(mounterId) {
|
|
20463
|
+
return `mounts__${mermaidId(mounterId)}`;
|
|
20464
|
+
}
|
|
20465
|
+
function runtimeTopologyMountLabel(mount) {
|
|
20466
|
+
return `${mount.node.name} (${mount.target}, ${mount.mode}, order ${mount.order})`;
|
|
20467
|
+
}
|
|
20468
|
+
function exportRuntimeTopologyMermaid(model) {
|
|
20469
|
+
const view = runtimeTopologyView(model);
|
|
20470
|
+
const namespaceById = new Map(view.namespaces.map((namespace) => [namespace.node.id, namespace]));
|
|
20471
|
+
const nodeLine = (indent, node, label = node.name) => `${indent}${mermaidId(node.id)}["${escapeMermaid(label)}"]`;
|
|
20472
|
+
const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart TB"];
|
|
20473
|
+
const pushNamespace = (namespaceId, depth) => {
|
|
20474
|
+
const namespace = namespaceById.get(namespaceId);
|
|
20475
|
+
const indent = " ".repeat(depth);
|
|
20476
|
+
lines.push(`${indent}subgraph ${mermaidId(namespaceId)}["${escapeMermaid(`${namespace.node.name} (${namespace.type})`)}"]`);
|
|
20477
|
+
for (const processId of namespace.processIds)
|
|
20478
|
+
lines.push(nodeLine(`${indent} `, view.nodesById.get(processId)));
|
|
20479
|
+
for (const childId of namespace.namespaceIds)
|
|
20480
|
+
pushNamespace(childId, depth + 1);
|
|
20481
|
+
lines.push(`${indent}end`);
|
|
20482
|
+
};
|
|
20483
|
+
for (const namespaceId of view.rootNamespaceIds)
|
|
20484
|
+
pushNamespace(namespaceId, 1);
|
|
20485
|
+
for (const topologyProcess of view.processes)
|
|
20486
|
+
if (topologyProcess.namespaceId === undefined)
|
|
20487
|
+
lines.push(nodeLine(" ", topologyProcess.node));
|
|
20488
|
+
for (const stack of view.mountStacks) {
|
|
20489
|
+
const mounter = view.nodesById.get(stack.mounterId);
|
|
20490
|
+
lines.push(` subgraph ${runtimeTopologyMountStackId(stack.mounterId)}["${escapeMermaid(`Mounts: ${mounter.name}`)}"]`, " direction TB");
|
|
20491
|
+
for (const mount of stack.mounts)
|
|
20492
|
+
lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
|
|
20493
|
+
for (let index = 1;index < stack.mounts.length; index += 1) {
|
|
20494
|
+
lines.push(` ${mermaidId(stack.mounts[index - 1].node.id)} ~~~ ${mermaidId(stack.mounts[index].node.id)}`);
|
|
20495
|
+
}
|
|
20496
|
+
lines.push(" end");
|
|
20497
|
+
}
|
|
20498
|
+
for (const mount of view.unmountedMounts)
|
|
20499
|
+
lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
|
|
20500
|
+
for (const node of view.linkedNodes)
|
|
20501
|
+
lines.push(nodeLine(" ", node));
|
|
20502
|
+
for (const relation of [...view.spawns, ...view.mounts, ...view.deployments].sort((left, right) => compareStrings3(left.id, right.id))) {
|
|
20503
|
+
lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
|
|
20504
|
+
}
|
|
20505
|
+
const content = `${lines.join(`
|
|
20506
|
+
`)}
|
|
20507
|
+
`;
|
|
20508
|
+
return {
|
|
20509
|
+
format: "mermaid",
|
|
20510
|
+
digest: digestJson({ format: "mermaid", content }),
|
|
20511
|
+
files: [{ path: RUNTIME_TOPOLOGY_DIAGRAM_PATH, content }]
|
|
20512
|
+
};
|
|
20513
|
+
}
|
|
20514
|
+
function renderRuntimeTopologySummary(model, summaryPath) {
|
|
20515
|
+
const view = runtimeTopologyView(model);
|
|
20516
|
+
const cell = (value) => value.replaceAll("|", "\\|");
|
|
20517
|
+
const named = (id) => {
|
|
20518
|
+
const node = view.nodesById.get(id);
|
|
20519
|
+
return `\`${id}\`${node ? ` ${cell(node.name)}` : ""}`;
|
|
20520
|
+
};
|
|
20521
|
+
const list = (ids) => ids.length === 0 ? "none" : ids.map((id) => `\`${id}\``).join(", ");
|
|
20522
|
+
const lines = [
|
|
20523
|
+
"# Runtime Topology",
|
|
20524
|
+
"",
|
|
20525
|
+
"The architecture model declares this topology. No code facts verify it.",
|
|
20526
|
+
"",
|
|
20527
|
+
`- Diagram: [Mermaid](${posix.relative(posix.dirname(summaryPath), RUNTIME_TOPOLOGY_DIAGRAM_PATH)})`,
|
|
20528
|
+
"",
|
|
20529
|
+
"## Namespaces",
|
|
20530
|
+
""
|
|
20531
|
+
];
|
|
20532
|
+
if (view.namespaces.length === 0)
|
|
20533
|
+
lines.push("- No namespaces recorded.");
|
|
20534
|
+
else {
|
|
20535
|
+
lines.push("| Namespace | Type | Parent | Namespaces | Processes |", "| --- | --- | --- | --- | --- |");
|
|
20536
|
+
for (const namespace of view.namespaces) {
|
|
20537
|
+
lines.push(`| ${named(namespace.node.id)} | ${namespace.type} | ${namespace.parentId === undefined ? "none" : `\`${namespace.parentId}\``} | ${list(namespace.namespaceIds)} | ${list(namespace.processIds)} |`);
|
|
20538
|
+
}
|
|
20539
|
+
}
|
|
20540
|
+
lines.push("", "## Processes", "");
|
|
20541
|
+
if (view.processes.length === 0)
|
|
20542
|
+
lines.push("- No processes recorded.");
|
|
20543
|
+
else {
|
|
20544
|
+
lines.push("| Process | Namespace | Spawns | Deployed here |", "| --- | --- | --- | --- |");
|
|
20545
|
+
for (const topologyProcess of view.processes) {
|
|
20546
|
+
const id = topologyProcess.node.id;
|
|
20547
|
+
const spawned = view.spawns.filter((relation) => relation.source === id).map((relation) => relation.target);
|
|
20548
|
+
const deployed = view.deployments.filter((relation) => relation.target === id).map((relation) => relation.source);
|
|
20549
|
+
lines.push(`| ${named(id)} | ${topologyProcess.namespaceId === undefined ? "none" : `\`${topologyProcess.namespaceId}\``} | ${list([...new Set(spawned)].sort(compareStrings3))} | ${list([...new Set(deployed)].sort(compareStrings3))} |`);
|
|
20550
|
+
}
|
|
20551
|
+
}
|
|
20552
|
+
lines.push("", "## Mount Stacks", "");
|
|
20553
|
+
const mountRows = (stack) => [
|
|
20554
|
+
"| Order | Mount | Source | Target | Mode |",
|
|
20555
|
+
"| --- | --- | --- | --- | --- |",
|
|
20556
|
+
...stack.map((mount) => `| ${mount.order} | ${named(mount.node.id)} | ${mount.source === undefined ? "none" : `\`${cell(mount.source)}\``} | \`${cell(mount.target)}\` | ${mount.mode} |`)
|
|
20557
|
+
];
|
|
20558
|
+
if (view.mountStacks.length === 0 && view.unmountedMounts.length === 0)
|
|
20559
|
+
lines.push("- No mounts recorded.");
|
|
20560
|
+
for (const [index, stack] of view.mountStacks.entries()) {
|
|
20561
|
+
if (index > 0)
|
|
20562
|
+
lines.push("");
|
|
20563
|
+
lines.push(`### ${named(stack.mounterId)}`, "", ...mountRows(stack.mounts));
|
|
20564
|
+
}
|
|
20565
|
+
if (view.unmountedMounts.length > 0) {
|
|
20566
|
+
if (view.mountStacks.length > 0)
|
|
20567
|
+
lines.push("");
|
|
20568
|
+
lines.push("### Not mounted", "", ...mountRows(view.unmountedMounts));
|
|
20569
|
+
}
|
|
20570
|
+
const otherDeployments = view.deployments.filter((relation) => view.nodesById.get(relation.target)?.kind !== "process");
|
|
20571
|
+
if (otherDeployments.length > 0) {
|
|
20572
|
+
lines.push("", "## Other Deployments", "");
|
|
20573
|
+
for (const relation of otherDeployments)
|
|
20574
|
+
lines.push(`- \`${relation.source}\` -> \`${relation.target}\` — ${relation.intent}`);
|
|
20575
|
+
}
|
|
20576
|
+
return `${lines.join(`
|
|
20577
|
+
`)}
|
|
20578
|
+
`;
|
|
20579
|
+
}
|
|
20187
20580
|
function loadNativeModelFromArchContext(root) {
|
|
20188
20581
|
return nativeModelFromYamlObjects({
|
|
20189
20582
|
nodes: readYamlObjects(resolve6(root, ".archcontext/model/nodes")),
|
|
@@ -20202,6 +20595,7 @@ function nativeModelFromYamlObjects(input) {
|
|
|
20202
20595
|
throw new Error(`architecture-node-schema-version-unsupported: ${node.id || "unknown"} (${String(node.schemaVersion)})`);
|
|
20203
20596
|
}
|
|
20204
20597
|
assertArchitectureNodeSourceV2(node);
|
|
20598
|
+
nativeNodeLayer(node);
|
|
20205
20599
|
}
|
|
20206
20600
|
const flows = input.flows;
|
|
20207
20601
|
for (const flow of flows)
|
|
@@ -20386,8 +20780,15 @@ function renderTargetGeneratedBody(target, model, input) {
|
|
|
20386
20780
|
return exportDocumentationStructurizrWorkspace(model).files[0].content;
|
|
20387
20781
|
if (target.type === "diagram-likec4")
|
|
20388
20782
|
return exportDocumentationLikeC4Model(model).files[0].content;
|
|
20783
|
+
if (target.type === "diagram-runtime-topology")
|
|
20784
|
+
return exportRuntimeTopologyMermaid(model).files[0].content;
|
|
20785
|
+
if (target.type === "runtime-topology-summary")
|
|
20786
|
+
return renderRuntimeTopologySummary(model, target.path);
|
|
20389
20787
|
return "";
|
|
20390
20788
|
}
|
|
20789
|
+
function architectureIndexLink(path) {
|
|
20790
|
+
return path.replace(/^docs\/architecture\//, "");
|
|
20791
|
+
}
|
|
20391
20792
|
function renderArchitectureIndex(model, layout2) {
|
|
20392
20793
|
const lines = [
|
|
20393
20794
|
"# Architecture Index",
|
|
@@ -20412,6 +20813,7 @@ function renderArchitectureIndex(model, layout2) {
|
|
|
20412
20813
|
"- [Mermaid](diagrams/architecture.mmd)",
|
|
20413
20814
|
"- [Structurizr JSON](diagrams/architecture.structurizr.json)",
|
|
20414
20815
|
"- [LikeC4](diagrams/architecture.likec4)",
|
|
20816
|
+
...layout2.targets.flatMap((target) => target.type === "diagram-runtime-topology" ? [`- [Runtime topology Mermaid](${architectureIndexLink(target.path)})`] : target.type === "runtime-topology-summary" ? [`- [Runtime topology](${architectureIndexLink(target.path)})`] : []),
|
|
20415
20817
|
"- [Decision index](decisions/index.md)",
|
|
20416
20818
|
"- [Architecture changelog](changelog.md)"
|
|
20417
20819
|
];
|
|
@@ -20865,26 +21267,6 @@ function isManagedArchitectureDocumentationMarkdownPath(path) {
|
|
|
20865
21267
|
function pathSegment2(id) {
|
|
20866
21268
|
return stableId(id).replace(/\./g, "-");
|
|
20867
21269
|
}
|
|
20868
|
-
function documentationLikeC4Kind(kind) {
|
|
20869
|
-
if (kind === "external-system")
|
|
20870
|
-
return "external_system";
|
|
20871
|
-
if (kind === "datastore")
|
|
20872
|
-
return "datastore";
|
|
20873
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
20874
|
-
return kind;
|
|
20875
|
-
return "component";
|
|
20876
|
-
}
|
|
20877
|
-
function documentationStructurizrElementType(kind) {
|
|
20878
|
-
if (kind === "capability")
|
|
20879
|
-
return "Software System";
|
|
20880
|
-
if (kind === "module")
|
|
20881
|
-
return "Container";
|
|
20882
|
-
if (kind === "datastore")
|
|
20883
|
-
return "Database";
|
|
20884
|
-
if (kind === "external-system")
|
|
20885
|
-
return "External System";
|
|
20886
|
-
return "Component";
|
|
20887
|
-
}
|
|
20888
21270
|
function escapeDsl(value) {
|
|
20889
21271
|
return value.replace(/"/g, "'");
|
|
20890
21272
|
}
|
|
@@ -21548,7 +21930,7 @@ function sameOwners(left, right) {
|
|
|
21548
21930
|
|
|
21549
21931
|
// packages/core/module-statistics/src/index.ts
|
|
21550
21932
|
function evaluateDependencyConstraints(input) {
|
|
21551
|
-
const constraints = [...input.constraints].sort((left, right) =>
|
|
21933
|
+
const constraints = [...input.constraints].sort((left, right) => compare2(left.id, right.id));
|
|
21552
21934
|
const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded ?? false);
|
|
21553
21935
|
if (constraints.length === 0) {
|
|
21554
21936
|
return {
|
|
@@ -21605,7 +21987,7 @@ function evaluateDependencyConstraints(input) {
|
|
|
21605
21987
|
reasonCodes.add("unresolved-import");
|
|
21606
21988
|
if (input.workspacePackagesResolved === false)
|
|
21607
21989
|
reasonCodes.add("workspace-resolution-failed");
|
|
21608
|
-
const sortedViolations = [...violations.values()].sort((left, right) =>
|
|
21990
|
+
const sortedViolations = [...violations.values()].sort((left, right) => compare2(left.constraintId, right.constraintId) || compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
|
|
21609
21991
|
return {
|
|
21610
21992
|
schemaVersion: "archcontext.dependency-constraint-evaluation/v1",
|
|
21611
21993
|
status: sortedViolations.length > 0 ? "violated" : reasonCodes.size > 0 ? "undetermined" : "pass",
|
|
@@ -21682,7 +22064,7 @@ function measureCapabilityDependencies(input) {
|
|
|
21682
22064
|
const cellKey = JSON.stringify([from.capability, to.capability]);
|
|
21683
22065
|
let cell = cells.get(cellKey);
|
|
21684
22066
|
if (cell === undefined) {
|
|
21685
|
-
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(
|
|
22067
|
+
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(compare2);
|
|
21686
22068
|
cell = { from: from.capability, to: to.capability, imports: 0, calls: null, declared: from.capability === to.capability || relationIds.length > 0, relationIds };
|
|
21687
22069
|
cells.set(cellKey, cell);
|
|
21688
22070
|
}
|
|
@@ -21690,14 +22072,14 @@ function measureCapabilityDependencies(input) {
|
|
|
21690
22072
|
attributedEdges.push({ fromPath: edge.from, toPath: edge.to, fromCapability: from.capability, toCapability: to.capability });
|
|
21691
22073
|
}
|
|
21692
22074
|
const edgeLimit = input.edgeLimit ?? CAPABILITY_DEPENDENCIES_EDGE_LIMIT;
|
|
21693
|
-
attributedEdges.sort((left, right) =>
|
|
22075
|
+
attributedEdges.sort((left, right) => compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
|
|
21694
22076
|
const listed = attributedEdges.slice(0, edgeLimit);
|
|
21695
22077
|
return {
|
|
21696
22078
|
schemaVersion: CAPABILITY_DEPENDENCIES_SCHEMA_VERSION,
|
|
21697
22079
|
ownershipRule: "parent-chain",
|
|
21698
22080
|
filter: { from: input.from ?? null, into: input.into ?? null },
|
|
21699
|
-
capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(
|
|
21700
|
-
matrix: [...cells.values()].sort((left, right) =>
|
|
22081
|
+
capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(compare2),
|
|
22082
|
+
matrix: [...cells.values()].sort((left, right) => compare2(left.from, right.from) || compare2(left.to, right.to)),
|
|
21701
22083
|
imports: {
|
|
21702
22084
|
coverage: certification.coverage,
|
|
21703
22085
|
reasonCodes: [...certification.reasonCodes].sort(),
|
|
@@ -21733,7 +22115,7 @@ function isRepositoryLocalSpecifier(specifier, workspacePackages) {
|
|
|
21733
22115
|
return true;
|
|
21734
22116
|
return workspacePackages.some((workspacePackage) => specifier === workspacePackage.name || specifier.startsWith(`${workspacePackage.name}/`));
|
|
21735
22117
|
}
|
|
21736
|
-
function
|
|
22118
|
+
function compare2(left, right) {
|
|
21737
22119
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
21738
22120
|
}
|
|
21739
22121
|
function measureModuleStatistics(input) {
|
|
@@ -21818,7 +22200,7 @@ function modelDigest(model, constraints) {
|
|
|
21818
22200
|
nodes: [...model.nodes].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21819
22201
|
relations: [...model.relations].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21820
22202
|
flows: [...model.flows ?? []].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21821
|
-
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) =>
|
|
22203
|
+
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare2(left.id, right.id)) }
|
|
21822
22204
|
});
|
|
21823
22205
|
}
|
|
21824
22206
|
function countViolationsByNode(evaluation) {
|
|
@@ -22715,6 +23097,29 @@ function listGitVisibleFiles(root, pathspec) {
|
|
|
22715
23097
|
}
|
|
22716
23098
|
}).sort((left, right) => left.localeCompare(right));
|
|
22717
23099
|
}
|
|
23100
|
+
function ignoredRepositoryPaths(root, paths) {
|
|
23101
|
+
if (paths.length === 0)
|
|
23102
|
+
return [];
|
|
23103
|
+
const result = spawnSync2("git", ["check-ignore", "--verbose", "--no-index", "-z", "--stdin"], {
|
|
23104
|
+
cwd: root,
|
|
23105
|
+
encoding: "utf8",
|
|
23106
|
+
input: paths.map((path) => `${path}\x00`).join(""),
|
|
23107
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
23108
|
+
});
|
|
23109
|
+
if (result.status !== 0 && result.status !== 1) {
|
|
23110
|
+
const detail = result.error?.message ?? (result.stderr.trim() || `git check-ignore exited ${result.status}`);
|
|
23111
|
+
throw new GitVisibleFilesUnavailableError(root, detail);
|
|
23112
|
+
}
|
|
23113
|
+
const fields = result.stdout.split("\x00");
|
|
23114
|
+
const ignored = [];
|
|
23115
|
+
for (let index = 0;index + 3 < fields.length; index += 4) {
|
|
23116
|
+
const [source, line, pattern, path] = fields.slice(index, index + 4);
|
|
23117
|
+
if (pattern.startsWith("!"))
|
|
23118
|
+
continue;
|
|
23119
|
+
ignored.push({ path, source, line: Number(line), pattern });
|
|
23120
|
+
}
|
|
23121
|
+
return ignored;
|
|
23122
|
+
}
|
|
22718
23123
|
function readHeadSha(root) {
|
|
22719
23124
|
try {
|
|
22720
23125
|
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
@@ -26083,7 +26488,7 @@ function buildObservations(snapshot, structure, model) {
|
|
|
26083
26488
|
evidence: evidence.evidenceGap()
|
|
26084
26489
|
});
|
|
26085
26490
|
}
|
|
26086
|
-
return drafts.map((draft, index) => ({ draft, order: draft.kind === "cycle" ? index : 0 })).sort((left, right) =>
|
|
26491
|
+
return drafts.map((draft, index) => ({ draft, order: draft.kind === "cycle" ? index : 0 })).sort((left, right) => compare3(left.draft.kind, right.draft.kind) || left.order - right.order || compare3(left.draft.subjectSelectorId, right.draft.subjectSelectorId)).map(({ draft }) => ({
|
|
26087
26492
|
kind: draft.kind,
|
|
26088
26493
|
subjectSelectorId: draft.subjectSelectorId,
|
|
26089
26494
|
affectedNodeIds: draft.affectedNodeIds,
|
|
@@ -26273,13 +26678,13 @@ function signalIdFor(draft) {
|
|
|
26273
26678
|
}
|
|
26274
26679
|
function modelDigest2(model, constraints) {
|
|
26275
26680
|
return digestJson({
|
|
26276
|
-
nodes: [...model.nodes].sort((left, right) =>
|
|
26277
|
-
relations: [...model.relations].sort((left, right) =>
|
|
26278
|
-
flows: [...model.flows ?? []].sort((left, right) =>
|
|
26279
|
-
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) =>
|
|
26681
|
+
nodes: [...model.nodes].sort((left, right) => compare3(left.id, right.id)),
|
|
26682
|
+
relations: [...model.relations].sort((left, right) => compare3(left.id, right.id)),
|
|
26683
|
+
flows: [...model.flows ?? []].sort((left, right) => compare3(left.id, right.id)),
|
|
26684
|
+
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare3(left.id, right.id)) }
|
|
26280
26685
|
});
|
|
26281
26686
|
}
|
|
26282
|
-
function
|
|
26687
|
+
function compare3(left, right) {
|
|
26283
26688
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
26284
26689
|
}
|
|
26285
26690
|
function isNumber(value) {
|
|
@@ -26301,17 +26706,49 @@ var architecture_node_schema_default = {
|
|
|
26301
26706
|
type: "object",
|
|
26302
26707
|
additionalProperties: false,
|
|
26303
26708
|
required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
|
|
26709
|
+
allOf: [
|
|
26710
|
+
{
|
|
26711
|
+
if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
|
|
26712
|
+
then: { required: ["namespace"] },
|
|
26713
|
+
else: { not: { required: ["namespace"] } }
|
|
26714
|
+
},
|
|
26715
|
+
{
|
|
26716
|
+
if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
|
|
26717
|
+
then: { required: ["mount"] },
|
|
26718
|
+
else: { not: { required: ["mount"] } }
|
|
26719
|
+
}
|
|
26720
|
+
],
|
|
26304
26721
|
properties: {
|
|
26305
26722
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
26306
26723
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26307
26724
|
kind: {
|
|
26308
26725
|
type: "string",
|
|
26309
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
26726
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
26310
26727
|
},
|
|
26311
26728
|
name: { type: "string" },
|
|
26312
26729
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
26313
26730
|
parent: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26731
|
+
layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
26314
26732
|
summary: { type: "string" },
|
|
26733
|
+
namespace: {
|
|
26734
|
+
type: "object",
|
|
26735
|
+
additionalProperties: false,
|
|
26736
|
+
required: ["type"],
|
|
26737
|
+
properties: {
|
|
26738
|
+
type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
|
|
26739
|
+
}
|
|
26740
|
+
},
|
|
26741
|
+
mount: {
|
|
26742
|
+
type: "object",
|
|
26743
|
+
additionalProperties: false,
|
|
26744
|
+
required: ["target", "mode", "order"],
|
|
26745
|
+
properties: {
|
|
26746
|
+
source: { type: "string", minLength: 1 },
|
|
26747
|
+
target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
|
|
26748
|
+
mode: { type: "string", enum: ["ro", "rw"] },
|
|
26749
|
+
order: { type: "integer", minimum: 0 }
|
|
26750
|
+
}
|
|
26751
|
+
},
|
|
26315
26752
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
26316
26753
|
source: {
|
|
26317
26754
|
type: "object",
|
|
@@ -26400,7 +26837,7 @@ var architecture_relation_schema_default = {
|
|
|
26400
26837
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26401
26838
|
kind: {
|
|
26402
26839
|
type: "string",
|
|
26403
|
-
enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on"]
|
|
26840
|
+
enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on", "spawns", "runs-in", "mounts"]
|
|
26404
26841
|
},
|
|
26405
26842
|
source: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
26406
26843
|
target: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
@@ -27108,6 +27545,22 @@ function createDefaultProjectionTargetManifest() {
|
|
|
27108
27545
|
ownership: "generated",
|
|
27109
27546
|
format: "likec4"
|
|
27110
27547
|
},
|
|
27548
|
+
{
|
|
27549
|
+
id: "projection_rule.diagram.runtime-topology",
|
|
27550
|
+
targetType: "diagram-runtime-topology",
|
|
27551
|
+
scope: { kind: "diagram", id: "runtime-topology" },
|
|
27552
|
+
pathTemplate: "docs/architecture/diagrams/runtime-topology.mmd",
|
|
27553
|
+
ownership: "generated",
|
|
27554
|
+
format: "mermaid"
|
|
27555
|
+
},
|
|
27556
|
+
{
|
|
27557
|
+
id: "projection_rule.runtime-topology.summary",
|
|
27558
|
+
targetType: "runtime-topology-summary",
|
|
27559
|
+
scope: { kind: "diagram", id: "runtime-topology" },
|
|
27560
|
+
pathTemplate: "docs/architecture/runtime-topology.md",
|
|
27561
|
+
ownership: "generated",
|
|
27562
|
+
format: "markdown"
|
|
27563
|
+
},
|
|
27111
27564
|
{
|
|
27112
27565
|
id: "projection_rule.agent-context.claude",
|
|
27113
27566
|
targetType: "agent-context",
|
|
@@ -27253,10 +27706,19 @@ class YamlModelStore {
|
|
|
27253
27706
|
}
|
|
27254
27707
|
}
|
|
27255
27708
|
const files = listModelFiles(workspace.root);
|
|
27709
|
+
const topologyRecords = { nodes: [], relations: [] };
|
|
27710
|
+
const topologyRecordPaths = new Map;
|
|
27256
27711
|
for (const file of files) {
|
|
27257
27712
|
if (file.path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(file.path)) {
|
|
27258
27713
|
try {
|
|
27259
27714
|
const value = parseJsonOrStableYaml(file.body, file.path);
|
|
27715
|
+
for (const [directory, records] of [["nodes", topologyRecords.nodes], ["relations", topologyRecords.relations]]) {
|
|
27716
|
+
if (!file.path.startsWith(`.archcontext/model/${directory}/`) || !isJsonRecord2(value))
|
|
27717
|
+
continue;
|
|
27718
|
+
records.push(value);
|
|
27719
|
+
if (typeof value.id === "string")
|
|
27720
|
+
topologyRecordPaths.set(value.id, file.path);
|
|
27721
|
+
}
|
|
27260
27722
|
if (file.path.startsWith(".archcontext/model/nodes/")) {
|
|
27261
27723
|
const { issues } = validateJsonSchema(architecture_node_schema_default, value);
|
|
27262
27724
|
for (const issue of issues) {
|
|
@@ -27277,6 +27739,9 @@ class YamlModelStore {
|
|
|
27277
27739
|
errors.push(`${file.path}: expected archcontext.flow/v1, got ${file.schemaVersion || "missing"}`);
|
|
27278
27740
|
}
|
|
27279
27741
|
}
|
|
27742
|
+
for (const issue of runtimeTopologyModelIssues(topologyRecords)) {
|
|
27743
|
+
errors.push(`${topologyRecordPaths.get(issue.entityId) ?? issue.entityId}: ${issue.field}: ${issue.invariant}`);
|
|
27744
|
+
}
|
|
27280
27745
|
const adr = validateAdrAppliesTo(files);
|
|
27281
27746
|
const constraints = readDependencyConstraints(files);
|
|
27282
27747
|
const reviewPolicy = readReviewPolicy(files);
|
|
@@ -27297,6 +27762,21 @@ class YamlModelStore {
|
|
|
27297
27762
|
return { digest: digestJson(changeSet), summary: "ChangeSet preview generated" };
|
|
27298
27763
|
}
|
|
27299
27764
|
}
|
|
27765
|
+
var ARCHITECTURE_MODEL_COMMITTED_PATHS = Object.freeze([
|
|
27766
|
+
".archcontext/manifest.yaml",
|
|
27767
|
+
".archcontext/product.yaml",
|
|
27768
|
+
".archcontext/model/nodes/",
|
|
27769
|
+
".archcontext/model/relations/",
|
|
27770
|
+
".archcontext/model/flows/",
|
|
27771
|
+
".archcontext/decisions/",
|
|
27772
|
+
".archcontext/policies/",
|
|
27773
|
+
".archcontext/practices/",
|
|
27774
|
+
".archcontext/projections/"
|
|
27775
|
+
]);
|
|
27776
|
+
function architectureModelIgnoreProbePaths(root) {
|
|
27777
|
+
const directories = ARCHITECTURE_MODEL_COMMITTED_PATHS.filter((path) => path.endsWith("/")).map((path) => path.slice(0, -1));
|
|
27778
|
+
return [...new Set([...ARCHITECTURE_MODEL_COMMITTED_PATHS, ...collectArchContextFiles(root, directories)])];
|
|
27779
|
+
}
|
|
27300
27780
|
function listModelFiles(root) {
|
|
27301
27781
|
const paths = collectArchContextFiles(root, [
|
|
27302
27782
|
".archcontext/manifest.yaml",
|
|
@@ -27381,6 +27861,9 @@ function extractSchemaVersion(body, path) {
|
|
|
27381
27861
|
return "archcontext.generated/v1";
|
|
27382
27862
|
return "";
|
|
27383
27863
|
}
|
|
27864
|
+
function isJsonRecord2(value) {
|
|
27865
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27866
|
+
}
|
|
27384
27867
|
function withTrailingNewline(body) {
|
|
27385
27868
|
return body.endsWith(`
|
|
27386
27869
|
`) ? body : `${body}
|
|
@@ -31318,13 +31801,10 @@ class HttpContext7Transport {
|
|
|
31318
31801
|
url.searchParams.set("libraryName", input.libraryName);
|
|
31319
31802
|
url.searchParams.set("query", input.query);
|
|
31320
31803
|
url.searchParams.set("fast", input.fast ? "true" : "false");
|
|
31321
|
-
const
|
|
31804
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31322
31805
|
method: "GET",
|
|
31323
31806
|
headers: authHeaders(input.apiKey)
|
|
31324
|
-
}, input.timeoutMs);
|
|
31325
|
-
if (!response.ok)
|
|
31326
|
-
throw httpStatusError("Context7 search failed", response);
|
|
31327
|
-
const { payload } = await parseJsonResponse(response, "Context7 search");
|
|
31807
|
+
}, input.timeoutMs, "Context7 search");
|
|
31328
31808
|
return parseSearchResponse(payload);
|
|
31329
31809
|
}
|
|
31330
31810
|
async getContext(input) {
|
|
@@ -31333,13 +31813,10 @@ class HttpContext7Transport {
|
|
|
31333
31813
|
url.searchParams.set("query", input.query);
|
|
31334
31814
|
url.searchParams.set("type", "json");
|
|
31335
31815
|
url.searchParams.set("fast", "true");
|
|
31336
|
-
const
|
|
31816
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31337
31817
|
method: "GET",
|
|
31338
31818
|
headers: authHeaders(input.apiKey)
|
|
31339
|
-
}, input.timeoutMs);
|
|
31340
|
-
if (!response.ok)
|
|
31341
|
-
throw httpStatusError("Context7 context fetch failed", response);
|
|
31342
|
-
const { payload } = await parseJsonResponse(response, "Context7 context fetch");
|
|
31819
|
+
}, input.timeoutMs, "Context7 context fetch");
|
|
31343
31820
|
return parseDocumentationResponse(payload).slice(0, input.maxResults);
|
|
31344
31821
|
}
|
|
31345
31822
|
}
|
|
@@ -31437,11 +31914,14 @@ function normalizeSourceUri(value) {
|
|
|
31437
31914
|
return;
|
|
31438
31915
|
}
|
|
31439
31916
|
}
|
|
31440
|
-
async function
|
|
31917
|
+
async function fetchJsonWithTimeout(input, init, timeoutMs, label) {
|
|
31441
31918
|
const controller = new AbortController;
|
|
31442
31919
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
31443
31920
|
try {
|
|
31444
|
-
|
|
31921
|
+
const response = await withLocalEgress2("context7", () => fetch(input, { ...init, redirect: "error", signal: controller.signal }));
|
|
31922
|
+
if (!response.ok)
|
|
31923
|
+
throw httpStatusError(`${label} failed`, response);
|
|
31924
|
+
return await parseJsonResponse(response, label);
|
|
31445
31925
|
} catch (error) {
|
|
31446
31926
|
if (isAbortError(error)) {
|
|
31447
31927
|
throw new Context7ProviderError("timeout", "Context7 provider request timed out", { retryable: true, cause: error });
|
|
@@ -32674,6 +33154,11 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32674
33154
|
try {
|
|
32675
33155
|
const subcommand = input.action ?? "plan";
|
|
32676
33156
|
const root = findRepositoryRoot(cwd);
|
|
33157
|
+
if (subcommand === "adopt") {
|
|
33158
|
+
const ignored = architectureModelIgnoredEnvelope("docs.adopt", root);
|
|
33159
|
+
if (ignored)
|
|
33160
|
+
return ignored;
|
|
33161
|
+
}
|
|
32677
33162
|
let profile;
|
|
32678
33163
|
let projection;
|
|
32679
33164
|
let acceptedChange;
|
|
@@ -32789,6 +33274,23 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32789
33274
|
throw publicOperationFailure(error);
|
|
32790
33275
|
}
|
|
32791
33276
|
}
|
|
33277
|
+
function ignoredArchitectureModelPaths(root) {
|
|
33278
|
+
const ignored = ignoredRepositoryPaths(root, architectureModelIgnoreProbePaths(root));
|
|
33279
|
+
const directories = ignored.filter((entry) => entry.path.endsWith("/")).map((entry) => entry.path);
|
|
33280
|
+
return ignored.filter((entry) => entry.path.endsWith("/") || !directories.some((directory) => entry.path.startsWith(directory)));
|
|
33281
|
+
}
|
|
33282
|
+
function architectureModelIgnoredEnvelope(requestId, root) {
|
|
33283
|
+
const ignored = ignoredArchitectureModelPaths(root);
|
|
33284
|
+
if (ignored.length === 0)
|
|
33285
|
+
return;
|
|
33286
|
+
const paths = ignored.map((entry) => entry.path).slice(0, 8);
|
|
33287
|
+
return publicFailureEnvelope(requestId, "AC_PRECONDITION_FAILED", "architecture-model-ignored", {
|
|
33288
|
+
pathCount: ignored.length,
|
|
33289
|
+
paths,
|
|
33290
|
+
commands: [`git check-ignore --verbose --no-index -- ${paths.join(" ")}`],
|
|
33291
|
+
omittedPathCount: ignored.length - paths.length
|
|
33292
|
+
});
|
|
33293
|
+
}
|
|
32792
33294
|
function committedArchitectureDocsProfile(root) {
|
|
32793
33295
|
return existsSync9(resolve13(root, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)) ? loadArchitectureDocumentationProfile(root) : "default";
|
|
32794
33296
|
}
|
|
@@ -33099,6 +33601,11 @@ async function runProjectionProtocolCommand(invocation, cwd, daemon) {
|
|
|
33099
33601
|
if (replay)
|
|
33100
33602
|
return replay;
|
|
33101
33603
|
}
|
|
33604
|
+
if (request.mode === "adopt") {
|
|
33605
|
+
const ignored = architectureModelIgnoredEnvelope("projection.run", root);
|
|
33606
|
+
if (ignored)
|
|
33607
|
+
return ignored;
|
|
33608
|
+
}
|
|
33102
33609
|
let observed;
|
|
33103
33610
|
let acceptedChange = request.acceptedChange;
|
|
33104
33611
|
let declinedUnprovableNodeIds;
|
|
@@ -34663,6 +35170,7 @@ init_projection_recovery();
|
|
|
34663
35170
|
init_projection_writer();
|
|
34664
35171
|
init_projection_manifest();
|
|
34665
35172
|
init_refactor();
|
|
35173
|
+
init_runtime_topology();
|
|
34666
35174
|
init_schema();
|
|
34667
35175
|
init_failure();
|
|
34668
35176
|
init_validator();
|
|
@@ -47072,7 +47580,7 @@ function upgradeExistingLocalStoreTarget2(paths, integrityCheck) {
|
|
|
47072
47580
|
var RUNTIME_AGENT_JOB_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled", "superseded", "expired"];
|
|
47073
47581
|
var CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION = "archcontext.changeset-model-transition/v1";
|
|
47074
47582
|
function assertChangeSetModelTransition(value, label = "changeset-model-transition-invalid") {
|
|
47075
|
-
if (!
|
|
47583
|
+
if (!isJsonRecord3(value) || Object.keys(value).sort().join(",") !== "after,before,schemaVersion" || value.schemaVersion !== CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION || typeof value.before !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.before) || typeof value.after !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.after)) {
|
|
47076
47584
|
throw new Error(label);
|
|
47077
47585
|
}
|
|
47078
47586
|
return { schemaVersion: CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION, before: value.before, after: value.after };
|
|
@@ -51360,10 +51868,10 @@ function readChangeSetJournalTaskSessionId(metadataJson, journalId) {
|
|
|
51360
51868
|
} catch {
|
|
51361
51869
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51362
51870
|
}
|
|
51363
|
-
if (!
|
|
51871
|
+
if (!isJsonRecord3(metadata))
|
|
51364
51872
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51365
51873
|
const reason = metadata.reason;
|
|
51366
|
-
if (!
|
|
51874
|
+
if (!isJsonRecord3(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
|
|
51367
51875
|
throw new Error(`changeset-journal-reason-malformed: ${journalId}`);
|
|
51368
51876
|
}
|
|
51369
51877
|
return reason.taskSessionId;
|
|
@@ -51396,7 +51904,7 @@ function committedChangeSetJournalFiles(filesJson, journalId) {
|
|
|
51396
51904
|
if (!Array.isArray(parsed))
|
|
51397
51905
|
throw new Error(`changeset-journal-files-malformed: ${journalId}`);
|
|
51398
51906
|
return parsed.map((entry) => {
|
|
51399
|
-
if (!
|
|
51907
|
+
if (!isJsonRecord3(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
|
|
51400
51908
|
throw new Error(`changeset-journal-file-malformed: ${journalId}`);
|
|
51401
51909
|
}
|
|
51402
51910
|
if (typeof entry.bodyHash !== "string" || entry.bodyHash === "") {
|
|
@@ -51416,7 +51924,7 @@ function committedChangeSetProjectionOwned(metadataJson, journalId) {
|
|
|
51416
51924
|
} catch {
|
|
51417
51925
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51418
51926
|
}
|
|
51419
|
-
return
|
|
51927
|
+
return isJsonRecord3(metadata) && metadata.projectionOwned === true;
|
|
51420
51928
|
}
|
|
51421
51929
|
function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
51422
51930
|
let metadata;
|
|
@@ -51425,7 +51933,7 @@ function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
|
51425
51933
|
} catch {
|
|
51426
51934
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51427
51935
|
}
|
|
51428
|
-
if (!
|
|
51936
|
+
if (!isJsonRecord3(metadata))
|
|
51429
51937
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51430
51938
|
if (metadata.modelTransition === undefined)
|
|
51431
51939
|
return;
|
|
@@ -51439,7 +51947,7 @@ function readChangeSetJournalMetadata(db, journalId) {
|
|
|
51439
51947
|
}
|
|
51440
51948
|
function withChangeSetJournalArchitectureLedger(metadata, patch) {
|
|
51441
51949
|
const existing = metadata.architectureLedger;
|
|
51442
|
-
const architectureLedger =
|
|
51950
|
+
const architectureLedger = isJsonRecord3(existing) ? { ...existing, ...patch } : {
|
|
51443
51951
|
schemaVersion: "archcontext.changeset-ledger-recovery/v1",
|
|
51444
51952
|
...patch
|
|
51445
51953
|
};
|
|
@@ -51461,12 +51969,12 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51461
51969
|
const architectureLedger = metadata.architectureLedger;
|
|
51462
51970
|
if (architectureLedger === undefined)
|
|
51463
51971
|
return;
|
|
51464
|
-
if (!
|
|
51972
|
+
if (!isJsonRecord3(architectureLedger))
|
|
51465
51973
|
throw new Error("changeset-ledger-recovery-metadata-malformed");
|
|
51466
51974
|
const plannedEvent = architectureLedger.plannedEvent;
|
|
51467
51975
|
if (plannedEvent === undefined)
|
|
51468
51976
|
return;
|
|
51469
|
-
if (!
|
|
51977
|
+
if (!isJsonRecord3(plannedEvent))
|
|
51470
51978
|
throw new Error("changeset-ledger-recovery-planned-event-malformed");
|
|
51471
51979
|
const event = plannedEvent;
|
|
51472
51980
|
try {
|
|
@@ -51476,7 +51984,7 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51476
51984
|
}
|
|
51477
51985
|
return event;
|
|
51478
51986
|
}
|
|
51479
|
-
function
|
|
51987
|
+
function isJsonRecord3(value) {
|
|
51480
51988
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51481
51989
|
}
|
|
51482
51990
|
function recoverJournalFiles(root, files) {
|
|
@@ -59359,11 +59867,13 @@ class ArchctxDaemon {
|
|
|
59359
59867
|
return okEnvelope("status", status);
|
|
59360
59868
|
const repositoryId = repositoryFingerprint(root);
|
|
59361
59869
|
const session = this.sessions.get(repositoryId);
|
|
59870
|
+
const ignoredModelPaths = ignoredArchitectureModelPaths(root);
|
|
59362
59871
|
return okEnvelope("status", {
|
|
59363
59872
|
...status,
|
|
59364
59873
|
repositoryId,
|
|
59365
59874
|
headSha: session?.workspace.headSha ?? readHeadSha(root),
|
|
59366
|
-
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root))
|
|
59875
|
+
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root)),
|
|
59876
|
+
...ignoredModelPaths.length === 0 ? {} : { architectureModelIgnoredPaths: ignoredModelPaths }
|
|
59367
59877
|
});
|
|
59368
59878
|
} catch (error) {
|
|
59369
59879
|
throw publicOperationFailure(error);
|
|
@@ -60078,26 +60588,19 @@ function exportLikeC4Model(model) {
|
|
|
60078
60588
|
const normalized = normalizeNativeModel(model);
|
|
60079
60589
|
const lines = [
|
|
60080
60590
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
60081
|
-
|
|
60082
|
-
" element capability",
|
|
60083
|
-
" element module",
|
|
60084
|
-
" element component",
|
|
60085
|
-
" element interface",
|
|
60086
|
-
" element datastore",
|
|
60087
|
-
" element external_system",
|
|
60088
|
-
"}",
|
|
60591
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
60089
60592
|
"",
|
|
60090
60593
|
"model {"
|
|
60091
60594
|
];
|
|
60092
60595
|
for (const node of normalized.nodes) {
|
|
60093
60596
|
lines.push(` // archctx-node ${JSON.stringify(node)}`);
|
|
60094
|
-
lines.push(` ${likec4Id(node.id)} = ${
|
|
60597
|
+
lines.push(` ${likec4Id(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl2(node.name)}"`);
|
|
60095
60598
|
}
|
|
60096
60599
|
for (const relation of normalized.relations) {
|
|
60097
60600
|
lines.push(` // archctx-relation ${JSON.stringify(relation)}`);
|
|
60098
60601
|
lines.push(` ${likec4Id(relation.source)} -> ${likec4Id(relation.target)} "${escapeDsl2(relation.kind)}"`);
|
|
60099
60602
|
}
|
|
60100
|
-
lines.push("}", "",
|
|
60603
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), likec4Id));
|
|
60101
60604
|
const content = `${lines.join(`
|
|
60102
60605
|
`)}
|
|
60103
60606
|
`;
|
|
@@ -60127,15 +60630,6 @@ function importLikeC4InitialModel(content) {
|
|
|
60127
60630
|
function likec4Id(id) {
|
|
60128
60631
|
return stableId(id).replace(/-/g, "_").replace(/\./g, "_").replace(/::/g, "__");
|
|
60129
60632
|
}
|
|
60130
|
-
function likec4Kind(kind) {
|
|
60131
|
-
if (kind === "external-system")
|
|
60132
|
-
return "external_system";
|
|
60133
|
-
if (kind === "datastore")
|
|
60134
|
-
return "datastore";
|
|
60135
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
60136
|
-
return kind;
|
|
60137
|
-
return "component";
|
|
60138
|
-
}
|
|
60139
60633
|
function escapeDsl2(value) {
|
|
60140
60634
|
return value.replace(/"/g, "'");
|
|
60141
60635
|
}
|
|
@@ -60144,17 +60638,22 @@ function escapeDsl2(value) {
|
|
|
60144
60638
|
init_src();
|
|
60145
60639
|
function exportStructurizrWorkspace(model, name = "ArchContext Export") {
|
|
60146
60640
|
const normalized = normalizeNativeModel(model);
|
|
60641
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
60147
60642
|
const workspace = {
|
|
60148
60643
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
60149
60644
|
name,
|
|
60150
60645
|
model: {
|
|
60151
|
-
elements: normalized.nodes.map((node) =>
|
|
60152
|
-
|
|
60153
|
-
|
|
60154
|
-
|
|
60155
|
-
|
|
60156
|
-
|
|
60157
|
-
|
|
60646
|
+
elements: normalized.nodes.map((node) => {
|
|
60647
|
+
const layer = layerByNodeId.get(node.id);
|
|
60648
|
+
return {
|
|
60649
|
+
id: node.id,
|
|
60650
|
+
name: node.name,
|
|
60651
|
+
type: architectureStructurizrElementType(node.kind),
|
|
60652
|
+
tags: [node.kind],
|
|
60653
|
+
...layer === undefined ? {} : { group: layer },
|
|
60654
|
+
archcontextNative: node
|
|
60655
|
+
};
|
|
60656
|
+
}),
|
|
60158
60657
|
relationships: normalized.relations.map((relation) => ({
|
|
60159
60658
|
id: relation.id,
|
|
60160
60659
|
sourceId: relation.source,
|
|
@@ -60198,17 +60697,6 @@ function importStructurizrInitialModel(content) {
|
|
|
60198
60697
|
warnings: ["Structurizr import is initialization-only; Native ArchContext remains source of truth."]
|
|
60199
60698
|
};
|
|
60200
60699
|
}
|
|
60201
|
-
function structurizrElementType(kind) {
|
|
60202
|
-
if (kind === "capability")
|
|
60203
|
-
return "Software System";
|
|
60204
|
-
if (kind === "module")
|
|
60205
|
-
return "Container";
|
|
60206
|
-
if (kind === "datastore")
|
|
60207
|
-
return "Database";
|
|
60208
|
-
if (kind === "external-system")
|
|
60209
|
-
return "External System";
|
|
60210
|
-
return "Component";
|
|
60211
|
-
}
|
|
60212
60700
|
|
|
60213
60701
|
// packages/surfaces/mcp-local/src/tool-schemas.ts
|
|
60214
60702
|
var text2 = { type: "string", minLength: 1 };
|
|
@@ -60782,17 +61270,49 @@ var architecture_node_schema_default2 = {
|
|
|
60782
61270
|
type: "object",
|
|
60783
61271
|
additionalProperties: false,
|
|
60784
61272
|
required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
|
|
61273
|
+
allOf: [
|
|
61274
|
+
{
|
|
61275
|
+
if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
|
|
61276
|
+
then: { required: ["namespace"] },
|
|
61277
|
+
else: { not: { required: ["namespace"] } }
|
|
61278
|
+
},
|
|
61279
|
+
{
|
|
61280
|
+
if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
|
|
61281
|
+
then: { required: ["mount"] },
|
|
61282
|
+
else: { not: { required: ["mount"] } }
|
|
61283
|
+
}
|
|
61284
|
+
],
|
|
60785
61285
|
properties: {
|
|
60786
61286
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
60787
61287
|
id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
60788
61288
|
kind: {
|
|
60789
61289
|
type: "string",
|
|
60790
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
61290
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
60791
61291
|
},
|
|
60792
61292
|
name: { type: "string" },
|
|
60793
61293
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
60794
61294
|
parent: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
|
|
61295
|
+
layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
|
|
60795
61296
|
summary: { type: "string" },
|
|
61297
|
+
namespace: {
|
|
61298
|
+
type: "object",
|
|
61299
|
+
additionalProperties: false,
|
|
61300
|
+
required: ["type"],
|
|
61301
|
+
properties: {
|
|
61302
|
+
type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
|
|
61303
|
+
}
|
|
61304
|
+
},
|
|
61305
|
+
mount: {
|
|
61306
|
+
type: "object",
|
|
61307
|
+
additionalProperties: false,
|
|
61308
|
+
required: ["target", "mode", "order"],
|
|
61309
|
+
properties: {
|
|
61310
|
+
source: { type: "string", minLength: 1 },
|
|
61311
|
+
target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
|
|
61312
|
+
mode: { type: "string", enum: ["ro", "rw"] },
|
|
61313
|
+
order: { type: "integer", minimum: 0 }
|
|
61314
|
+
}
|
|
61315
|
+
},
|
|
60796
61316
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
60797
61317
|
source: {
|
|
60798
61318
|
type: "object",
|