archctx 0.9.1 → 0.9.3

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/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,
@@ -3373,6 +3462,9 @@ var PROJECTION_PUBLISH_RUN_SCHEMA_VERSION = "archcontext.projection-publish-run/
3373
3462
  var init_projection_writer = () => {};
3374
3463
 
3375
3464
  // packages/contracts/src/projection-manifest.ts
3465
+ function isRuntimeTopologyTargetType(type) {
3466
+ return typeof type === "string" && RUNTIME_TOPOLOGY_TARGET_TYPES.includes(type);
3467
+ }
3376
3468
  function architectureDocsProjectionManifestIssues(value) {
3377
3469
  const issues = [];
3378
3470
  const manifest = record3(value);
@@ -3408,6 +3500,7 @@ function architectureDocsProjectionManifestIssues(value) {
3408
3500
  }
3409
3501
  const paths = new Set;
3410
3502
  const stampOwners = new Map;
3503
+ let topologyStamps;
3411
3504
  for (const [targetId, row] of Object.entries(targets)) {
3412
3505
  const prefix = `manifest.targets[${JSON.stringify(targetId)}]`;
3413
3506
  if (!isNonEmptyString(targetId))
@@ -3419,7 +3512,23 @@ function architectureDocsProjectionManifestIssues(value) {
3419
3512
  issues.push(`${prefix}.path is recorded by another target`);
3420
3513
  paths.add(path);
3421
3514
  }
3422
- for (const nodeId of Object.keys(record3(record3(row)?.sourceStamps) ?? {})) {
3515
+ const stamps = record3(record3(row)?.sourceStamps) ?? {};
3516
+ if (isRuntimeTopologyTargetType(record3(row)?.type)) {
3517
+ const serialized = JSON.stringify(Object.keys(stamps).sort(compareStrings).map((nodeId) => [nodeId, stamps[nodeId]]));
3518
+ if (topologyStamps === undefined)
3519
+ topologyStamps = { targetId, stamps: serialized };
3520
+ else if (topologyStamps.stamps !== serialized)
3521
+ issues.push(`${prefix}.sourceStamps must equal the sourceStamps of ${topologyStamps.targetId}`);
3522
+ for (const nodeId of Object.keys(stamps)) {
3523
+ const owner = stampOwners.get(nodeId);
3524
+ if (owner !== undefined && !isRuntimeTopologyTargetType(record3(targets[owner])?.type))
3525
+ issues.push(`${prefix}.sourceStamps.${nodeId} is already stamped on ${owner}`);
3526
+ else if (owner === undefined)
3527
+ stampOwners.set(nodeId, targetId);
3528
+ }
3529
+ continue;
3530
+ }
3531
+ for (const nodeId of Object.keys(stamps)) {
3423
3532
  const owner = stampOwners.get(nodeId);
3424
3533
  if (owner !== undefined)
3425
3534
  issues.push(`${prefix}.sourceStamps.${nodeId} is already stamped on ${owner}`);
@@ -3674,9 +3783,11 @@ function manifestTargetIssues(value, prefix) {
3674
3783
  issues.push(`${prefix}.format is unsupported`);
3675
3784
  if (!isDigest(target.outputDigest))
3676
3785
  issues.push(`${prefix}.outputDigest must be a SHA-256 digest`);
3677
- if ((target.sourceStamps !== undefined || target.scale !== undefined) && target.type !== "entity-summary") {
3678
- issues.push(`${prefix}.sourceStamps and scale are only allowed on entity-summary targets`);
3786
+ if (target.sourceStamps !== undefined && target.type !== "entity-summary" && !isRuntimeTopologyTargetType(target.type)) {
3787
+ issues.push(`${prefix}.sourceStamps are only allowed on entity-summary and runtime topology targets`);
3679
3788
  }
3789
+ if (target.scale !== undefined && target.type !== "entity-summary")
3790
+ issues.push(`${prefix}.scale is only allowed on entity-summary targets`);
3680
3791
  if (target.sourceStamps !== undefined) {
3681
3792
  const stamps = record3(target.sourceStamps);
3682
3793
  if (!stamps || Object.keys(stamps).length === 0)
@@ -3765,7 +3876,7 @@ function architectureDocsProjectionCompatibilityIssues(value, engine = {
3765
3876
  }
3766
3877
  return issues;
3767
3878
  }
3768
- var ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSION = "archcontext.architecture-docs-projection-manifest/v3", SUPERSEDED_ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSIONS, PROJECTION_MANIFEST_UPGRADE_REQUIRED = "projection-manifest-upgrade-required", PROJECTION_MANIFEST_INVALID = "projection-manifest-invalid", PROJECTION_FOOTPRINT_UNCOVERED = "projection-footprint-uncovered", ARCHITECTURE_DOCS_PROJECTION_PROVENANCE_SCHEMA_VERSION = "archcontext.architecture-docs-projection-provenance/v3", ARCHITECTURE_DOCS_LAYOUT_VERSION_V1 = "archcontext.docs-layout/v1", ARCHITECTURE_SEMANTIC_STATE_SCHEMA_VERSION = "archcontext.architecture-semantic-state/v1", ARCHITECTURE_DOCS_PROJECTION_PROFILES, SHA256, TARGET_TYPES, SCOPE_KINDS, OWNERSHIPS, FORMATS;
3879
+ var ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSION = "archcontext.architecture-docs-projection-manifest/v3", SUPERSEDED_ARCHITECTURE_DOCS_PROJECTION_MANIFEST_SCHEMA_VERSIONS, PROJECTION_MANIFEST_UPGRADE_REQUIRED = "projection-manifest-upgrade-required", PROJECTION_MANIFEST_INVALID = "projection-manifest-invalid", PROJECTION_FOOTPRINT_UNCOVERED = "projection-footprint-uncovered", ARCHITECTURE_DOCS_PROJECTION_PROVENANCE_SCHEMA_VERSION = "archcontext.architecture-docs-projection-provenance/v3", ARCHITECTURE_DOCS_LAYOUT_VERSION_V1 = "archcontext.docs-layout/v1", ARCHITECTURE_SEMANTIC_STATE_SCHEMA_VERSION = "archcontext.architecture-semantic-state/v1", ARCHITECTURE_DOCS_PROJECTION_PROFILES, SHA256, TARGET_TYPES, RUNTIME_TOPOLOGY_TARGET_TYPES, SCOPE_KINDS, OWNERSHIPS, FORMATS;
3769
3880
  var init_projection_manifest = __esm(() => {
3770
3881
  init_projection();
3771
3882
  init_schema();
@@ -3784,8 +3895,11 @@ var init_projection_manifest = __esm(() => {
3784
3895
  "diagram-mermaid": true,
3785
3896
  "diagram-structurizr": true,
3786
3897
  "diagram-likec4": true,
3898
+ "diagram-runtime-topology": true,
3899
+ "runtime-topology-summary": true,
3787
3900
  "agent-context": true
3788
3901
  };
3902
+ RUNTIME_TOPOLOGY_TARGET_TYPES = ["diagram-runtime-topology", "runtime-topology-summary"];
3789
3903
  SCOPE_KINDS = { repository: true, entity: true, relation: true, decision: true, diagram: true, changelog: true };
3790
3904
  OWNERSHIPS = { generated: true, mixed: true };
3791
3905
  FORMATS = { markdown: true, mermaid: true, "structurizr-json": true, likec4: true };
@@ -3876,7 +3990,7 @@ function compareVersionTriples(left, right) {
3876
3990
  }
3877
3991
  return 0;
3878
3992
  }
3879
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.1", 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";
3993
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.3", 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
3994
 
3881
3995
  // packages/contracts/src/refactor.ts
3882
3996
  function refactorNamespaceIssues(value, label = "product.id") {
@@ -4981,6 +5095,7 @@ var init_src = __esm(() => {
4981
5095
  init_projection_writer();
4982
5096
  init_projection_manifest();
4983
5097
  init_refactor();
5098
+ init_runtime_topology();
4984
5099
  init_schema();
4985
5100
  init_failure();
4986
5101
  init_validator();
@@ -17694,6 +17809,16 @@ var FIXED_TARGETS = [
17694
17809
  layoutTarget("projection_target.diagram.structurizr", "diagram-structurizr", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.structurizr.json", "generated", "structurizr-json"),
17695
17810
  layoutTarget("projection_target.diagram.likec4", "diagram-likec4", { kind: "diagram", id: "architecture" }, "docs/architecture/diagrams/architecture.likec4", "generated", "likec4")
17696
17811
  ];
17812
+ var RUNTIME_TOPOLOGY_DIAGRAM_PATH = "docs/architecture/diagrams/runtime-topology.mmd";
17813
+ var RUNTIME_TOPOLOGY_SUMMARY_PATH = "docs/architecture/runtime-topology.md";
17814
+ var RUNTIME_TOPOLOGY_SCOPE_ID = "runtime-topology";
17815
+ var RUNTIME_TOPOLOGY_TARGETS = [
17816
+ layoutTarget("projection_target.diagram.runtime-topology", "diagram-runtime-topology", { kind: "diagram", id: RUNTIME_TOPOLOGY_SCOPE_ID }, RUNTIME_TOPOLOGY_DIAGRAM_PATH, "generated", "mermaid"),
17817
+ layoutTarget("projection_target.runtime-topology.summary", "runtime-topology-summary", { kind: "diagram", id: RUNTIME_TOPOLOGY_SCOPE_ID }, RUNTIME_TOPOLOGY_SUMMARY_PATH, "generated", "markdown")
17818
+ ];
17819
+ function hasRuntimeTopologyNodes(nodes) {
17820
+ return nodes.some((node) => isRuntimeTopologyNodeKind(node.kind));
17821
+ }
17697
17822
  function parseRepoHarnessNodeProfile(node) {
17698
17823
  const match = /^capability\.([a-z0-9]+(?:-[a-z0-9]+)*)\.([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(node.id);
17699
17824
  if (!match || node.kind !== "capability") {
@@ -17721,7 +17846,7 @@ function parseRepoHarnessNodeProfile(node) {
17721
17846
  };
17722
17847
  }
17723
17848
  function entitySummaryTargetNodes(nodes, profile) {
17724
- return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") : [...nodes];
17849
+ return profile === REPO_HARNESS_PROJECTION_PROFILE ? nodes.filter((node) => node.kind === "capability") : nodes.filter((node) => !isRuntimeTopologyNodeKind(node.kind));
17725
17850
  }
17726
17851
  function resolveArchitectureDocumentationLayout(input) {
17727
17852
  const profile = input.profile ?? "default";
@@ -17731,7 +17856,8 @@ function resolveArchitectureDocumentationLayout(input) {
17731
17856
  return layoutTarget(`projection_target.entity.${stableId(node.id)}`, "entity-summary", { kind: "entity", id: node.id, entityKind: node.kind }, path, "mixed", "markdown");
17732
17857
  });
17733
17858
  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 targets = [FIXED_TARGETS[0], ...entityTargets, ...relationTargets, ...FIXED_TARGETS.slice(1)].map((target) => ({ ...target, path: requiredPath(target.path, target.targetId) }));
17859
+ const topologyTargets = hasRuntimeTopologyNodes(input.nodes) ? RUNTIME_TOPOLOGY_TARGETS : [];
17860
+ const targets = [FIXED_TARGETS[0], ...entityTargets, ...relationTargets, ...FIXED_TARGETS.slice(1), ...topologyTargets].map((target) => ({ ...target, path: requiredPath(target.path, target.targetId) }));
17735
17861
  assertUniqueTargets(targets);
17736
17862
  return {
17737
17863
  profile,
@@ -19369,6 +19495,7 @@ function renderArchitectureDocumentationProjection(input) {
19369
19495
  const rendered = targetDrafts.map((draft) => {
19370
19496
  const existing = existingByPath.get(draft.path);
19371
19497
  const nodeId = draft.type === "entity-summary" ? draft.scope.id : undefined;
19498
+ const topology = isRuntimeTopologyTargetType(draft.type);
19372
19499
  const generatedBody = renderTargetGeneratedBody(draft, model, {
19373
19500
  scaleSignalsByNodeId,
19374
19501
  scaleByNodeId,
@@ -19392,6 +19519,10 @@ function renderArchitectureDocumentationProjection(input) {
19392
19519
  if (scale === undefined)
19393
19520
  throw new Error(`architecture-docs-projection-scale-signal-missing: ${nodeId}`);
19394
19521
  }
19522
+ } else if (topology) {
19523
+ const owned = Object.keys(sourceStamps).filter((stamped) => footprintOwners.get(stamped) === RUNTIME_TOPOLOGY_SCOPE_ID);
19524
+ if (owned.length > 0)
19525
+ targetStamps = Object.fromEntries(owned.map((stamped) => [stamped, sourceStamps[stamped]]));
19395
19526
  }
19396
19527
  const generatedBodyDigest = digestJson({ targetId: draft.targetId, body: generatedBody });
19397
19528
  const target = projectionTarget({
@@ -19757,6 +19888,10 @@ function architectureProjectionFootprintOwners(model, profile = "default") {
19757
19888
  for (const node of [...model.nodes].sort((left, right) => compareStrings3(left.id, right.id))) {
19758
19889
  if ((nativeNodeSource(node)?.include ?? []).length === 0)
19759
19890
  continue;
19891
+ if (isRuntimeTopologyNodeKind(node.kind)) {
19892
+ owners.set(node.id, RUNTIME_TOPOLOGY_SCOPE_ID);
19893
+ continue;
19894
+ }
19760
19895
  const seen = new Set;
19761
19896
  let current = node;
19762
19897
  while (current && !targetNodeIds.has(current.id) && !seen.has(current.id)) {
@@ -20103,12 +20238,52 @@ function normalizeNativeModel(model) {
20103
20238
  flows: [...model.flows ?? []].sort((a, b) => a.id.localeCompare(b.id))
20104
20239
  };
20105
20240
  }
20241
+ var ARCHITECTURE_NODE_LAYER_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
20242
+ function nativeNodeLayer(node) {
20243
+ const layer = node.layer;
20244
+ if (layer === undefined)
20245
+ return;
20246
+ if (typeof layer !== "string" || !ARCHITECTURE_NODE_LAYER_PATTERN.test(layer)) {
20247
+ throw new Error(`architecture-node-layer-invalid: ${node.id} (${JSON.stringify(layer)})`);
20248
+ }
20249
+ return layer;
20250
+ }
20251
+ function architectureLayerGroups(model) {
20252
+ const nodesByLayer = new Map;
20253
+ const ungroupedNodes = [];
20254
+ const layerByNodeId = new Map;
20255
+ for (const node of [...model.nodes].sort((a, b) => a.id.localeCompare(b.id))) {
20256
+ const layer = nativeNodeLayer(node);
20257
+ if (layer === undefined) {
20258
+ ungroupedNodes.push(node);
20259
+ continue;
20260
+ }
20261
+ const members = nodesByLayer.get(layer);
20262
+ if (members)
20263
+ members.push(node);
20264
+ else
20265
+ nodesByLayer.set(layer, [node]);
20266
+ layerByNodeId.set(node.id, layer);
20267
+ }
20268
+ const layers = [...nodesByLayer.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([layer, nodes]) => ({ layer, nodes }));
20269
+ return { layers, ungroupedNodes, layerByNodeId };
20270
+ }
20271
+ function mermaidLayerId(layer) {
20272
+ return `layer__${layer.replace(/-/g, "_")}`;
20273
+ }
20106
20274
  function exportMermaidModel(model) {
20107
20275
  const normalized = normalizeNativeModel(model);
20276
+ const grouping = architectureLayerGroups(normalized);
20277
+ const nodeLine = (indent, node) => `${indent}${mermaidId(node.id)}["${escapeMermaid(node.name)}"]`;
20108
20278
  const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart LR"];
20109
- for (const node of normalized.nodes) {
20110
- lines.push(` ${mermaidId(node.id)}["${escapeMermaid(node.name)}"]`);
20279
+ for (const group of grouping.layers) {
20280
+ lines.push(` subgraph ${mermaidLayerId(group.layer)}["${escapeMermaid(group.layer)}"]`);
20281
+ for (const node of group.nodes)
20282
+ lines.push(nodeLine(" ", node));
20283
+ lines.push(" end");
20111
20284
  }
20285
+ for (const node of grouping.ungroupedNodes)
20286
+ lines.push(nodeLine(" ", node));
20112
20287
  for (const relation of normalized.relations) {
20113
20288
  lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
20114
20289
  }
@@ -20125,22 +20300,15 @@ function exportDocumentationLikeC4Model(model) {
20125
20300
  const normalized = normalizeNativeModel(model);
20126
20301
  const lines = [
20127
20302
  "// Generated by ArchContext. Native model remains source of truth.",
20128
- "specification {",
20129
- " element capability",
20130
- " element module",
20131
- " element component",
20132
- " element interface",
20133
- " element datastore",
20134
- " element external_system",
20135
- "}",
20303
+ ...likeC4SpecificationLines(normalized.nodes),
20136
20304
  "",
20137
20305
  "model {"
20138
20306
  ];
20139
20307
  for (const node of normalized.nodes)
20140
- lines.push(` ${mermaidId(node.id)} = ${documentationLikeC4Kind(node.kind)} "${escapeDsl(node.name)}"`);
20308
+ lines.push(` ${mermaidId(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl(node.name)}"`);
20141
20309
  for (const relation of normalized.relations)
20142
20310
  lines.push(` ${mermaidId(relation.source)} -> ${mermaidId(relation.target)} "${escapeDsl(relation.kind)}"`);
20143
- lines.push("}", "", "views {", " view index {", " include *", " autoLayout TopBottom", " }", "}");
20311
+ lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), mermaidId));
20144
20312
  const content = `${lines.join(`
20145
20313
  `)}
20146
20314
  `;
@@ -20150,18 +20318,73 @@ function exportDocumentationLikeC4Model(model) {
20150
20318
  files: [{ path: "docs/architecture/diagrams/architecture.likec4", content }]
20151
20319
  };
20152
20320
  }
20321
+ function likeC4SpecificationLines(nodes) {
20322
+ const topologyKinds = RUNTIME_TOPOLOGY_NODE_KINDS.filter((kind) => nodes.some((node) => node.kind === kind));
20323
+ return [
20324
+ "specification {",
20325
+ " element capability",
20326
+ " element module",
20327
+ " element component",
20328
+ " element interface",
20329
+ " element datastore",
20330
+ " element external_system",
20331
+ ...topologyKinds.map((kind) => ` element ${kind}`),
20332
+ "}"
20333
+ ];
20334
+ }
20335
+ function likeC4ElementKind(kind) {
20336
+ if (kind === "external-system")
20337
+ return "external_system";
20338
+ if (kind === "datastore")
20339
+ return "datastore";
20340
+ if (["capability", "module", "component", "interface"].includes(kind))
20341
+ return kind;
20342
+ if (isRuntimeTopologyNodeKind(kind))
20343
+ return kind;
20344
+ return "component";
20345
+ }
20346
+ function architectureStructurizrElementType(kind) {
20347
+ if (kind === "capability")
20348
+ return "Software System";
20349
+ if (kind === "module")
20350
+ return "Container";
20351
+ if (kind === "datastore")
20352
+ return "Database";
20353
+ if (kind === "external-system")
20354
+ return "External System";
20355
+ if (kind === "namespace")
20356
+ return "Deployment Node";
20357
+ if (kind === "process")
20358
+ return "Container Instance";
20359
+ if (kind === "mount")
20360
+ return "Infrastructure Node";
20361
+ return "Component";
20362
+ }
20363
+ function likeC4IndexViewLines(grouping, elementId) {
20364
+ const groupLines = grouping.layers.flatMap((group) => [
20365
+ ` group "${escapeDsl(group.layer)}" {`,
20366
+ ...group.nodes.map((node) => ` include ${elementId(node.id)}`),
20367
+ " }"
20368
+ ]);
20369
+ return ["views {", " view index {", ...groupLines, " include *", " autoLayout TopBottom", " }", "}"];
20370
+ }
20153
20371
  function exportDocumentationStructurizrWorkspace(model) {
20154
20372
  const normalized = normalizeNativeModel(model);
20373
+ const { layerByNodeId } = architectureLayerGroups(normalized);
20155
20374
  const workspace = {
20156
20375
  schemaVersion: "archcontext.structurizr-export/v1",
20157
20376
  name: "ArchContext Architecture",
20158
20377
  model: {
20159
- elements: normalized.nodes.map((node) => ({
20160
- id: node.id,
20161
- name: node.name,
20162
- type: documentationStructurizrElementType(node.kind),
20163
- tags: [node.kind]
20164
- })),
20378
+ elements: normalized.nodes.map((node) => {
20379
+ const layer = layerByNodeId.get(node.id);
20380
+ return {
20381
+ id: node.id,
20382
+ name: node.name,
20383
+ type: architectureStructurizrElementType(node.kind),
20384
+ tags: [node.kind],
20385
+ ...layer === undefined ? {} : { group: layer }
20386
+ };
20387
+ }),
20165
20388
  relationships: normalized.relations.map((relation) => ({
20166
20389
  id: relation.id,
20167
20390
  sourceId: relation.source,
@@ -20184,6 +20407,214 @@ function exportDocumentationStructurizrWorkspace(model) {
20184
20407
  files: [{ path: "docs/architecture/diagrams/architecture.structurizr.json", content }]
20185
20408
  };
20186
20409
  }
20410
+ var RUNTIME_TOPOLOGY_NAMESPACE_TYPES = ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"];
20411
+ var RUNTIME_TOPOLOGY_MOUNT_TARGET_PATTERN = /^\/([^/\u0000]+(\/[^/\u0000]+)*)?$/;
20412
+ function runtimeTopologyView(model) {
20413
+ const issues = runtimeTopologyModelIssues(model);
20414
+ if (issues.length > 0) {
20415
+ throw new Error(`runtime-topology-model-invalid: ${issues.map((issue) => `${issue.entityId} ${issue.field} ${issue.invariant}`).join("; ")}`);
20416
+ }
20417
+ const byId = (left, right) => compareStrings3(left.id, right.id);
20418
+ const nodes = [...model.nodes].sort(byId);
20419
+ const relations = [...model.relations].sort(byId);
20420
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
20421
+ const namespaces = nodes.filter((node) => node.kind === "namespace").map((node) => ({
20422
+ node,
20423
+ type: runtimeTopologyNamespaceType(node),
20424
+ ...typeof node.parent === "string" ? { parentId: node.parent } : {},
20425
+ namespaceIds: [],
20426
+ processIds: []
20427
+ }));
20428
+ const namespaceById = new Map(namespaces.map((namespace) => [namespace.node.id, namespace]));
20429
+ for (const namespace of namespaces) {
20430
+ if (namespace.parentId !== undefined)
20431
+ namespaceById.get(namespace.parentId).namespaceIds.push(namespace.node.id);
20432
+ }
20433
+ const namespaceByProcessId = new Map(relations.filter((relation) => relation.kind === "runs-in").map((relation) => [relation.source, relation.target]));
20434
+ const processes = nodes.filter((node) => node.kind === "process").map((node) => {
20435
+ const namespaceId = namespaceByProcessId.get(node.id);
20436
+ return namespaceId === undefined ? { node } : { node, namespaceId };
20437
+ });
20438
+ for (const topologyProcess of processes) {
20439
+ if (topologyProcess.namespaceId !== undefined)
20440
+ namespaceById.get(topologyProcess.namespaceId).processIds.push(topologyProcess.node.id);
20441
+ }
20442
+ const mounts = nodes.filter((node) => node.kind === "mount").map(runtimeTopologyMount);
20443
+ const mounterByMountId = new Map(relations.filter((relation) => relation.kind === "mounts").map((relation) => [relation.target, relation.source]));
20444
+ const stacksByMounterId = new Map;
20445
+ for (const mount of mounts) {
20446
+ const mounterId = mounterByMountId.get(mount.node.id);
20447
+ if (mounterId === undefined)
20448
+ continue;
20449
+ stacksByMounterId.set(mounterId, [...stacksByMounterId.get(mounterId) ?? [], mount]);
20450
+ }
20451
+ const mountStacks = [...stacksByMounterId.entries()].sort(([left], [right]) => compareStrings3(left, right)).map(([mounterId, stack]) => ({
20452
+ mounterId,
20453
+ mounts: stack.sort((left, right) => left.order - right.order || compareStrings3(left.node.id, right.node.id))
20454
+ }));
20455
+ const deployments = relations.filter((relation) => relation.kind === "deployed-on" && isRuntimeTopologyNodeKind(nodesById.get(relation.target)?.kind));
20456
+ const linkedNodeIds = new Set;
20457
+ for (const relation of deployments) {
20458
+ const source = nodesById.get(relation.source);
20459
+ if (!source)
20460
+ throw new Error(`runtime-topology-relation-endpoint-missing: ${relation.id} (${relation.source})`);
20461
+ if (!isRuntimeTopologyNodeKind(source.kind))
20462
+ linkedNodeIds.add(source.id);
20463
+ }
20464
+ return {
20465
+ nodesById,
20466
+ namespaces,
20467
+ rootNamespaceIds: namespaces.filter((namespace) => namespace.parentId === undefined).map((namespace) => namespace.node.id),
20468
+ processes,
20469
+ mountStacks,
20470
+ unmountedMounts: mounts.filter((mount) => !mounterByMountId.has(mount.node.id)),
20471
+ spawns: relations.filter((relation) => relation.kind === "spawns"),
20472
+ mounts: relations.filter((relation) => relation.kind === "mounts"),
20473
+ deployments,
20474
+ linkedNodes: nodes.filter((node) => linkedNodeIds.has(node.id))
20475
+ };
20476
+ }
20477
+ function runtimeTopologyNamespaceType(node) {
20478
+ const value = node.namespace;
20479
+ const type = value !== null && typeof value === "object" && !Array.isArray(value) ? value.type : undefined;
20480
+ if (typeof type !== "string" || !RUNTIME_TOPOLOGY_NAMESPACE_TYPES.includes(type)) {
20481
+ throw new Error(`runtime-topology-namespace-invalid: ${node.id}`);
20482
+ }
20483
+ return type;
20484
+ }
20485
+ function runtimeTopologyMount(node) {
20486
+ const value = node.mount;
20487
+ if (value === null || typeof value !== "object" || Array.isArray(value))
20488
+ throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
20489
+ const { source, target, mode, order } = value;
20490
+ 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) {
20491
+ throw new Error(`runtime-topology-mount-invalid: ${node.id}`);
20492
+ }
20493
+ return { node, ...source === undefined ? {} : { source }, target, mode, order };
20494
+ }
20495
+ function runtimeTopologyMountStackId(mounterId) {
20496
+ return `mounts__${mermaidId(mounterId)}`;
20497
+ }
20498
+ function runtimeTopologyMountLabel(mount) {
20499
+ return `${mount.node.name} (${mount.target}, ${mount.mode}, order ${mount.order})`;
20500
+ }
20501
+ function exportRuntimeTopologyMermaid(model) {
20502
+ const view = runtimeTopologyView(model);
20503
+ const namespaceById = new Map(view.namespaces.map((namespace) => [namespace.node.id, namespace]));
20504
+ const nodeLine = (indent, node, label = node.name) => `${indent}${mermaidId(node.id)}["${escapeMermaid(label)}"]`;
20505
+ const lines = ["%% Generated by ArchContext. Native model remains source of truth.", "flowchart TB"];
20506
+ const pushNamespace = (namespaceId, depth) => {
20507
+ const namespace = namespaceById.get(namespaceId);
20508
+ const indent = " ".repeat(depth);
20509
+ lines.push(`${indent}subgraph ${mermaidId(namespaceId)}["${escapeMermaid(`${namespace.node.name} (${namespace.type})`)}"]`);
20510
+ for (const processId of namespace.processIds)
20511
+ lines.push(nodeLine(`${indent} `, view.nodesById.get(processId)));
20512
+ for (const childId of namespace.namespaceIds)
20513
+ pushNamespace(childId, depth + 1);
20514
+ lines.push(`${indent}end`);
20515
+ };
20516
+ for (const namespaceId of view.rootNamespaceIds)
20517
+ pushNamespace(namespaceId, 1);
20518
+ for (const topologyProcess of view.processes)
20519
+ if (topologyProcess.namespaceId === undefined)
20520
+ lines.push(nodeLine(" ", topologyProcess.node));
20521
+ for (const stack of view.mountStacks) {
20522
+ const mounter = view.nodesById.get(stack.mounterId);
20523
+ lines.push(` subgraph ${runtimeTopologyMountStackId(stack.mounterId)}["${escapeMermaid(`Mounts: ${mounter.name}`)}"]`, " direction TB");
20524
+ for (const mount of stack.mounts)
20525
+ lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
20526
+ for (let index = 1;index < stack.mounts.length; index += 1) {
20527
+ lines.push(` ${mermaidId(stack.mounts[index - 1].node.id)} ~~~ ${mermaidId(stack.mounts[index].node.id)}`);
20528
+ }
20529
+ lines.push(" end");
20530
+ }
20531
+ for (const mount of view.unmountedMounts)
20532
+ lines.push(nodeLine(" ", mount.node, runtimeTopologyMountLabel(mount)));
20533
+ for (const node of view.linkedNodes)
20534
+ lines.push(nodeLine(" ", node));
20535
+ for (const relation of [...view.spawns, ...view.mounts, ...view.deployments].sort((left, right) => compareStrings3(left.id, right.id))) {
20536
+ lines.push(` ${mermaidId(relation.source)} -->|"${escapeMermaid(relation.kind)}"| ${mermaidId(relation.target)}`);
20537
+ }
20538
+ const content = `${lines.join(`
20539
+ `)}
20540
+ `;
20541
+ return {
20542
+ format: "mermaid",
20543
+ digest: digestJson({ format: "mermaid", content }),
20544
+ files: [{ path: RUNTIME_TOPOLOGY_DIAGRAM_PATH, content }]
20545
+ };
20546
+ }
20547
+ function renderRuntimeTopologySummary(model, summaryPath) {
20548
+ const view = runtimeTopologyView(model);
20549
+ const cell = (value) => value.replaceAll("|", "\\|");
20550
+ const named = (id) => {
20551
+ const node = view.nodesById.get(id);
20552
+ return `\`${id}\`${node ? ` ${cell(node.name)}` : ""}`;
20553
+ };
20554
+ const list = (ids) => ids.length === 0 ? "none" : ids.map((id) => `\`${id}\``).join(", ");
20555
+ const lines = [
20556
+ "# Runtime Topology",
20557
+ "",
20558
+ "The architecture model declares this topology. No code facts verify it: archctx never reads the configuration it describes. A change to a file a node lists in `source.include` marks this page and its diagram stale.",
20559
+ "",
20560
+ `- Diagram: [Mermaid](${posix.relative(posix.dirname(summaryPath), RUNTIME_TOPOLOGY_DIAGRAM_PATH)})`,
20561
+ "",
20562
+ "## Namespaces",
20563
+ ""
20564
+ ];
20565
+ if (view.namespaces.length === 0)
20566
+ lines.push("- No namespaces recorded.");
20567
+ else {
20568
+ lines.push("| Namespace | Type | Parent | Namespaces | Processes |", "| --- | --- | --- | --- | --- |");
20569
+ for (const namespace of view.namespaces) {
20570
+ lines.push(`| ${named(namespace.node.id)} | ${namespace.type} | ${namespace.parentId === undefined ? "none" : `\`${namespace.parentId}\``} | ${list(namespace.namespaceIds)} | ${list(namespace.processIds)} |`);
20571
+ }
20572
+ }
20573
+ lines.push("", "## Processes", "");
20574
+ if (view.processes.length === 0)
20575
+ lines.push("- No processes recorded.");
20576
+ else {
20577
+ lines.push("| Process | Namespace | Spawns | Deployed here |", "| --- | --- | --- | --- |");
20578
+ for (const topologyProcess of view.processes) {
20579
+ const id = topologyProcess.node.id;
20580
+ const spawned = view.spawns.filter((relation) => relation.source === id).map((relation) => relation.target);
20581
+ const deployed = view.deployments.filter((relation) => relation.target === id).map((relation) => relation.source);
20582
+ lines.push(`| ${named(id)} | ${topologyProcess.namespaceId === undefined ? "none" : `\`${topologyProcess.namespaceId}\``} | ${list([...new Set(spawned)].sort(compareStrings3))} | ${list([...new Set(deployed)].sort(compareStrings3))} |`);
20583
+ }
20584
+ }
20585
+ lines.push("", "## Mount Stacks", "");
20586
+ const mountRows = (stack) => [
20587
+ "| Order | Mount | Source | Target | Mode |",
20588
+ "| --- | --- | --- | --- | --- |",
20589
+ ...stack.map((mount) => `| ${mount.order} | ${named(mount.node.id)} | ${mount.source === undefined ? "none" : `\`${cell(mount.source)}\``} | \`${cell(mount.target)}\` | ${mount.mode} |`)
20590
+ ];
20591
+ if (view.mountStacks.length === 0 && view.unmountedMounts.length === 0)
20592
+ lines.push("- No mounts recorded.");
20593
+ for (const [index, stack] of view.mountStacks.entries()) {
20594
+ if (index > 0)
20595
+ lines.push("");
20596
+ lines.push(`### ${named(stack.mounterId)}`, "", ...mountRows(stack.mounts));
20597
+ }
20598
+ if (view.unmountedMounts.length > 0) {
20599
+ if (view.mountStacks.length > 0)
20600
+ lines.push("");
20601
+ lines.push("### Not mounted", "", ...mountRows(view.unmountedMounts));
20602
+ }
20603
+ const otherDeployments = view.deployments.filter((relation) => view.nodesById.get(relation.target)?.kind !== "process");
20604
+ if (otherDeployments.length > 0) {
20605
+ lines.push("", "## Other Deployments", "");
20606
+ for (const relation of otherDeployments)
20607
+ lines.push(`- \`${relation.source}\` -> \`${relation.target}\` — ${relation.intent}`);
20608
+ }
20609
+ lines.push("", "## Source Anchors", "", "| Node | Source |", "| --- | --- |");
20610
+ for (const node of [...view.nodesById.values()].filter((entry) => isRuntimeTopologyNodeKind(entry.kind)).sort((left, right) => compareStrings3(left.id, right.id))) {
20611
+ const include = nativeNodeSource(node)?.include ?? [];
20612
+ lines.push(`| ${named(node.id)} | ${include.length === 0 ? "model-authored, no source anchor" : include.map((pattern) => `\`${cell(pattern)}\``).join(", ")} |`);
20613
+ }
20614
+ return `${lines.join(`
20615
+ `)}
20616
+ `;
20617
+ }
20187
20618
  function loadNativeModelFromArchContext(root) {
20188
20619
  return nativeModelFromYamlObjects({
20189
20620
  nodes: readYamlObjects(resolve6(root, ".archcontext/model/nodes")),
@@ -20202,6 +20633,7 @@ function nativeModelFromYamlObjects(input) {
20202
20633
  throw new Error(`architecture-node-schema-version-unsupported: ${node.id || "unknown"} (${String(node.schemaVersion)})`);
20203
20634
  }
20204
20635
  assertArchitectureNodeSourceV2(node);
20636
+ nativeNodeLayer(node);
20205
20637
  }
20206
20638
  const flows = input.flows;
20207
20639
  for (const flow of flows)
@@ -20386,8 +20818,15 @@ function renderTargetGeneratedBody(target, model, input) {
20386
20818
  return exportDocumentationStructurizrWorkspace(model).files[0].content;
20387
20819
  if (target.type === "diagram-likec4")
20388
20820
  return exportDocumentationLikeC4Model(model).files[0].content;
20821
+ if (target.type === "diagram-runtime-topology")
20822
+ return exportRuntimeTopologyMermaid(model).files[0].content;
20823
+ if (target.type === "runtime-topology-summary")
20824
+ return renderRuntimeTopologySummary(model, target.path);
20389
20825
  return "";
20390
20826
  }
20827
+ function architectureIndexLink(path) {
20828
+ return path.replace(/^docs\/architecture\//, "");
20829
+ }
20391
20830
  function renderArchitectureIndex(model, layout2) {
20392
20831
  const lines = [
20393
20832
  "# Architecture Index",
@@ -20412,6 +20851,7 @@ function renderArchitectureIndex(model, layout2) {
20412
20851
  "- [Mermaid](diagrams/architecture.mmd)",
20413
20852
  "- [Structurizr JSON](diagrams/architecture.structurizr.json)",
20414
20853
  "- [LikeC4](diagrams/architecture.likec4)",
20854
+ ...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
20855
  "- [Decision index](decisions/index.md)",
20416
20856
  "- [Architecture changelog](changelog.md)"
20417
20857
  ];
@@ -20865,26 +21305,6 @@ function isManagedArchitectureDocumentationMarkdownPath(path) {
20865
21305
  function pathSegment2(id) {
20866
21306
  return stableId(id).replace(/\./g, "-");
20867
21307
  }
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
21308
  function escapeDsl(value) {
20889
21309
  return value.replace(/"/g, "'");
20890
21310
  }
@@ -21548,7 +21968,7 @@ function sameOwners(left, right) {
21548
21968
 
21549
21969
  // packages/core/module-statistics/src/index.ts
21550
21970
  function evaluateDependencyConstraints(input) {
21551
- const constraints = [...input.constraints].sort((left, right) => compare(left.id, right.id));
21971
+ const constraints = [...input.constraints].sort((left, right) => compare2(left.id, right.id));
21552
21972
  const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded ?? false);
21553
21973
  if (constraints.length === 0) {
21554
21974
  return {
@@ -21605,7 +22025,7 @@ function evaluateDependencyConstraints(input) {
21605
22025
  reasonCodes.add("unresolved-import");
21606
22026
  if (input.workspacePackagesResolved === false)
21607
22027
  reasonCodes.add("workspace-resolution-failed");
21608
- const sortedViolations = [...violations.values()].sort((left, right) => compare(left.constraintId, right.constraintId) || compare(left.fromPath, right.fromPath) || compare(left.toPath, right.toPath));
22028
+ const sortedViolations = [...violations.values()].sort((left, right) => compare2(left.constraintId, right.constraintId) || compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
21609
22029
  return {
21610
22030
  schemaVersion: "archcontext.dependency-constraint-evaluation/v1",
21611
22031
  status: sortedViolations.length > 0 ? "violated" : reasonCodes.size > 0 ? "undetermined" : "pass",
@@ -21682,7 +22102,7 @@ function measureCapabilityDependencies(input) {
21682
22102
  const cellKey = JSON.stringify([from.capability, to.capability]);
21683
22103
  let cell = cells.get(cellKey);
21684
22104
  if (cell === undefined) {
21685
- const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(compare);
22105
+ const relationIds = [...new Set(declaringRelations.get(cellKey) ?? [])].sort(compare2);
21686
22106
  cell = { from: from.capability, to: to.capability, imports: 0, calls: null, declared: from.capability === to.capability || relationIds.length > 0, relationIds };
21687
22107
  cells.set(cellKey, cell);
21688
22108
  }
@@ -21690,14 +22110,14 @@ function measureCapabilityDependencies(input) {
21690
22110
  attributedEdges.push({ fromPath: edge.from, toPath: edge.to, fromCapability: from.capability, toCapability: to.capability });
21691
22111
  }
21692
22112
  const edgeLimit = input.edgeLimit ?? CAPABILITY_DEPENDENCIES_EDGE_LIMIT;
21693
- attributedEdges.sort((left, right) => compare(left.fromPath, right.fromPath) || compare(left.toPath, right.toPath));
22113
+ attributedEdges.sort((left, right) => compare2(left.fromPath, right.fromPath) || compare2(left.toPath, right.toPath));
21694
22114
  const listed = attributedEdges.slice(0, edgeLimit);
21695
22115
  return {
21696
22116
  schemaVersion: CAPABILITY_DEPENDENCIES_SCHEMA_VERSION,
21697
22117
  ownershipRule: "parent-chain",
21698
22118
  filter: { from: input.from ?? null, into: input.into ?? null },
21699
- capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(compare),
21700
- matrix: [...cells.values()].sort((left, right) => compare(left.from, right.from) || compare(left.to, right.to)),
22119
+ capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(compare2),
22120
+ matrix: [...cells.values()].sort((left, right) => compare2(left.from, right.from) || compare2(left.to, right.to)),
21701
22121
  imports: {
21702
22122
  coverage: certification.coverage,
21703
22123
  reasonCodes: [...certification.reasonCodes].sort(),
@@ -21733,7 +22153,7 @@ function isRepositoryLocalSpecifier(specifier, workspacePackages) {
21733
22153
  return true;
21734
22154
  return workspacePackages.some((workspacePackage) => specifier === workspacePackage.name || specifier.startsWith(`${workspacePackage.name}/`));
21735
22155
  }
21736
- function compare(left, right) {
22156
+ function compare2(left, right) {
21737
22157
  return left < right ? -1 : left > right ? 1 : 0;
21738
22158
  }
21739
22159
  function measureModuleStatistics(input) {
@@ -21818,7 +22238,7 @@ function modelDigest(model, constraints) {
21818
22238
  nodes: [...model.nodes].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
21819
22239
  relations: [...model.relations].sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0),
21820
22240
  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) => compare(left.id, right.id)) }
22241
+ ...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare2(left.id, right.id)) }
21822
22242
  });
21823
22243
  }
21824
22244
  function countViolationsByNode(evaluation) {
@@ -22715,6 +23135,29 @@ function listGitVisibleFiles(root, pathspec) {
22715
23135
  }
22716
23136
  }).sort((left, right) => left.localeCompare(right));
22717
23137
  }
23138
+ function ignoredRepositoryPaths(root, paths) {
23139
+ if (paths.length === 0)
23140
+ return [];
23141
+ const result = spawnSync2("git", ["check-ignore", "--verbose", "--no-index", "-z", "--stdin"], {
23142
+ cwd: root,
23143
+ encoding: "utf8",
23144
+ input: paths.map((path) => `${path}\x00`).join(""),
23145
+ stdio: ["pipe", "pipe", "pipe"]
23146
+ });
23147
+ if (result.status !== 0 && result.status !== 1) {
23148
+ const detail = result.error?.message ?? (result.stderr.trim() || `git check-ignore exited ${result.status}`);
23149
+ throw new GitVisibleFilesUnavailableError(root, detail);
23150
+ }
23151
+ const fields = result.stdout.split("\x00");
23152
+ const ignored = [];
23153
+ for (let index = 0;index + 3 < fields.length; index += 4) {
23154
+ const [source, line, pattern, path] = fields.slice(index, index + 4);
23155
+ if (pattern.startsWith("!"))
23156
+ continue;
23157
+ ignored.push({ path, source, line: Number(line), pattern });
23158
+ }
23159
+ return ignored;
23160
+ }
22718
23161
  function readHeadSha(root) {
22719
23162
  try {
22720
23163
  return execFileSync("git", ["rev-parse", "HEAD"], {
@@ -26083,7 +26526,7 @@ function buildObservations(snapshot, structure, model) {
26083
26526
  evidence: evidence.evidenceGap()
26084
26527
  });
26085
26528
  }
26086
- return drafts.map((draft, index) => ({ draft, order: draft.kind === "cycle" ? index : 0 })).sort((left, right) => compare2(left.draft.kind, right.draft.kind) || left.order - right.order || compare2(left.draft.subjectSelectorId, right.draft.subjectSelectorId)).map(({ draft }) => ({
26529
+ 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
26530
  kind: draft.kind,
26088
26531
  subjectSelectorId: draft.subjectSelectorId,
26089
26532
  affectedNodeIds: draft.affectedNodeIds,
@@ -26273,13 +26716,13 @@ function signalIdFor(draft) {
26273
26716
  }
26274
26717
  function modelDigest2(model, constraints) {
26275
26718
  return digestJson({
26276
- nodes: [...model.nodes].sort((left, right) => compare2(left.id, right.id)),
26277
- relations: [...model.relations].sort((left, right) => compare2(left.id, right.id)),
26278
- flows: [...model.flows ?? []].sort((left, right) => compare2(left.id, right.id)),
26279
- ...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare2(left.id, right.id)) }
26719
+ nodes: [...model.nodes].sort((left, right) => compare3(left.id, right.id)),
26720
+ relations: [...model.relations].sort((left, right) => compare3(left.id, right.id)),
26721
+ flows: [...model.flows ?? []].sort((left, right) => compare3(left.id, right.id)),
26722
+ ...constraints.length === 0 ? {} : { constraints: [...constraints].sort((left, right) => compare3(left.id, right.id)) }
26280
26723
  });
26281
26724
  }
26282
- function compare2(left, right) {
26725
+ function compare3(left, right) {
26283
26726
  return left < right ? -1 : left > right ? 1 : 0;
26284
26727
  }
26285
26728
  function isNumber(value) {
@@ -26301,17 +26744,49 @@ var architecture_node_schema_default = {
26301
26744
  type: "object",
26302
26745
  additionalProperties: false,
26303
26746
  required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
26747
+ allOf: [
26748
+ {
26749
+ if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
26750
+ then: { required: ["namespace"] },
26751
+ else: { not: { required: ["namespace"] } }
26752
+ },
26753
+ {
26754
+ if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
26755
+ then: { required: ["mount"] },
26756
+ else: { not: { required: ["mount"] } }
26757
+ }
26758
+ ],
26304
26759
  properties: {
26305
26760
  schemaVersion: { const: "archcontext.node/v2" },
26306
26761
  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
26762
  kind: {
26308
26763
  type: "string",
26309
- enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
26764
+ enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
26310
26765
  },
26311
26766
  name: { type: "string" },
26312
26767
  status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
26313
26768
  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-]*)+$" },
26769
+ layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
26314
26770
  summary: { type: "string" },
26771
+ namespace: {
26772
+ type: "object",
26773
+ additionalProperties: false,
26774
+ required: ["type"],
26775
+ properties: {
26776
+ type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
26777
+ }
26778
+ },
26779
+ mount: {
26780
+ type: "object",
26781
+ additionalProperties: false,
26782
+ required: ["target", "mode", "order"],
26783
+ properties: {
26784
+ source: { type: "string", minLength: 1 },
26785
+ target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
26786
+ mode: { type: "string", enum: ["ro", "rw"] },
26787
+ order: { type: "integer", minimum: 0 }
26788
+ }
26789
+ },
26315
26790
  responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
26316
26791
  source: {
26317
26792
  type: "object",
@@ -26400,7 +26875,7 @@ var architecture_relation_schema_default = {
26400
26875
  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
26876
  kind: {
26402
26877
  type: "string",
26403
- enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on"]
26878
+ enum: ["depends-on", "calls", "publishes", "subscribes", "reads", "writes", "implements", "deployed-on", "spawns", "runs-in", "mounts"]
26404
26879
  },
26405
26880
  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
26881
  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 +27583,22 @@ function createDefaultProjectionTargetManifest() {
27108
27583
  ownership: "generated",
27109
27584
  format: "likec4"
27110
27585
  },
27586
+ {
27587
+ id: "projection_rule.diagram.runtime-topology",
27588
+ targetType: "diagram-runtime-topology",
27589
+ scope: { kind: "diagram", id: "runtime-topology" },
27590
+ pathTemplate: "docs/architecture/diagrams/runtime-topology.mmd",
27591
+ ownership: "generated",
27592
+ format: "mermaid"
27593
+ },
27594
+ {
27595
+ id: "projection_rule.runtime-topology.summary",
27596
+ targetType: "runtime-topology-summary",
27597
+ scope: { kind: "diagram", id: "runtime-topology" },
27598
+ pathTemplate: "docs/architecture/runtime-topology.md",
27599
+ ownership: "generated",
27600
+ format: "markdown"
27601
+ },
27111
27602
  {
27112
27603
  id: "projection_rule.agent-context.claude",
27113
27604
  targetType: "agent-context",
@@ -27253,10 +27744,19 @@ class YamlModelStore {
27253
27744
  }
27254
27745
  }
27255
27746
  const files = listModelFiles(workspace.root);
27747
+ const topologyRecords = { nodes: [], relations: [] };
27748
+ const topologyRecordPaths = new Map;
27256
27749
  for (const file of files) {
27257
27750
  if (file.path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(file.path)) {
27258
27751
  try {
27259
27752
  const value = parseJsonOrStableYaml(file.body, file.path);
27753
+ for (const [directory, records] of [["nodes", topologyRecords.nodes], ["relations", topologyRecords.relations]]) {
27754
+ if (!file.path.startsWith(`.archcontext/model/${directory}/`) || !isJsonRecord2(value))
27755
+ continue;
27756
+ records.push(value);
27757
+ if (typeof value.id === "string")
27758
+ topologyRecordPaths.set(value.id, file.path);
27759
+ }
27260
27760
  if (file.path.startsWith(".archcontext/model/nodes/")) {
27261
27761
  const { issues } = validateJsonSchema(architecture_node_schema_default, value);
27262
27762
  for (const issue of issues) {
@@ -27277,6 +27777,9 @@ class YamlModelStore {
27277
27777
  errors.push(`${file.path}: expected archcontext.flow/v1, got ${file.schemaVersion || "missing"}`);
27278
27778
  }
27279
27779
  }
27780
+ for (const issue of runtimeTopologyModelIssues(topologyRecords)) {
27781
+ errors.push(`${topologyRecordPaths.get(issue.entityId) ?? issue.entityId}: ${issue.field}: ${issue.invariant}`);
27782
+ }
27280
27783
  const adr = validateAdrAppliesTo(files);
27281
27784
  const constraints = readDependencyConstraints(files);
27282
27785
  const reviewPolicy = readReviewPolicy(files);
@@ -27297,6 +27800,21 @@ class YamlModelStore {
27297
27800
  return { digest: digestJson(changeSet), summary: "ChangeSet preview generated" };
27298
27801
  }
27299
27802
  }
27803
+ var ARCHITECTURE_MODEL_COMMITTED_PATHS = Object.freeze([
27804
+ ".archcontext/manifest.yaml",
27805
+ ".archcontext/product.yaml",
27806
+ ".archcontext/model/nodes/",
27807
+ ".archcontext/model/relations/",
27808
+ ".archcontext/model/flows/",
27809
+ ".archcontext/decisions/",
27810
+ ".archcontext/policies/",
27811
+ ".archcontext/practices/",
27812
+ ".archcontext/projections/"
27813
+ ]);
27814
+ function architectureModelIgnoreProbePaths(root) {
27815
+ const directories = ARCHITECTURE_MODEL_COMMITTED_PATHS.filter((path) => path.endsWith("/")).map((path) => path.slice(0, -1));
27816
+ return [...new Set([...ARCHITECTURE_MODEL_COMMITTED_PATHS, ...collectArchContextFiles(root, directories)])];
27817
+ }
27300
27818
  function listModelFiles(root) {
27301
27819
  const paths = collectArchContextFiles(root, [
27302
27820
  ".archcontext/manifest.yaml",
@@ -27381,6 +27899,9 @@ function extractSchemaVersion(body, path) {
27381
27899
  return "archcontext.generated/v1";
27382
27900
  return "";
27383
27901
  }
27902
+ function isJsonRecord2(value) {
27903
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27904
+ }
27384
27905
  function withTrailingNewline(body) {
27385
27906
  return body.endsWith(`
27386
27907
  `) ? body : `${body}
@@ -31318,13 +31839,10 @@ class HttpContext7Transport {
31318
31839
  url.searchParams.set("libraryName", input.libraryName);
31319
31840
  url.searchParams.set("query", input.query);
31320
31841
  url.searchParams.set("fast", input.fast ? "true" : "false");
31321
- const response = await fetchWithTimeout(url, {
31842
+ const { payload } = await fetchJsonWithTimeout(url, {
31322
31843
  method: "GET",
31323
31844
  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");
31845
+ }, input.timeoutMs, "Context7 search");
31328
31846
  return parseSearchResponse(payload);
31329
31847
  }
31330
31848
  async getContext(input) {
@@ -31333,13 +31851,10 @@ class HttpContext7Transport {
31333
31851
  url.searchParams.set("query", input.query);
31334
31852
  url.searchParams.set("type", "json");
31335
31853
  url.searchParams.set("fast", "true");
31336
- const response = await fetchWithTimeout(url, {
31854
+ const { payload } = await fetchJsonWithTimeout(url, {
31337
31855
  method: "GET",
31338
31856
  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");
31857
+ }, input.timeoutMs, "Context7 context fetch");
31343
31858
  return parseDocumentationResponse(payload).slice(0, input.maxResults);
31344
31859
  }
31345
31860
  }
@@ -31437,11 +31952,14 @@ function normalizeSourceUri(value) {
31437
31952
  return;
31438
31953
  }
31439
31954
  }
31440
- async function fetchWithTimeout(input, init, timeoutMs) {
31955
+ async function fetchJsonWithTimeout(input, init, timeoutMs, label) {
31441
31956
  const controller = new AbortController;
31442
31957
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
31443
31958
  try {
31444
- return await withLocalEgress2("context7", () => fetch(input, { ...init, redirect: "error", signal: controller.signal }));
31959
+ const response = await withLocalEgress2("context7", () => fetch(input, { ...init, redirect: "error", signal: controller.signal }));
31960
+ if (!response.ok)
31961
+ throw httpStatusError(`${label} failed`, response);
31962
+ return await parseJsonResponse(response, label);
31445
31963
  } catch (error) {
31446
31964
  if (isAbortError(error)) {
31447
31965
  throw new Context7ProviderError("timeout", "Context7 provider request timed out", { retryable: true, cause: error });
@@ -32674,6 +33192,11 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
32674
33192
  try {
32675
33193
  const subcommand = input.action ?? "plan";
32676
33194
  const root = findRepositoryRoot(cwd);
33195
+ if (subcommand === "adopt") {
33196
+ const ignored = architectureModelIgnoredEnvelope("docs.adopt", root);
33197
+ if (ignored)
33198
+ return ignored;
33199
+ }
32677
33200
  let profile;
32678
33201
  let projection;
32679
33202
  let acceptedChange;
@@ -32789,6 +33312,23 @@ async function runArchitectureDocsProjectionCommand(input, cwd, daemon) {
32789
33312
  throw publicOperationFailure(error);
32790
33313
  }
32791
33314
  }
33315
+ function ignoredArchitectureModelPaths(root) {
33316
+ const ignored = ignoredRepositoryPaths(root, architectureModelIgnoreProbePaths(root));
33317
+ const directories = ignored.filter((entry) => entry.path.endsWith("/")).map((entry) => entry.path);
33318
+ return ignored.filter((entry) => entry.path.endsWith("/") || !directories.some((directory) => entry.path.startsWith(directory)));
33319
+ }
33320
+ function architectureModelIgnoredEnvelope(requestId, root) {
33321
+ const ignored = ignoredArchitectureModelPaths(root);
33322
+ if (ignored.length === 0)
33323
+ return;
33324
+ const paths = ignored.map((entry) => entry.path).slice(0, 8);
33325
+ return publicFailureEnvelope(requestId, "AC_PRECONDITION_FAILED", "architecture-model-ignored", {
33326
+ pathCount: ignored.length,
33327
+ paths,
33328
+ commands: [`git check-ignore --verbose --no-index -- ${paths.join(" ")}`],
33329
+ omittedPathCount: ignored.length - paths.length
33330
+ });
33331
+ }
32792
33332
  function committedArchitectureDocsProfile(root) {
32793
33333
  return existsSync9(resolve13(root, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)) ? loadArchitectureDocumentationProfile(root) : "default";
32794
33334
  }
@@ -33099,6 +33639,11 @@ async function runProjectionProtocolCommand(invocation, cwd, daemon) {
33099
33639
  if (replay)
33100
33640
  return replay;
33101
33641
  }
33642
+ if (request.mode === "adopt") {
33643
+ const ignored = architectureModelIgnoredEnvelope("projection.run", root);
33644
+ if (ignored)
33645
+ return ignored;
33646
+ }
33102
33647
  let observed;
33103
33648
  let acceptedChange = request.acceptedChange;
33104
33649
  let declinedUnprovableNodeIds;
@@ -34663,6 +35208,7 @@ init_projection_recovery();
34663
35208
  init_projection_writer();
34664
35209
  init_projection_manifest();
34665
35210
  init_refactor();
35211
+ init_runtime_topology();
34666
35212
  init_schema();
34667
35213
  init_failure();
34668
35214
  init_validator();
@@ -47072,7 +47618,7 @@ function upgradeExistingLocalStoreTarget2(paths, integrityCheck) {
47072
47618
  var RUNTIME_AGENT_JOB_STATUSES = ["queued", "running", "succeeded", "failed", "cancelled", "superseded", "expired"];
47073
47619
  var CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION = "archcontext.changeset-model-transition/v1";
47074
47620
  function assertChangeSetModelTransition(value, label = "changeset-model-transition-invalid") {
47075
- if (!isJsonRecord2(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)) {
47621
+ 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
47622
  throw new Error(label);
47077
47623
  }
47078
47624
  return { schemaVersion: CHANGESET_MODEL_TRANSITION_SCHEMA_VERSION, before: value.before, after: value.after };
@@ -51360,10 +51906,10 @@ function readChangeSetJournalTaskSessionId(metadataJson, journalId) {
51360
51906
  } catch {
51361
51907
  throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
51362
51908
  }
51363
- if (!isJsonRecord2(metadata))
51909
+ if (!isJsonRecord3(metadata))
51364
51910
  throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
51365
51911
  const reason = metadata.reason;
51366
- if (!isJsonRecord2(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
51912
+ if (!isJsonRecord3(reason) || typeof reason.taskSessionId !== "string" || reason.taskSessionId === "") {
51367
51913
  throw new Error(`changeset-journal-reason-malformed: ${journalId}`);
51368
51914
  }
51369
51915
  return reason.taskSessionId;
@@ -51396,7 +51942,7 @@ function committedChangeSetJournalFiles(filesJson, journalId) {
51396
51942
  if (!Array.isArray(parsed))
51397
51943
  throw new Error(`changeset-journal-files-malformed: ${journalId}`);
51398
51944
  return parsed.map((entry) => {
51399
- if (!isJsonRecord2(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
51945
+ if (!isJsonRecord3(entry) || typeof entry.path !== "string" || typeof entry.operation !== "string") {
51400
51946
  throw new Error(`changeset-journal-file-malformed: ${journalId}`);
51401
51947
  }
51402
51948
  if (typeof entry.bodyHash !== "string" || entry.bodyHash === "") {
@@ -51416,7 +51962,7 @@ function committedChangeSetProjectionOwned(metadataJson, journalId) {
51416
51962
  } catch {
51417
51963
  throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
51418
51964
  }
51419
- return isJsonRecord2(metadata) && metadata.projectionOwned === true;
51965
+ return isJsonRecord3(metadata) && metadata.projectionOwned === true;
51420
51966
  }
51421
51967
  function committedChangeSetModelTransition(metadataJson, journalId) {
51422
51968
  let metadata;
@@ -51425,7 +51971,7 @@ function committedChangeSetModelTransition(metadataJson, journalId) {
51425
51971
  } catch {
51426
51972
  throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
51427
51973
  }
51428
- if (!isJsonRecord2(metadata))
51974
+ if (!isJsonRecord3(metadata))
51429
51975
  throw new Error(`changeset-journal-metadata-malformed: ${journalId}`);
51430
51976
  if (metadata.modelTransition === undefined)
51431
51977
  return;
@@ -51439,7 +51985,7 @@ function readChangeSetJournalMetadata(db, journalId) {
51439
51985
  }
51440
51986
  function withChangeSetJournalArchitectureLedger(metadata, patch) {
51441
51987
  const existing = metadata.architectureLedger;
51442
- const architectureLedger = isJsonRecord2(existing) ? { ...existing, ...patch } : {
51988
+ const architectureLedger = isJsonRecord3(existing) ? { ...existing, ...patch } : {
51443
51989
  schemaVersion: "archcontext.changeset-ledger-recovery/v1",
51444
51990
  ...patch
51445
51991
  };
@@ -51461,12 +52007,12 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
51461
52007
  const architectureLedger = metadata.architectureLedger;
51462
52008
  if (architectureLedger === undefined)
51463
52009
  return;
51464
- if (!isJsonRecord2(architectureLedger))
52010
+ if (!isJsonRecord3(architectureLedger))
51465
52011
  throw new Error("changeset-ledger-recovery-metadata-malformed");
51466
52012
  const plannedEvent = architectureLedger.plannedEvent;
51467
52013
  if (plannedEvent === undefined)
51468
52014
  return;
51469
- if (!isJsonRecord2(plannedEvent))
52015
+ if (!isJsonRecord3(plannedEvent))
51470
52016
  throw new Error("changeset-ledger-recovery-planned-event-malformed");
51471
52017
  const event = plannedEvent;
51472
52018
  try {
@@ -51476,7 +52022,7 @@ function changeSetJournalPlannedLedgerEvent(metadata) {
51476
52022
  }
51477
52023
  return event;
51478
52024
  }
51479
- function isJsonRecord2(value) {
52025
+ function isJsonRecord3(value) {
51480
52026
  return typeof value === "object" && value !== null && !Array.isArray(value);
51481
52027
  }
51482
52028
  function recoverJournalFiles(root, files) {
@@ -59359,11 +59905,13 @@ class ArchctxDaemon {
59359
59905
  return okEnvelope("status", status);
59360
59906
  const repositoryId = repositoryFingerprint(root);
59361
59907
  const session = this.sessions.get(repositoryId);
59908
+ const ignoredModelPaths = ignoredArchitectureModelPaths(root);
59362
59909
  return okEnvelope("status", {
59363
59910
  ...status,
59364
59911
  repositoryId,
59365
59912
  headSha: session?.workspace.headSha ?? readHeadSha(root),
59366
- worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root))
59913
+ worktreeDigest: computeWorktreeDigest(root, listGitVisibleFiles(root)),
59914
+ ...ignoredModelPaths.length === 0 ? {} : { architectureModelIgnoredPaths: ignoredModelPaths }
59367
59915
  });
59368
59916
  } catch (error) {
59369
59917
  throw publicOperationFailure(error);
@@ -60078,26 +60626,19 @@ function exportLikeC4Model(model) {
60078
60626
  const normalized = normalizeNativeModel(model);
60079
60627
  const lines = [
60080
60628
  "// Generated by ArchContext. Native model remains source of truth.",
60081
- "specification {",
60082
- " element capability",
60083
- " element module",
60084
- " element component",
60085
- " element interface",
60086
- " element datastore",
60087
- " element external_system",
60088
- "}",
60629
+ ...likeC4SpecificationLines(normalized.nodes),
60089
60630
  "",
60090
60631
  "model {"
60091
60632
  ];
60092
60633
  for (const node of normalized.nodes) {
60093
60634
  lines.push(` // archctx-node ${JSON.stringify(node)}`);
60094
- lines.push(` ${likec4Id(node.id)} = ${likec4Kind(node.kind)} "${escapeDsl2(node.name)}"`);
60635
+ lines.push(` ${likec4Id(node.id)} = ${likeC4ElementKind(node.kind)} "${escapeDsl2(node.name)}"`);
60095
60636
  }
60096
60637
  for (const relation of normalized.relations) {
60097
60638
  lines.push(` // archctx-relation ${JSON.stringify(relation)}`);
60098
60639
  lines.push(` ${likec4Id(relation.source)} -> ${likec4Id(relation.target)} "${escapeDsl2(relation.kind)}"`);
60099
60640
  }
60100
- lines.push("}", "", "views {", " view index {", " include *", " autoLayout TopBottom", " }", "}");
60641
+ lines.push("}", "", ...likeC4IndexViewLines(architectureLayerGroups(normalized), likec4Id));
60101
60642
  const content = `${lines.join(`
60102
60643
  `)}
60103
60644
  `;
@@ -60127,15 +60668,6 @@ function importLikeC4InitialModel(content) {
60127
60668
  function likec4Id(id) {
60128
60669
  return stableId(id).replace(/-/g, "_").replace(/\./g, "_").replace(/::/g, "__");
60129
60670
  }
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
60671
  function escapeDsl2(value) {
60140
60672
  return value.replace(/"/g, "'");
60141
60673
  }
@@ -60144,17 +60676,22 @@ function escapeDsl2(value) {
60144
60676
  init_src();
60145
60677
  function exportStructurizrWorkspace(model, name = "ArchContext Export") {
60146
60678
  const normalized = normalizeNativeModel(model);
60679
+ const { layerByNodeId } = architectureLayerGroups(normalized);
60147
60680
  const workspace = {
60148
60681
  schemaVersion: "archcontext.structurizr-export/v1",
60149
60682
  name,
60150
60683
  model: {
60151
- elements: normalized.nodes.map((node) => ({
60152
- id: node.id,
60153
- name: node.name,
60154
- type: structurizrElementType(node.kind),
60155
- tags: [node.kind],
60156
- archcontextNative: node
60157
- })),
60684
+ elements: normalized.nodes.map((node) => {
60685
+ const layer = layerByNodeId.get(node.id);
60686
+ return {
60687
+ id: node.id,
60688
+ name: node.name,
60689
+ type: architectureStructurizrElementType(node.kind),
60690
+ tags: [node.kind],
60691
+ ...layer === undefined ? {} : { group: layer },
60692
+ archcontextNative: node
60693
+ };
60694
+ }),
60158
60695
  relationships: normalized.relations.map((relation) => ({
60159
60696
  id: relation.id,
60160
60697
  sourceId: relation.source,
@@ -60198,17 +60735,6 @@ function importStructurizrInitialModel(content) {
60198
60735
  warnings: ["Structurizr import is initialization-only; Native ArchContext remains source of truth."]
60199
60736
  };
60200
60737
  }
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
60738
 
60213
60739
  // packages/surfaces/mcp-local/src/tool-schemas.ts
60214
60740
  var text2 = { type: "string", minLength: 1 };
@@ -60782,17 +61308,49 @@ var architecture_node_schema_default2 = {
60782
61308
  type: "object",
60783
61309
  additionalProperties: false,
60784
61310
  required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
61311
+ allOf: [
61312
+ {
61313
+ if: { properties: { kind: { const: "namespace" } }, required: ["kind"] },
61314
+ then: { required: ["namespace"] },
61315
+ else: { not: { required: ["namespace"] } }
61316
+ },
61317
+ {
61318
+ if: { properties: { kind: { const: "mount" } }, required: ["kind"] },
61319
+ then: { required: ["mount"] },
61320
+ else: { not: { required: ["mount"] } }
61321
+ }
61322
+ ],
60785
61323
  properties: {
60786
61324
  schemaVersion: { const: "archcontext.node/v2" },
60787
61325
  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
61326
  kind: {
60789
61327
  type: "string",
60790
- enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
61328
+ enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment", "process", "namespace", "mount"]
60791
61329
  },
60792
61330
  name: { type: "string" },
60793
61331
  status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
60794
61332
  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-]*)+$" },
61333
+ layer: { type: "string", pattern: "^[a-z][a-z0-9]*(-[a-z0-9]+)*$" },
60795
61334
  summary: { type: "string" },
61335
+ namespace: {
61336
+ type: "object",
61337
+ additionalProperties: false,
61338
+ required: ["type"],
61339
+ properties: {
61340
+ type: { type: "string", enum: ["host", "container", "pod", "kubernetes", "pid", "mount", "net", "user"] }
61341
+ }
61342
+ },
61343
+ mount: {
61344
+ type: "object",
61345
+ additionalProperties: false,
61346
+ required: ["target", "mode", "order"],
61347
+ properties: {
61348
+ source: { type: "string", minLength: 1 },
61349
+ target: { type: "string", pattern: "^/([^/\\u0000]+(/[^/\\u0000]+)*)?$" },
61350
+ mode: { type: "string", enum: ["ro", "rw"] },
61351
+ order: { type: "integer", minimum: 0 }
61352
+ }
61353
+ },
60796
61354
  responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
60797
61355
  source: {
60798
61356
  type: "object",