archctx 0.9.0 → 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 +716 -149
- 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
|
@@ -53,6 +53,9 @@ function stableYaml(value, indent = 0) {
|
|
|
53
53
|
return `[]
|
|
54
54
|
`;
|
|
55
55
|
return value.map((item) => {
|
|
56
|
+
if (Array.isArray(item))
|
|
57
|
+
return `${pad}- ${canonicalize(item)}
|
|
58
|
+
`;
|
|
56
59
|
if (item !== null && typeof item === "object") {
|
|
57
60
|
return `${pad}- ${stableYaml(item, indent + 2).trimStart()}`;
|
|
58
61
|
}
|
|
@@ -2374,8 +2377,9 @@ var init_failure = __esm(() => {
|
|
|
2374
2377
|
"model-missing": { code: "AC_MODEL_ADOPTION_REQUIRED", message: "Architecture debt requires an adopted model.", fields: ["checks"] },
|
|
2375
2378
|
"changed-path-invalid": { code: "AC_SCHEMA_INVALID", message: "Changed paths must be an array of repository-relative POSIX paths.", fields: ["checks"] },
|
|
2376
2379
|
"debt-report-invalid": { code: "AC_SCHEMA_INVALID", message: "Architecture debt evidence violates its contract.", fields: ["checks"] },
|
|
2377
|
-
"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>." },
|
|
2378
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" } },
|
|
2379
2383
|
"projection-footprint-uncovered": { code: "AC_PRECONDITION_FAILED", message: "Declared source footprints lack a projection target." },
|
|
2380
2384
|
"index-missing": { code: "AC_CODE_FACTS_UNAVAILABLE", message: "Required code facts have no available index. Run codegraph init." },
|
|
2381
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" } },
|
|
@@ -2813,6 +2817,89 @@ var init_failure = __esm(() => {
|
|
|
2813
2817
|
};
|
|
2814
2818
|
});
|
|
2815
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
|
+
|
|
2816
2903
|
// packages/contracts/src/validator.ts
|
|
2817
2904
|
function jsonSchemaResolver(documents) {
|
|
2818
2905
|
const byId = new Map;
|
|
@@ -3156,6 +3243,10 @@ function nativeContextModelIssues(model, schemas, recordPaths) {
|
|
|
3156
3243
|
if (relation[field] !== undefined && !nodes.has(relation[field]))
|
|
3157
3244
|
add(relation, field, "relation-node-missing");
|
|
3158
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
|
+
}
|
|
3159
3250
|
for (const flow of model.flows) {
|
|
3160
3251
|
if (nodes.get(flow.capabilityId)?.kind !== "capability")
|
|
3161
3252
|
add(flow, "capabilityId", "flow-capability-missing");
|
|
@@ -3198,6 +3289,7 @@ var NATIVE_CONTEXT_SCHEMA_VERSION = "archcontext.native-context-export/v1", NATI
|
|
|
3198
3289
|
var init_native_context = __esm(() => {
|
|
3199
3290
|
init_failure();
|
|
3200
3291
|
init_schema();
|
|
3292
|
+
init_runtime_topology();
|
|
3201
3293
|
init_validator();
|
|
3202
3294
|
NATIVE_CONTEXT_LIMITS = Object.freeze({
|
|
3203
3295
|
inputFiles: 1e4,
|
|
@@ -3781,6 +3873,8 @@ var init_projection_manifest = __esm(() => {
|
|
|
3781
3873
|
"diagram-mermaid": true,
|
|
3782
3874
|
"diagram-structurizr": true,
|
|
3783
3875
|
"diagram-likec4": true,
|
|
3876
|
+
"diagram-runtime-topology": true,
|
|
3877
|
+
"runtime-topology-summary": true,
|
|
3784
3878
|
"agent-context": true
|
|
3785
3879
|
};
|
|
3786
3880
|
SCOPE_KINDS = { repository: true, entity: true, relation: true, decision: true, diagram: true, changelog: true };
|
|
@@ -3873,7 +3967,7 @@ function compareVersionTriples(left, right) {
|
|
|
3873
3967
|
}
|
|
3874
3968
|
return 0;
|
|
3875
3969
|
}
|
|
3876
|
-
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";
|
|
3877
3971
|
|
|
3878
3972
|
// packages/contracts/src/refactor.ts
|
|
3879
3973
|
function refactorNamespaceIssues(value, label = "product.id") {
|
|
@@ -4978,6 +5072,7 @@ var init_src = __esm(() => {
|
|
|
4978
5072
|
init_projection_writer();
|
|
4979
5073
|
init_projection_manifest();
|
|
4980
5074
|
init_refactor();
|
|
5075
|
+
init_runtime_topology();
|
|
4981
5076
|
init_schema();
|
|
4982
5077
|
init_failure();
|
|
4983
5078
|
init_validator();
|
|
@@ -17691,6 +17786,15 @@ var FIXED_TARGETS = [
|
|
|
17691
17786
|
layoutTarget("projection_target.diagram.structurizr", "diagram-structurizr", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.structurizr.json", "generated", "structurizr-json"),
|
|
17692
17787
|
layoutTarget("projection_target.diagram.likec4", "diagram-likec4", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.likec4", "generated", "likec4")
|
|
17693
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
|
+
}
|
|
17694
17798
|
function parseRepoHarnessNodeProfile(node) {
|
|
17695
17799
|
const match = /^capability\.([a-z0-9]+(?:-[a-z0-9]+)*)\.([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(node.id);
|
|
17696
17800
|
if (!match || node.kind !== "capability") {
|
|
@@ -17718,7 +17822,7 @@ function parseRepoHarnessNodeProfile(node) {
|
|
|
17718
17822
|
};
|
|
17719
17823
|
}
|
|
17720
17824
|
function entitySummaryTargetNodes(nodes, profile) {
|
|
17721
|
-
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));
|
|
17722
17826
|
}
|
|
17723
17827
|
function resolveArchitectureDocumentationLayout(input) {
|
|
17724
17828
|
const profile = input.profile ?? "default";
|
|
@@ -17728,7 +17832,8 @@ function resolveArchitectureDocumentationLayout(input) {
|
|
|
17728
17832
|
return layoutTarget(`projection_target.entity.${stableId(node.id)}`, "entity-summary", { kind: "entity", id: node.id, entityKind: node.kind }, path, "mixed", "markdown");
|
|
17729
17833
|
});
|
|
17730
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"));
|
|
17731
|
-
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) }));
|
|
17732
17837
|
assertUniqueTargets(targets);
|
|
17733
17838
|
return {
|
|
17734
17839
|
profile,
|
|
@@ -20100,12 +20205,52 @@ function normalizeNativeModel(model) {
|
|
|
20100
20205
|
flows: [...model.flows ?? []].sort((a, b) => a.id.localeCompare(b.id))
|
|
20101
20206
|
};
|
|
20102
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
|
+
}
|
|
20103
20241
|
function exportMermaidModel(model) {
|
|
20104
20242
|
const normalized = normalizeNativeModel(model);
|
|
20243
|
+
const grouping = architectureLayerGroups(normalized);
|
|
20244
|
+
const nodeLine = (indent, node) => `${indent}${mermaidId(node.id)}["${escapeMermaid(node.name)}"]`;
|
|
20105
20245
|
const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart LR"];
|
|
20106
|
-
for (const
|
|
20107
|
-
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");
|
|
20108
20251
|
}
|
|
20252
|
+
for (const node of grouping.ungroupedNodes)
|
|
20253
|
+
lines.push(nodeLine(" ", node));
|
|
20109
20254
|
for (const relation of normalized.relations) {
|
|
20110
20255
|
lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
|
|
20111
20256
|
}
|
|
@@ -20122,22 +20267,15 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20122
20267
|
const normalized = normalizeNativeModel(model);
|
|
20123
20268
|
const lines = [
|
|
20124
20269
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
20125
|
-
|
|
20126
|
-
" element capability",
|
|
20127
|
-
" element module",
|
|
20128
|
-
" element component",
|
|
20129
|
-
" element interface",
|
|
20130
|
-
" element datastore",
|
|
20131
|
-
" element external_system",
|
|
20132
|
-
"}",
|
|
20270
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
20133
20271
|
"",
|
|
20134
20272
|
"model {"
|
|
20135
20273
|
];
|
|
20136
20274
|
for (const node of normalized.nodes)
|
|
20137
|
-
lines.push(` ${mermaidId(node.id)} = ${
|
|
20275
|
+
lines.push(` ${mermaidId(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl(node.name)}"`);
|
|
20138
20276
|
for (const relation of normalized.relations)
|
|
20139
20277
|
lines.push(` ${mermaidId(relation.source)} -> ${mermaidId(relation.target)} "${escapeDsl(relation.kind)}"`);
|
|
20140
|
-
lines.push("}", "",
|
|
20278
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), mermaidId));
|
|
20141
20279
|
const content = `${lines.join(`
|
|
20142
20280
|
`)}
|
|
20143
20281
|
`;
|
|
@@ -20147,18 +20285,73 @@ function exportDocumentationLikeC4Model(model) {
|
|
|
20147
20285
|
files: [{ path: "docs/architecture/diagrams/architecture.likec4", content }]
|
|
20148
20286
|
};
|
|
20149
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
|
+
}
|
|
20150
20338
|
function exportDocumentationStructurizrWorkspace(model) {
|
|
20151
20339
|
const normalized = normalizeNativeModel(model);
|
|
20340
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
20152
20341
|
const workspace = {
|
|
20153
20342
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
20154
20343
|
name: "ArchContext Architecture",
|
|
20155
20344
|
model: {
|
|
20156
|
-
elements: normalized.nodes.map((node) =>
|
|
20157
|
-
|
|
20158
|
-
|
|
20159
|
-
|
|
20160
|
-
|
|
20161
|
-
|
|
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
|
+
}),
|
|
20162
20355
|
relationships: normalized.relations.map((relation) => ({
|
|
20163
20356
|
id: relation.id,
|
|
20164
20357
|
sourceId: relation.source,
|
|
@@ -20181,6 +20374,209 @@ function exportDocumentationStructurizrWorkspace(model) {
|
|
|
20181
20374
|
files: [{ path: "docs/architecture/diagrams/architecture.structurizr.json", content }]
|
|
20182
20375
|
};
|
|
20183
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
|
+
}
|
|
20184
20580
|
function loadNativeModelFromArchContext(root) {
|
|
20185
20581
|
return nativeModelFromYamlObjects({
|
|
20186
20582
|
nodes: readYamlObjects(resolve6(root, ".archcontext/model/nodes")),
|
|
@@ -20199,6 +20595,7 @@ function nativeModelFromYamlObjects(input) {
|
|
|
20199
20595
|
throw new Error(`architecture-node-schema-version-unsupported: ${node.id || "unknown"} (${String(node.schemaVersion)})`);
|
|
20200
20596
|
}
|
|
20201
20597
|
assertArchitectureNodeSourceV2(node);
|
|
20598
|
+
nativeNodeLayer(node);
|
|
20202
20599
|
}
|
|
20203
20600
|
const flows = input.flows;
|
|
20204
20601
|
for (const flow of flows)
|
|
@@ -20383,8 +20780,15 @@ function renderTargetGeneratedBody(target, model, input) {
|
|
|
20383
20780
|
return exportDocumentationStructurizrWorkspace(model).files[0].content;
|
|
20384
20781
|
if (target.type === "diagram-likec4")
|
|
20385
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);
|
|
20386
20787
|
return "";
|
|
20387
20788
|
}
|
|
20789
|
+
function architectureIndexLink(path) {
|
|
20790
|
+
return path.replace(/^docs\/architecture\//, "");
|
|
20791
|
+
}
|
|
20388
20792
|
function renderArchitectureIndex(model, layout2) {
|
|
20389
20793
|
const lines = [
|
|
20390
20794
|
"# Architecture Index",
|
|
@@ -20409,6 +20813,7 @@ function renderArchitectureIndex(model, layout2) {
|
|
|
20409
20813
|
"- [Mermaid](diagrams/architecture.mmd)",
|
|
20410
20814
|
"- [Structurizr JSON](diagrams/architecture.structurizr.json)",
|
|
20411
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)})`] : []),
|
|
20412
20817
|
"- [Decision index](decisions/index.md)",
|
|
20413
20818
|
"- [Architecture changelog](changelog.md)"
|
|
20414
20819
|
];
|
|
@@ -20862,26 +21267,6 @@ function isManagedArchitectureDocumentationMarkdownPath(path) {
|
|
|
20862
21267
|
function pathSegment2(id) {
|
|
20863
21268
|
return stableId(id).replace(/\./g, "-");
|
|
20864
21269
|
}
|
|
20865
|
-
function documentationLikeC4Kind(kind) {
|
|
20866
|
-
if (kind === "external-system")
|
|
20867
|
-
return "external_system";
|
|
20868
|
-
if (kind === "datastore")
|
|
20869
|
-
return "datastore";
|
|
20870
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
20871
|
-
return kind;
|
|
20872
|
-
return "component";
|
|
20873
|
-
}
|
|
20874
|
-
function documentationStructurizrElementType(kind) {
|
|
20875
|
-
if (kind === "capability")
|
|
20876
|
-
return "Software System";
|
|
20877
|
-
if (kind === "module")
|
|
20878
|
-
return "Container";
|
|
20879
|
-
if (kind === "datastore")
|
|
20880
|
-
return "Database";
|
|
20881
|
-
if (kind === "external-system")
|
|
20882
|
-
return "External System";
|
|
20883
|
-
return "Component";
|
|
20884
|
-
}
|
|
20885
21270
|
function escapeDsl(value) {
|
|
20886
21271
|
return value.replace(/"/g, "'");
|
|
20887
21272
|
}
|
|
@@ -21545,7 +21930,7 @@ function sameOwners(left, right) {
|
|
|
21545
21930
|
|
|
21546
21931
|
// packages/core/module-statistics/src/index.ts
|
|
21547
21932
|
function evaluateDependencyConstraints(input) {
|
|
21548
|
-
const constraints = [...input.constraints].sort((left, right) =>
|
|
21933
|
+
const constraints = [...input.constraints].sort((left, right) => compare2(left.id, right.id));
|
|
21549
21934
|
const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded ?? false);
|
|
21550
21935
|
if (constraints.length === 0) {
|
|
21551
21936
|
return {
|
|
@@ -21602,7 +21987,7 @@ function evaluateDependencyConstraints(input) {
|
|
|
21602
21987
|
reasonCodes.add("unresolved-import");
|
|
21603
21988
|
if (input.workspacePackagesResolved === false)
|
|
21604
21989
|
reasonCodes.add("workspace-resolution-failed");
|
|
21605
|
-
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));
|
|
21606
21991
|
return {
|
|
21607
21992
|
schemaVersion: "archcontext.dependency-constraint-evaluation/v1",
|
|
21608
21993
|
status: sortedViolations.length > 0 ? "violated" : reasonCodes.size > 0 ? "undetermined" : "pass",
|
|
@@ -21679,7 +22064,7 @@ function measureCapabilityDependencies(input) {
|
|
|
21679
22064
|
const cellKey = JSON.stringify([from.capability, to.capability]);
|
|
21680
22065
|
let cell = cells.get(cellKey);
|
|
21681
22066
|
if (cell === undefined) {
|
|
21682
|
-
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(
|
|
22067
|
+
const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(compare2);
|
|
21683
22068
|
cell = { from: from.capability, to: to.capability, imports: 0, calls: null, declared: from.capability === to.capability || relationIds.length > 0, relationIds };
|
|
21684
22069
|
cells.set(cellKey, cell);
|
|
21685
22070
|
}
|
|
@@ -21687,14 +22072,14 @@ function measureCapabilityDependencies(input) {
|
|
|
21687
22072
|
attributedEdges.push({ fromPath: edge.from, toPath: edge.to, fromCapability: from.capability, toCapability: to.capability });
|
|
21688
22073
|
}
|
|
21689
22074
|
const edgeLimit = input.edgeLimit ?? CAPABILITY_DEPENDENCIES_EDGE_LIMIT;
|
|
21690
|
-
attributedEdges.sort((left, right) =>
|
|
22075
|
+
attributedEdges.sort((left, right) => compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
|
|
21691
22076
|
const listed = attributedEdges.slice(0, edgeLimit);
|
|
21692
22077
|
return {
|
|
21693
22078
|
schemaVersion: CAPABILITY_DEPENDENCIES_SCHEMA_VERSION,
|
|
21694
22079
|
ownershipRule: "parent-chain",
|
|
21695
22080
|
filter: { from: input.from ?? null, into: input.into ?? null },
|
|
21696
|
-
capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(
|
|
21697
|
-
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)),
|
|
21698
22083
|
imports: {
|
|
21699
22084
|
coverage: certification.coverage,
|
|
21700
22085
|
reasonCodes: [...certification.reasonCodes].sort(),
|
|
@@ -21730,7 +22115,7 @@ function isRepositoryLocalSpecifier(specifier, workspacePackages) {
|
|
|
21730
22115
|
return true;
|
|
21731
22116
|
return workspacePackages.some((workspacePackage) => specifier === workspacePackage.name || specifier.startsWith(`${workspacePackage.name}/`));
|
|
21732
22117
|
}
|
|
21733
|
-
function
|
|
22118
|
+
function compare2(left, right) {
|
|
21734
22119
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
21735
22120
|
}
|
|
21736
22121
|
function measureModuleStatistics(input) {
|
|
@@ -21815,7 +22200,7 @@ function modelDigest(model, constraints) {
|
|
|
21815
22200
|
nodes: [...model.nodes].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21816
22201
|
relations: [...model.relations].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21817
22202
|
flows: [...model.flows ?? []].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
|
|
21818
|
-
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) =>
|
|
22203
|
+
...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare2(left.id, right.id)) }
|
|
21819
22204
|
});
|
|
21820
22205
|
}
|
|
21821
22206
|
function countViolationsByNode(evaluation) {
|
|
@@ -22712,6 +23097,29 @@ function listGitVisibleFiles(root, pathspec) {
|
|
|
22712
23097
|
}
|
|
22713
23098
|
}).sort((left, right) => left.localeCompare(right));
|
|
22714
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
|
+
}
|
|
22715
23123
|
function readHeadSha(root) {
|
|
22716
23124
|
try {
|
|
22717
23125
|
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
@@ -26080,7 +26488,7 @@ function buildObservations(snapshot, structure, model) {
|
|
|
26080
26488
|
evidence: evidence.evidenceGap()
|
|
26081
26489
|
});
|
|
26082
26490
|
}
|
|
26083
|
-
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 }) => ({
|
|
26084
26492
|
kind: draft.kind,
|
|
26085
26493
|
subjectSelectorId: draft.subjectSelectorId,
|
|
26086
26494
|
affectedNodeIds: draft.affectedNodeIds,
|
|
@@ -26270,13 +26678,13 @@ function signalIdFor(draft) {
|
|
|
26270
26678
|
}
|
|
26271
26679
|
function modelDigest2(model, constraints) {
|
|
26272
26680
|
return digestJson({
|
|
26273
|
-
nodes: [...model.nodes].sort((left, right) =>
|
|
26274
|
-
relations: [...model.relations].sort((left, right) =>
|
|
26275
|
-
flows: [...model.flows ?? []].sort((left, right) =>
|
|
26276
|
-
...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)) }
|
|
26277
26685
|
});
|
|
26278
26686
|
}
|
|
26279
|
-
function
|
|
26687
|
+
function compare3(left, right) {
|
|
26280
26688
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
26281
26689
|
}
|
|
26282
26690
|
function isNumber(value) {
|
|
@@ -26298,17 +26706,49 @@ var architecture_node_schema_default = {
|
|
|
26298
26706
|
type: "object",
|
|
26299
26707
|
additionalProperties: false,
|
|
26300
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
|
+
],
|
|
26301
26721
|
properties: {
|
|
26302
26722
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
26303
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-]*)+$" },
|
|
26304
26724
|
kind: {
|
|
26305
26725
|
type: "string",
|
|
26306
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
26726
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
26307
26727
|
},
|
|
26308
26728
|
name: { type: "string" },
|
|
26309
26729
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
26310
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]+)*$" },
|
|
26311
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
|
+
},
|
|
26312
26752
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
26313
26753
|
source: {
|
|
26314
26754
|
type: "object",
|
|
@@ -26397,7 +26837,7 @@ var architecture_relation_schema_default = {
|
|
|
26397
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-]*)+$" },
|
|
26398
26838
|
kind: {
|
|
26399
26839
|
type: "string",
|
|
26400
|
-
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"]
|
|
26401
26841
|
},
|
|
26402
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-]*)+$" },
|
|
26403
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-]*)+$" },
|
|
@@ -27105,6 +27545,22 @@ function createDefaultProjectionTargetManifest() {
|
|
|
27105
27545
|
ownership: "generated",
|
|
27106
27546
|
format: "likec4"
|
|
27107
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
|
+
},
|
|
27108
27564
|
{
|
|
27109
27565
|
id: "projection_rule.agent-context.claude",
|
|
27110
27566
|
targetType: "agent-context",
|
|
@@ -27250,10 +27706,19 @@ class YamlModelStore {
|
|
|
27250
27706
|
}
|
|
27251
27707
|
}
|
|
27252
27708
|
const files = listModelFiles(workspace.root);
|
|
27709
|
+
const topologyRecords = { nodes: [], relations: [] };
|
|
27710
|
+
const topologyRecordPaths = new Map;
|
|
27253
27711
|
for (const file of files) {
|
|
27254
27712
|
if (file.path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(file.path)) {
|
|
27255
27713
|
try {
|
|
27256
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
|
+
}
|
|
27257
27722
|
if (file.path.startsWith(".archcontext/model/nodes/")) {
|
|
27258
27723
|
const { issues } = validateJsonSchema(architecture_node_schema_default, value);
|
|
27259
27724
|
for (const issue of issues) {
|
|
@@ -27274,6 +27739,9 @@ class YamlModelStore {
|
|
|
27274
27739
|
errors.push(`${file.path}: expected archcontext.flow/v1, got ${file.schemaVersion || "missing"}`);
|
|
27275
27740
|
}
|
|
27276
27741
|
}
|
|
27742
|
+
for (const issue of runtimeTopologyModelIssues(topologyRecords)) {
|
|
27743
|
+
errors.push(`${topologyRecordPaths.get(issue.entityId) ?? issue.entityId}: ${issue.field}: ${issue.invariant}`);
|
|
27744
|
+
}
|
|
27277
27745
|
const adr = validateAdrAppliesTo(files);
|
|
27278
27746
|
const constraints = readDependencyConstraints(files);
|
|
27279
27747
|
const reviewPolicy = readReviewPolicy(files);
|
|
@@ -27294,6 +27762,21 @@ class YamlModelStore {
|
|
|
27294
27762
|
return { digest: digestJson(changeSet), summary: "ChangeSet preview generated" };
|
|
27295
27763
|
}
|
|
27296
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
|
+
}
|
|
27297
27780
|
function listModelFiles(root) {
|
|
27298
27781
|
const paths = collectArchContextFiles(root, [
|
|
27299
27782
|
".archcontext/manifest.yaml",
|
|
@@ -27311,7 +27794,7 @@ function listModelFiles(root) {
|
|
|
27311
27794
|
return {
|
|
27312
27795
|
path,
|
|
27313
27796
|
body,
|
|
27314
|
-
schemaVersion: extractSchemaVersion(body),
|
|
27797
|
+
schemaVersion: extractSchemaVersion(body, path),
|
|
27315
27798
|
digest: digestJson({ path, body })
|
|
27316
27799
|
};
|
|
27317
27800
|
});
|
|
@@ -27357,7 +27840,15 @@ function readdirOrFile(path) {
|
|
|
27357
27840
|
return "file";
|
|
27358
27841
|
}
|
|
27359
27842
|
}
|
|
27360
|
-
function extractSchemaVersion(body) {
|
|
27843
|
+
function extractSchemaVersion(body, path) {
|
|
27844
|
+
if (path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(path)) {
|
|
27845
|
+
try {
|
|
27846
|
+
const parsed = parseJsonOrStableYaml(body, path);
|
|
27847
|
+
return typeof parsed?.schemaVersion === "string" ? parsed.schemaVersion : "";
|
|
27848
|
+
} catch {
|
|
27849
|
+
return "";
|
|
27850
|
+
}
|
|
27851
|
+
}
|
|
27361
27852
|
const match = body.match(/schemaVersion:\s*"?([^"\n]+)"?/);
|
|
27362
27853
|
if (match)
|
|
27363
27854
|
return match[1].trim();
|
|
@@ -27370,6 +27861,9 @@ function extractSchemaVersion(body) {
|
|
|
27370
27861
|
return "archcontext.generated/v1";
|
|
27371
27862
|
return "";
|
|
27372
27863
|
}
|
|
27864
|
+
function isJsonRecord2(value) {
|
|
27865
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
27866
|
+
}
|
|
27373
27867
|
function withTrailingNewline(body) {
|
|
27374
27868
|
return body.endsWith(`
|
|
27375
27869
|
`) ? body : `${body}
|
|
@@ -31307,13 +31801,10 @@ class HttpContext7Transport {
|
|
|
31307
31801
|
url.searchParams.set("libraryName", input.libraryName);
|
|
31308
31802
|
url.searchParams.set("query", input.query);
|
|
31309
31803
|
url.searchParams.set("fast", input.fast ? "true" : "false");
|
|
31310
|
-
const
|
|
31804
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31311
31805
|
method: "GET",
|
|
31312
31806
|
headers: authHeaders(input.apiKey)
|
|
31313
|
-
}, input.timeoutMs);
|
|
31314
|
-
if (!response.ok)
|
|
31315
|
-
throw httpStatusError("Context7 search failed", response);
|
|
31316
|
-
const { payload } = await parseJsonResponse(response, "Context7 search");
|
|
31807
|
+
}, input.timeoutMs, "Context7 search");
|
|
31317
31808
|
return parseSearchResponse(payload);
|
|
31318
31809
|
}
|
|
31319
31810
|
async getContext(input) {
|
|
@@ -31322,13 +31813,10 @@ class HttpContext7Transport {
|
|
|
31322
31813
|
url.searchParams.set("query", input.query);
|
|
31323
31814
|
url.searchParams.set("type", "json");
|
|
31324
31815
|
url.searchParams.set("fast", "true");
|
|
31325
|
-
const
|
|
31816
|
+
const { payload } = await fetchJsonWithTimeout(url, {
|
|
31326
31817
|
method: "GET",
|
|
31327
31818
|
headers: authHeaders(input.apiKey)
|
|
31328
|
-
}, input.timeoutMs);
|
|
31329
|
-
if (!response.ok)
|
|
31330
|
-
throw httpStatusError("Context7 context fetch failed", response);
|
|
31331
|
-
const { payload } = await parseJsonResponse(response, "Context7 context fetch");
|
|
31819
|
+
}, input.timeoutMs, "Context7 context fetch");
|
|
31332
31820
|
return parseDocumentationResponse(payload).slice(0, input.maxResults);
|
|
31333
31821
|
}
|
|
31334
31822
|
}
|
|
@@ -31426,11 +31914,14 @@ function normalizeSourceUri(value) {
|
|
|
31426
31914
|
return;
|
|
31427
31915
|
}
|
|
31428
31916
|
}
|
|
31429
|
-
async function
|
|
31917
|
+
async function fetchJsonWithTimeout(input, init, timeoutMs, label) {
|
|
31430
31918
|
const controller = new AbortController;
|
|
31431
31919
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
31432
31920
|
try {
|
|
31433
|
-
|
|
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);
|
|
31434
31925
|
} catch (error) {
|
|
31435
31926
|
if (isAbortError(error)) {
|
|
31436
31927
|
throw new Context7ProviderError("timeout", "Context7 provider request timed out", { retryable: true, cause: error });
|
|
@@ -32663,6 +33154,11 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32663
33154
|
try {
|
|
32664
33155
|
const subcommand = input.action ?? "plan";
|
|
32665
33156
|
const root = findRepositoryRoot(cwd);
|
|
33157
|
+
if (subcommand === "adopt") {
|
|
33158
|
+
const ignored = architectureModelIgnoredEnvelope("docs.adopt", root);
|
|
33159
|
+
if (ignored)
|
|
33160
|
+
return ignored;
|
|
33161
|
+
}
|
|
32666
33162
|
let profile;
|
|
32667
33163
|
let projection;
|
|
32668
33164
|
let acceptedChange;
|
|
@@ -32778,6 +33274,23 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
|
|
|
32778
33274
|
throw publicOperationFailure(error);
|
|
32779
33275
|
}
|
|
32780
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
|
+
}
|
|
32781
33294
|
function committedArchitectureDocsProfile(root) {
|
|
32782
33295
|
return existsSync9(resolve13(root, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)) ? loadArchitectureDocumentationProfile(root) : "default";
|
|
32783
33296
|
}
|
|
@@ -33088,6 +33601,11 @@ async function runProjectionProtocolCommand(invocation, cwd, daemon) {
|
|
|
33088
33601
|
if (replay)
|
|
33089
33602
|
return replay;
|
|
33090
33603
|
}
|
|
33604
|
+
if (request.mode === "adopt") {
|
|
33605
|
+
const ignored = architectureModelIgnoredEnvelope("projection.run", root);
|
|
33606
|
+
if (ignored)
|
|
33607
|
+
return ignored;
|
|
33608
|
+
}
|
|
33091
33609
|
let observed;
|
|
33092
33610
|
let acceptedChange = request.acceptedChange;
|
|
33093
33611
|
let declinedUnprovableNodeIds;
|
|
@@ -34652,6 +35170,7 @@ init_projection_recovery();
|
|
|
34652
35170
|
init_projection_writer();
|
|
34653
35171
|
init_projection_manifest();
|
|
34654
35172
|
init_refactor();
|
|
35173
|
+
init_runtime_topology();
|
|
34655
35174
|
init_schema();
|
|
34656
35175
|
init_failure();
|
|
34657
35176
|
init_validator();
|
|
@@ -39648,6 +40167,7 @@ class AgentJobService {
|
|
|
39648
40167
|
supersededByJobId: input.supersededByJobId,
|
|
39649
40168
|
now: input.now ?? this.context.clock()
|
|
39650
40169
|
});
|
|
40170
|
+
this.context.abortOwnedAudit(job.job.jobId);
|
|
39651
40171
|
return okEnvelope("jobs.cancel", { job });
|
|
39652
40172
|
}
|
|
39653
40173
|
async runtimeAgentJobInScope(scope, jobId) {
|
|
@@ -47060,7 +47580,7 @@ function upgradeExistingLocalStoreTarget2(paths, integrityCheck) {
|
|
|
47060
47580
|
var RUNTIME_AGENT_JOB_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled", "superseded", "expired"];
|
|
47061
47581
|
var CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION = "archcontext.changeset-model-transition/v1";
|
|
47062
47582
|
function assertChangeSetModelTransition(value, label = "changeset-model-transition-invalid") {
|
|
47063
|
-
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)) {
|
|
47064
47584
|
throw new Error(label);
|
|
47065
47585
|
}
|
|
47066
47586
|
return { schemaVersion: CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION, before: value.before, after: value.after };
|
|
@@ -51348,10 +51868,10 @@ function readChangeSetJournalTaskSessionId(metadataJson, journalId) {
|
|
|
51348
51868
|
} catch {
|
|
51349
51869
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51350
51870
|
}
|
|
51351
|
-
if (!
|
|
51871
|
+
if (!isJsonRecord3(metadata))
|
|
51352
51872
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51353
51873
|
const reason = metadata.reason;
|
|
51354
|
-
if (!
|
|
51874
|
+
if (!isJsonRecord3(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
|
|
51355
51875
|
throw new Error(`changeset-journal-reason-malformed: ${journalId}`);
|
|
51356
51876
|
}
|
|
51357
51877
|
return reason.taskSessionId;
|
|
@@ -51384,7 +51904,7 @@ function committedChangeSetJournalFiles(filesJson, journalId) {
|
|
|
51384
51904
|
if (!Array.isArray(parsed))
|
|
51385
51905
|
throw new Error(`changeset-journal-files-malformed: ${journalId}`);
|
|
51386
51906
|
return parsed.map((entry) => {
|
|
51387
|
-
if (!
|
|
51907
|
+
if (!isJsonRecord3(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
|
|
51388
51908
|
throw new Error(`changeset-journal-file-malformed: ${journalId}`);
|
|
51389
51909
|
}
|
|
51390
51910
|
if (typeof entry.bodyHash !== "string" || entry.bodyHash === "") {
|
|
@@ -51404,7 +51924,7 @@ function committedChangeSetProjectionOwned(metadataJson, journalId) {
|
|
|
51404
51924
|
} catch {
|
|
51405
51925
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51406
51926
|
}
|
|
51407
|
-
return
|
|
51927
|
+
return isJsonRecord3(metadata) && metadata.projectionOwned === true;
|
|
51408
51928
|
}
|
|
51409
51929
|
function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
51410
51930
|
let metadata;
|
|
@@ -51413,7 +51933,7 @@ function committedChangeSetModelTransition(metadataJson, journalId) {
|
|
|
51413
51933
|
} catch {
|
|
51414
51934
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51415
51935
|
}
|
|
51416
|
-
if (!
|
|
51936
|
+
if (!isJsonRecord3(metadata))
|
|
51417
51937
|
throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
|
|
51418
51938
|
if (metadata.modelTransition === undefined)
|
|
51419
51939
|
return;
|
|
@@ -51427,7 +51947,7 @@ function readChangeSetJournalMetadata(db, journalId) {
|
|
|
51427
51947
|
}
|
|
51428
51948
|
function withChangeSetJournalArchitectureLedger(metadata, patch) {
|
|
51429
51949
|
const existing = metadata.architectureLedger;
|
|
51430
|
-
const architectureLedger =
|
|
51950
|
+
const architectureLedger = isJsonRecord3(existing) ? { ...existing, ...patch } : {
|
|
51431
51951
|
schemaVersion: "archcontext.changeset-ledger-recovery/v1",
|
|
51432
51952
|
...patch
|
|
51433
51953
|
};
|
|
@@ -51449,12 +51969,12 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51449
51969
|
const architectureLedger = metadata.architectureLedger;
|
|
51450
51970
|
if (architectureLedger === undefined)
|
|
51451
51971
|
return;
|
|
51452
|
-
if (!
|
|
51972
|
+
if (!isJsonRecord3(architectureLedger))
|
|
51453
51973
|
throw new Error("changeset-ledger-recovery-metadata-malformed");
|
|
51454
51974
|
const plannedEvent = architectureLedger.plannedEvent;
|
|
51455
51975
|
if (plannedEvent === undefined)
|
|
51456
51976
|
return;
|
|
51457
|
-
if (!
|
|
51977
|
+
if (!isJsonRecord3(plannedEvent))
|
|
51458
51978
|
throw new Error("changeset-ledger-recovery-planned-event-malformed");
|
|
51459
51979
|
const event = plannedEvent;
|
|
51460
51980
|
try {
|
|
@@ -51464,7 +51984,7 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
|
|
|
51464
51984
|
}
|
|
51465
51985
|
return event;
|
|
51466
51986
|
}
|
|
51467
|
-
function
|
|
51987
|
+
function isJsonRecord3(value) {
|
|
51468
51988
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51469
51989
|
}
|
|
51470
51990
|
function recoverJournalFiles(root, files) {
|
|
@@ -54391,7 +54911,7 @@ class ProjectionWriterFailure extends Error {
|
|
|
54391
54911
|
function projectionWriterPaths(repositoryRoot, remote, base, env = process.env) {
|
|
54392
54912
|
const key = createHash13("sha256").update(`${remote}\x00${base}`, "utf8").digest("hex").slice(0, 16);
|
|
54393
54913
|
const dir = join23(runtimeStatePaths(repositoryRoot, env).repositoryStateDir, "projection-writer", key);
|
|
54394
|
-
return { dir, worktree: join23(dir, "worktree"), state: join23(dir, "state.json"), lock: join23(dir, "lock") };
|
|
54914
|
+
return { dir, baseRef: `refs/archctx/projection-writer/${key}/base`, worktree: join23(dir, "worktree"), state: join23(dir, "state.json"), lock: join23(dir, "lock") };
|
|
54395
54915
|
}
|
|
54396
54916
|
async function runProjectionPublishCommand(options) {
|
|
54397
54917
|
const env = options.env ?? process.env;
|
|
@@ -54483,10 +55003,21 @@ async function runLockedProjectionPublish(input) {
|
|
|
54483
55003
|
const failed = (failure, fields = {}, extra = {}) => finish("failed", { ...fields, error: writerError(failure) }, extra);
|
|
54484
55004
|
try {
|
|
54485
55005
|
const pushRemote = projectionWriterPushUrl(repositoryRoot, remote);
|
|
55006
|
+
const fetchUrls = git(repositoryRoot, ["remote", "get-url", "--all", "--", remote]).trim().split(`
|
|
55007
|
+
`).filter(Boolean);
|
|
55008
|
+
const tracksPublication = fetchUrls.length === 1 && fetchUrls[0] === pushRemote;
|
|
54486
55009
|
while (iterations < PROJECTION_WRITER_MAX_ITERATIONS) {
|
|
54487
55010
|
iterations += 1;
|
|
54488
|
-
remoteGit(repositoryRoot, [
|
|
54489
|
-
|
|
55011
|
+
remoteGit(repositoryRoot, [
|
|
55012
|
+
"fetch",
|
|
55013
|
+
"--no-tags",
|
|
55014
|
+
"--no-write-fetch-head",
|
|
55015
|
+
"--",
|
|
55016
|
+
pushRemote,
|
|
55017
|
+
`+refs/heads/${base}:${paths.baseRef}`,
|
|
55018
|
+
...tracksPublication ? [`+refs/heads/${base}:refs/remotes/${remote}/${base}`] : []
|
|
55019
|
+
], env);
|
|
55020
|
+
baseSha = git(repositoryRoot, ["rev-parse", "--verify", `${paths.baseRef}^{commit}`]).trim();
|
|
54490
55021
|
const successMatches = state.lastSuccessfulBranch === branch && state.lastSuccessfulSha === baseSha;
|
|
54491
55022
|
const pending = state.publisherPending?.branch === branch && state.publisherPending.baseSha === baseSha ? state.publisherPending : successMatches && state.lastSuccessfulCommit && state.lastSuccessfulPublisher !== publisherIdentity ? { branch, baseSha, commitSha: state.lastSuccessfulCommit, files: state.lastSuccessfulFiles } : null;
|
|
54492
55023
|
if (!pending && successMatches && state.lastSuccessfulPublisher === publisherIdentity && (state.lastSuccessfulCommit === null || lsRemoteBranch(repositoryRoot, pushRemote, branch, env) === state.lastSuccessfulCommit)) {
|
|
@@ -54535,8 +55066,15 @@ async function runLockedProjectionPublish(input) {
|
|
|
54535
55066
|
throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-apply-status-unsupported", `projection apply ended with status ${projectionStatus}; the writer publishes only noop, applied and applied-partial results`);
|
|
54536
55067
|
}
|
|
54537
55068
|
const changed = worktreeChangedPaths(paths.worktree);
|
|
54538
|
-
if (changed.length === 0)
|
|
55069
|
+
if (changed.length === 0) {
|
|
55070
|
+
const tip = lsRemoteBranch(repositoryRoot, pushRemote, base, env);
|
|
55071
|
+
if (tip === null) {
|
|
55072
|
+
return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-missing", `${remote} has no branch ${base}`), { projectionStatus });
|
|
55073
|
+
}
|
|
55074
|
+
if (tip !== baseSha)
|
|
55075
|
+
continue;
|
|
54539
55076
|
return finish("up-to-date", { projectionStatus });
|
|
55077
|
+
}
|
|
54540
55078
|
const written = new Set(result.files.filter((file) => file.action !== "unchanged").map((file) => file.path));
|
|
54541
55079
|
const offending = changed.filter((path) => !written.has(path) || !isProjectionOwnedPath(path, ownedPatterns));
|
|
54542
55080
|
if (offending.length > 0) {
|
|
@@ -54551,7 +55089,7 @@ async function runLockedProjectionPublish(input) {
|
|
|
54551
55089
|
return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-commit-failed", `git commit failed in the writer worktree; configure a git identity (user.name, user.email) for the repository or set GIT_AUTHOR_*/GIT_COMMITTER_*: ${errorMessage2(error)}`), { projectionStatus, files: changed });
|
|
54552
55090
|
}
|
|
54553
55091
|
const commitSha = git(paths.worktree, ["rev-parse", "HEAD"]).trim();
|
|
54554
|
-
const tip = lsRemoteBranch(repositoryRoot,
|
|
55092
|
+
const tip = lsRemoteBranch(repositoryRoot, pushRemote, base, env);
|
|
54555
55093
|
if (tip === null) {
|
|
54556
55094
|
return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-missing", `${remote} has no branch ${base}`), { projectionStatus, files: changed });
|
|
54557
55095
|
}
|
|
@@ -54900,8 +55438,8 @@ function createGhProjectionPublisher(options = {}) {
|
|
|
54900
55438
|
const env = { ...stringEnv(process.env), GH_PROMPT_DISABLED: "1" };
|
|
54901
55439
|
delete env.GH_REPO;
|
|
54902
55440
|
const gh = (args) => withLocalEgress2("projection-publishing", () => exec("gh", [...args, "--repo", repository], { cwd: input.worktree, env, timeoutMs }), admissionEnv);
|
|
54903
|
-
const listed = await gh(["pr", "list", "--head", input.branch, "--base", input.base, "--state", "open", "--json", "number,url", "--limit", "
|
|
54904
|
-
const open = parseGhPullRequestList(listed.stdout);
|
|
55441
|
+
const listed = await gh(["pr", "list", "--head", input.branch, "--base", input.base, "--state", "open", "--json", "number,url,isCrossRepository,headRefName,baseRefName", "--limit", "100"]);
|
|
55442
|
+
const open = parseGhPullRequestList(listed.stdout, input.branch, input.base);
|
|
54905
55443
|
if (open) {
|
|
54906
55444
|
await gh(["pr", "edit", String(open.number), "--title", input.title, "--body-file", input.bodyFile]);
|
|
54907
55445
|
return { number: open.number, ...open.url ? { url: open.url } : {} };
|
|
@@ -55049,16 +55587,24 @@ function readCommandFirstLine(command) {
|
|
|
55049
55587
|
closeSync9(fd);
|
|
55050
55588
|
}
|
|
55051
55589
|
}
|
|
55052
|
-
function parseGhPullRequestList(stdout) {
|
|
55053
|
-
const parsed = JSON.parse(stdout
|
|
55590
|
+
function parseGhPullRequestList(stdout, branch, base) {
|
|
55591
|
+
const parsed = JSON.parse(stdout);
|
|
55054
55592
|
if (!Array.isArray(parsed))
|
|
55055
55593
|
throw new Error("gh pr list returned an unexpected shape");
|
|
55056
|
-
|
|
55057
|
-
|
|
55058
|
-
|
|
55059
|
-
|
|
55060
|
-
|
|
55061
|
-
|
|
55594
|
+
if (parsed.length >= 100)
|
|
55595
|
+
throw new Error("gh pr list reached the 100-result limit; resolve rolling PR ambiguity manually");
|
|
55596
|
+
const matches = [];
|
|
55597
|
+
for (const row of parsed) {
|
|
55598
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.isCrossRepository !== "boolean" || row.headRefName !== branch || row.baseRefName !== base || typeof row.number !== "number" || !Number.isInteger(row.number) || row.number <= 0) {
|
|
55599
|
+
throw new Error("gh pr list returned a pull request without verified identity");
|
|
55600
|
+
}
|
|
55601
|
+
if (!row.isCrossRepository) {
|
|
55602
|
+
matches.push({ number: row.number, ...typeof row.url === "string" && row.url !== "" ? { url: row.url } : {} });
|
|
55603
|
+
}
|
|
55604
|
+
}
|
|
55605
|
+
if (matches.length > 1)
|
|
55606
|
+
throw new Error("gh pr list returned multiple same-repository rolling pull requests");
|
|
55607
|
+
return matches[0];
|
|
55062
55608
|
}
|
|
55063
55609
|
function parsePublisherCommandOutput(stdout) {
|
|
55064
55610
|
let parsed;
|
|
@@ -57954,23 +58500,31 @@ class ArchctxRuntimeRpcServer {
|
|
|
57954
58500
|
this.server = undefined;
|
|
57955
58501
|
const connection = this.connection;
|
|
57956
58502
|
this.connection = undefined;
|
|
58503
|
+
const errors = [];
|
|
58504
|
+
const cleanup = async (action) => {
|
|
58505
|
+
try {
|
|
58506
|
+
await action();
|
|
58507
|
+
} catch (error) {
|
|
58508
|
+
errors.push(error);
|
|
58509
|
+
}
|
|
58510
|
+
};
|
|
57957
58511
|
if (server) {
|
|
57958
|
-
await new Promise((resolveClose, rejectClose) => {
|
|
58512
|
+
await cleanup(() => new Promise((resolveClose, rejectClose) => {
|
|
57959
58513
|
server.close((error) => error ? rejectClose(error) : resolveClose());
|
|
57960
|
-
});
|
|
57961
|
-
}
|
|
57962
|
-
await this.daemon.stop();
|
|
57963
|
-
try {
|
|
57964
|
-
if (connection)
|
|
57965
|
-
await removePrivateControlFile(connection.connectionPath);
|
|
57966
|
-
} finally {
|
|
57967
|
-
if (this.lockFd !== undefined)
|
|
57968
|
-
closeSync11(this.lockFd);
|
|
57969
|
-
this.lockFd = undefined;
|
|
58514
|
+
}));
|
|
57970
58515
|
}
|
|
58516
|
+
await cleanup(() => this.daemon.stop());
|
|
57971
58517
|
if (connection)
|
|
57972
|
-
await removePrivateControlFile(connection.
|
|
57973
|
-
this.
|
|
58518
|
+
await cleanup(() => removePrivateControlFile(connection.connectionPath));
|
|
58519
|
+
const lockFd = this.lockFd;
|
|
58520
|
+
this.lockFd = undefined;
|
|
58521
|
+
if (lockFd !== undefined)
|
|
58522
|
+
await cleanup(() => closeSync11(lockFd));
|
|
58523
|
+
if (connection)
|
|
58524
|
+
await cleanup(() => removePrivateControlFile(connection.lockPath));
|
|
58525
|
+
await cleanup(() => this.options.onStop?.());
|
|
58526
|
+
if (errors.length > 0)
|
|
58527
|
+
throw errors[0];
|
|
57974
58528
|
}
|
|
57975
58529
|
armIdleTimer() {
|
|
57976
58530
|
if (this.idleTimeoutMs <= 0 || !this.server)
|
|
@@ -58486,6 +59040,7 @@ class ArchctxDaemon {
|
|
|
58486
59040
|
localStore: this.localStore
|
|
58487
59041
|
});
|
|
58488
59042
|
this.agentJobs = new AgentJobService({
|
|
59043
|
+
abortOwnedAudit: (jobId) => this.auditRunAbortControllers.get(jobId)?.abort(),
|
|
58489
59044
|
assertRunning: () => this.assertRunning(),
|
|
58490
59045
|
openSession: (root) => this.openSession(root),
|
|
58491
59046
|
architectureLedgerScope: (root) => this.architectureLedgerScope(root),
|
|
@@ -59312,11 +59867,13 @@ class ArchctxDaemon {
|
|
|
59312
59867
|
return okEnvelope("status", status);
|
|
59313
59868
|
const repositoryId = repositoryFingerprint(root);
|
|
59314
59869
|
const session = this.sessions.get(repositoryId);
|
|
59870
|
+
const ignoredModelPaths = ignoredArchitectureModelPaths(root);
|
|
59315
59871
|
return okEnvelope("status", {
|
|
59316
59872
|
...status,
|
|
59317
59873
|
repositoryId,
|
|
59318
59874
|
headSha: session?.workspace.headSha ?? readHeadSha(root),
|
|
59319
|
-
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root))
|
|
59875
|
+
worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root)),
|
|
59876
|
+
...ignoredModelPaths.length === 0 ? {} : { architectureModelIgnoredPaths: ignoredModelPaths }
|
|
59320
59877
|
});
|
|
59321
59878
|
} catch (error) {
|
|
59322
59879
|
throw publicOperationFailure(error);
|
|
@@ -60031,26 +60588,19 @@ function exportLikeC4Model(model) {
|
|
|
60031
60588
|
const normalized = normalizeNativeModel(model);
|
|
60032
60589
|
const lines = [
|
|
60033
60590
|
"// Generated by ArchContext. Native model remains source of truth.",
|
|
60034
|
-
|
|
60035
|
-
" element capability",
|
|
60036
|
-
" element module",
|
|
60037
|
-
" element component",
|
|
60038
|
-
" element interface",
|
|
60039
|
-
" element datastore",
|
|
60040
|
-
" element external_system",
|
|
60041
|
-
"}",
|
|
60591
|
+
...likeC4SpecificationLines(normalized.nodes),
|
|
60042
60592
|
"",
|
|
60043
60593
|
"model {"
|
|
60044
60594
|
];
|
|
60045
60595
|
for (const node of normalized.nodes) {
|
|
60046
60596
|
lines.push(` // archctx-node ${JSON.stringify(node)}`);
|
|
60047
|
-
lines.push(` ${likec4Id(node.id)} = ${
|
|
60597
|
+
lines.push(` ${likec4Id(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl2(node.name)}"`);
|
|
60048
60598
|
}
|
|
60049
60599
|
for (const relation of normalized.relations) {
|
|
60050
60600
|
lines.push(` // archctx-relation ${JSON.stringify(relation)}`);
|
|
60051
60601
|
lines.push(` ${likec4Id(relation.source)} -> ${likec4Id(relation.target)} "${escapeDsl2(relation.kind)}"`);
|
|
60052
60602
|
}
|
|
60053
|
-
lines.push("}", "",
|
|
60603
|
+
lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), likec4Id));
|
|
60054
60604
|
const content = `${lines.join(`
|
|
60055
60605
|
`)}
|
|
60056
60606
|
`;
|
|
@@ -60080,15 +60630,6 @@ function importLikeC4InitialModel(content) {
|
|
|
60080
60630
|
function likec4Id(id) {
|
|
60081
60631
|
return stableId(id).replace(/-/g, "_").replace(/\./g, "_").replace(/::/g, "__");
|
|
60082
60632
|
}
|
|
60083
|
-
function likec4Kind(kind) {
|
|
60084
|
-
if (kind === "external-system")
|
|
60085
|
-
return "external_system";
|
|
60086
|
-
if (kind === "datastore")
|
|
60087
|
-
return "datastore";
|
|
60088
|
-
if (["capability", "module", "component", "interface"].includes(kind))
|
|
60089
|
-
return kind;
|
|
60090
|
-
return "component";
|
|
60091
|
-
}
|
|
60092
60633
|
function escapeDsl2(value) {
|
|
60093
60634
|
return value.replace(/"/g, "'");
|
|
60094
60635
|
}
|
|
@@ -60097,17 +60638,22 @@ function escapeDsl2(value) {
|
|
|
60097
60638
|
init_src();
|
|
60098
60639
|
function exportStructurizrWorkspace(model, name = "ArchContext Export") {
|
|
60099
60640
|
const normalized = normalizeNativeModel(model);
|
|
60641
|
+
const { layerByNodeId } = architectureLayerGroups(normalized);
|
|
60100
60642
|
const workspace = {
|
|
60101
60643
|
schemaVersion: "archcontext.structurizr-export/v1",
|
|
60102
60644
|
name,
|
|
60103
60645
|
model: {
|
|
60104
|
-
elements: normalized.nodes.map((node) =>
|
|
60105
|
-
|
|
60106
|
-
|
|
60107
|
-
|
|
60108
|
-
|
|
60109
|
-
|
|
60110
|
-
|
|
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
|
+
}),
|
|
60111
60657
|
relationships: normalized.relations.map((relation) => ({
|
|
60112
60658
|
id: relation.id,
|
|
60113
60659
|
sourceId: relation.source,
|
|
@@ -60151,17 +60697,6 @@ function importStructurizrInitialModel(content) {
|
|
|
60151
60697
|
warnings: ["Structurizr import is initialization-only; Native ArchContext remains source of truth."]
|
|
60152
60698
|
};
|
|
60153
60699
|
}
|
|
60154
|
-
function structurizrElementType(kind) {
|
|
60155
|
-
if (kind === "capability")
|
|
60156
|
-
return "Software System";
|
|
60157
|
-
if (kind === "module")
|
|
60158
|
-
return "Container";
|
|
60159
|
-
if (kind === "datastore")
|
|
60160
|
-
return "Database";
|
|
60161
|
-
if (kind === "external-system")
|
|
60162
|
-
return "External System";
|
|
60163
|
-
return "Component";
|
|
60164
|
-
}
|
|
60165
60700
|
|
|
60166
60701
|
// packages/surfaces/mcp-local/src/tool-schemas.ts
|
|
60167
60702
|
var text2 = { type: "string", minLength: 1 };
|
|
@@ -60735,17 +61270,49 @@ var architecture_node_schema_default2 = {
|
|
|
60735
61270
|
type: "object",
|
|
60736
61271
|
additionalProperties: false,
|
|
60737
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
|
+
],
|
|
60738
61285
|
properties: {
|
|
60739
61286
|
schemaVersion: { const: "archcontext.node/v2" },
|
|
60740
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-]*)+$" },
|
|
60741
61288
|
kind: {
|
|
60742
61289
|
type: "string",
|
|
60743
|
-
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
|
|
61290
|
+
enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
|
|
60744
61291
|
},
|
|
60745
61292
|
name: { type: "string" },
|
|
60746
61293
|
status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
|
|
60747
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]+)*$" },
|
|
60748
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
|
+
},
|
|
60749
61316
|
responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
60750
61317
|
source: {
|
|
60751
61318
|
type: "object",
|