archctx 0.7.0 → 0.7.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/bin/archctx.mjs CHANGED
@@ -167,6 +167,65 @@ var init_architecture = __esm(() => {
167
167
  ];
168
168
  });
169
169
 
170
+ // packages/contracts/src/docs-anchors.ts
171
+ function docsAnchorsReportIssues(report) {
172
+ const issues = [];
173
+ const ids = report.nodes.map((node) => node.nodeId);
174
+ const sorted = [...new Set(ids)].sort();
175
+ if (sorted.length !== ids.length || sorted.some((id, index) => id !== ids[index]))
176
+ issues.push("nodes.nodeId must be sorted and unique");
177
+ const findings = report.nodes.flatMap((node) => node.findings);
178
+ const unchecked = report.nodes.flatMap((node) => node.unchecked);
179
+ if (report.summary.citations !== report.nodes.reduce((total, node) => total + node.citationCount, 0))
180
+ issues.push("summary.citations must equal the sum of nodes[].citationCount");
181
+ if (report.summary.findings !== findings.length)
182
+ issues.push("summary.findings must equal the number of findings");
183
+ if (report.summary.unchecked !== unchecked.length)
184
+ issues.push("summary.unchecked must equal the number of unchecked citations");
185
+ for (const kind of DOCS_ANCHOR_FINDING_KINDS) {
186
+ if (report.summary.byKind[kind] !== findings.filter((finding) => finding.kind === kind).length)
187
+ issues.push(`summary.byKind.${kind} must equal the number of ${kind} findings`);
188
+ }
189
+ for (const [index, node] of report.nodes.entries()) {
190
+ if (node.citationCount === 0)
191
+ issues.push(`nodes[${index}] must have at least one citation`);
192
+ for (const [findingIndex, finding] of node.findings.entries()) {
193
+ issues.push(...docsAnchorFindingIssues(finding, `nodes[${index}].findings[${findingIndex}]`));
194
+ }
195
+ }
196
+ if (report.codeGraph.availability === "ready" && unchecked.some((entry) => entry.reasonCode === "codegraph-unavailable")) {
197
+ issues.push("codegraph-unavailable is reported only when codeGraph.availability is unavailable");
198
+ }
199
+ return issues;
200
+ }
201
+ function docsAnchorFindingIssues(finding, prefix) {
202
+ const issues = [];
203
+ const { citation } = finding;
204
+ if (finding.kind === "missing-path" && citation.path === undefined)
205
+ issues.push(`${prefix} missing-path must name citation.path`);
206
+ if (finding.kind === "missing-symbol" && citation.symbol === undefined)
207
+ issues.push(`${prefix} missing-symbol must name citation.symbol`);
208
+ if (finding.kind === "line-out-of-range" && (citation.line === undefined || finding.lineCount === undefined)) {
209
+ issues.push(`${prefix} line-out-of-range must name citation.line and lineCount`);
210
+ }
211
+ if (finding.lineCount !== undefined !== (finding.kind === "line-out-of-range"))
212
+ issues.push(`${prefix}.lineCount is present only on line-out-of-range`);
213
+ if (finding.kind === "line-moved") {
214
+ const lines = finding.currentLines ?? [];
215
+ if (citation.line === undefined || citation.symbol === undefined)
216
+ issues.push(`${prefix} line-moved must name citation.line and citation.symbol`);
217
+ if (lines.length === 0 || lines.some((line, index) => index > 0 && line <= lines[index - 1]))
218
+ issues.push(`${prefix}.currentLines must be non-empty, sorted and unique`);
219
+ } else if (finding.currentLines !== undefined) {
220
+ issues.push(`${prefix}.currentLines is present only on line-moved`);
221
+ }
222
+ return issues;
223
+ }
224
+ var DOCS_ANCHORS_REPORT_SCHEMA_VERSION = "archcontext.docs-anchors/v1", DOCS_ANCHOR_FINDING_KINDS;
225
+ var init_docs_anchors = __esm(() => {
226
+ DOCS_ANCHOR_FINDING_KINDS = ["line-moved", "line-out-of-range", "missing-path", "missing-symbol"];
227
+ });
228
+
170
229
  // packages/contracts/src/external-docs.ts
171
230
  var EXTERNAL_DOCUMENTATION_RESOURCE_SCHEMA_VERSION = "archcontext.external-document/v1", CONTEXT7_LOCKFILE_SCHEMA_VERSION = "archcontext.context7-lock/v1";
172
231
 
@@ -674,6 +733,9 @@ var init_ledger = __esm(() => {
674
733
  ];
675
734
  });
676
735
 
736
+ // packages/contracts/src/ownership-dependencies.ts
737
+ var CHANGESET_OWNERSHIP_DELTA_SCHEMA_VERSION = "archcontext.changeset-ownership-delta/v1", CHANGESET_OWNERSHIP_DELTA_PATH_LIMIT = 50, CAPABILITY_DEPENDENCIES_SCHEMA_VERSION = "archcontext.capability-dependencies/v1", CAPABILITY_DEPENDENCIES_EDGE_LIMIT = 200, CAPABILITY_DEPENDENCIES_CALLS_UNAVAILABLE_REASON = "call-edges-not-exposed";
738
+
677
739
  // packages/contracts/src/ports.ts
678
740
  function baseModelBlockingErrors(result) {
679
741
  if (result.valid)
@@ -1407,7 +1469,7 @@ function sortedUniqueIssues(label, values) {
1407
1469
  const expected = [...new Set(values)].sort();
1408
1470
  return expected.length === values.length && expected.every((value, index) => value === values[index]) ? [] : [`${label} must be sorted and unique`];
1409
1471
  }
1410
- var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v2", PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION = "archcontext.projection-apply-identity/v1", PROJECTION_APPLY_RECEIPT_SCHEMA_VERSION = "archcontext.projection-apply-receipt/v1", PROJECTION_APPLY_RECOVERY_BINDING_SCHEMA_VERSION = "archcontext.projection-apply-recovery-binding/v1", PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-intent/v1", PROJECTION_APPLY_RECOVERY_PROOF_SCHEMA_VERSION = "archcontext.projection-apply-recovery-proof/v1", PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-result/v1", PROJECTION_APPLY_READBACK_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-readback-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v5", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_PRIOR_COMMITTED_APPLY_OPERATIONS, PROJECTION_TARGETS, PROJECTION_HUMAN_ACTION_REASON_CODES, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES, ARCHITECTURE_SEMANTIC_FACETS, ARCHITECTURE_PROOF_P1_STATUSES, ARCHITECTURE_PROOF_P2_STATUSES, PROJECTION_FILE_PREVIEW_MAX_BYTES = 65536, PROJECTION_PREVIEW_TOTAL_MAX_BYTES = 1048576, PROJECTION_FRESHNESS_REASON_CODES, UTF8, SHA256_DIGEST, ISO_INSTANT;
1472
+ var PROJECTION_REQUEST_SCHEMA_VERSION = "archcontext.projection-request/v1", PROJECTION_RESULT_SCHEMA_VERSION = "archcontext.projection-result/v2", PROJECTION_APPLY_IDENTITY_SCHEMA_VERSION = "archcontext.projection-apply-identity/v1", PROJECTION_APPLY_RECEIPT_SCHEMA_VERSION = "archcontext.projection-apply-receipt/v1", PROJECTION_APPLY_RECOVERY_BINDING_SCHEMA_VERSION = "archcontext.projection-apply-recovery-binding/v1", PROJECTION_APPLY_RECOVERY_INTENT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-intent/v1", PROJECTION_APPLY_RECOVERY_PROOF_SCHEMA_VERSION = "archcontext.projection-apply-recovery-proof/v1", PROJECTION_APPLY_RECOVERY_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-recovery-result/v1", PROJECTION_APPLY_READBACK_RESULT_SCHEMA_VERSION = "archcontext.projection-apply-readback-result/v1", ARCHITECTURE_REFRESH_SIGNAL_SCHEMA_VERSION = "archcontext.architecture-refresh-signal/v1", ARCHCTX_CAPABILITIES_SCHEMA_VERSION = "archcontext.capabilities/v1", ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v5", AGENT_CONTEXT_RENDERER_VERSION = "archcontext.agent-context-renderer/v1", PROJECTION_MODES, PROJECTION_PRIOR_COMMITTED_APPLY_OPERATIONS, PROJECTION_TARGETS, PROJECTION_HUMAN_ACTION_REASON_CODES, ARCHITECTURE_MAJOR_CHANGE_REASON_CODES, ARCHCTX_FEATURES, ARCHITECTURE_SEMANTIC_FACETS, ARCHITECTURE_PROOF_P1_STATUSES, ARCHITECTURE_PROOF_P2_STATUSES, ARCHITECTURE_FLOW_SELECTOR_CHECK_SCHEMA_VERSION = "archcontext.flow-selector-check/v1", PROJECTION_FILE_PREVIEW_MAX_BYTES = 65536, PROJECTION_PREVIEW_TOTAL_MAX_BYTES = 1048576, PROJECTION_FRESHNESS_REASON_CODES, UTF8, SHA256_DIGEST, ISO_INSTANT;
1411
1473
  var init_projection = __esm(() => {
1412
1474
  init_schema();
1413
1475
  PROJECTION_MODES = ["check", "plan", "apply", "adopt"];
@@ -1440,8 +1502,12 @@ var init_projection = __esm(() => {
1440
1502
  "architecture-change-capabilities-v1",
1441
1503
  "architecture-docs-renderer-v2",
1442
1504
  "architecture-refresh-signal-v1",
1505
+ "capability-dependencies-v1",
1443
1506
  "changeset-entity-operations-v1",
1507
+ "changeset-ownership-delta-v1",
1444
1508
  "code-facts-unavailable-error-v1",
1509
+ "docs-anchors-v1",
1510
+ "flow-selector-check-v1",
1445
1511
  "module-statistics-v1",
1446
1512
  "projection-apply-readback-v1",
1447
1513
  "projection-apply-receipt-v1",
@@ -1459,7 +1525,8 @@ var init_projection = __esm(() => {
1459
1525
  "refactor-assessment-v1",
1460
1526
  "refactor-observation-evidence-v1",
1461
1527
  "refactor-resolution-v1",
1462
- "refactor-scan-limits-v1"
1528
+ "refactor-scan-limits-v1",
1529
+ "refactor-scan-untracked-count-v1"
1463
1530
  ];
1464
1531
  ARCHITECTURE_SEMANTIC_FACETS = [
1465
1532
  "constraints",
@@ -1802,7 +1869,7 @@ function productVersionManifest() {
1802
1869
  }
1803
1870
  };
1804
1871
  }
1805
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.7.0", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1872
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.7.2", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
1806
1873
 
1807
1874
  // packages/contracts/src/refactor.ts
1808
1875
  function refactorProposalDigest(proposal) {
@@ -1919,6 +1986,9 @@ function refactorRequestInvariantIssues(request, prefix = "request") {
1919
1986
  }
1920
1987
  if (request.proposal)
1921
1988
  issues.push(...refactorProposalInvariantIssues(request.proposal, `${prefix}.proposal`));
1989
+ if (request.importNodeLimit !== undefined && !(Number.isInteger(request.importNodeLimit) && request.importNodeLimit >= 1)) {
1990
+ issues.push(`${prefix}.importNodeLimit must be a positive integer`);
1991
+ }
1922
1992
  return issues;
1923
1993
  }
1924
1994
  function refactorVerificationRequestInvariantIssues(request, prefix = "request") {
@@ -1937,6 +2007,9 @@ function refactorVerificationRequestInvariantIssues(request, prefix = "request")
1937
2007
  if (request.expectedWorktreeDigest !== undefined) {
1938
2008
  issues.push(...digestIssues(`${prefix}.expectedWorktreeDigest`, request.expectedWorktreeDigest));
1939
2009
  }
2010
+ if (request.importNodeLimit !== undefined && !(Number.isInteger(request.importNodeLimit) && request.importNodeLimit >= 1)) {
2011
+ issues.push(`${prefix}.importNodeLimit must be a positive integer`);
2012
+ }
1940
2013
  if (request.executionEvidenceRefs !== undefined) {
1941
2014
  if (!Array.isArray(request.executionEvidenceRefs)) {
1942
2015
  issues.push(`${prefix}.executionEvidenceRefs must be an array`);
@@ -2013,6 +2086,16 @@ function moduleStatisticsSnapshotInvariantIssues(snapshot, prefix = "snapshot")
2013
2086
  }
2014
2087
  }
2015
2088
  issues.push(...nonNegativeIntegerIssues(`${prefix}.codeFacts.edgeLimit`, snapshot.codeFacts.edgeLimit));
2089
+ const importNodeCount = snapshot.codeFacts.importNodeCount ?? null;
2090
+ issues.push(...nonNegativeIntegerIssues(`${prefix}.codeFacts.importNodeCount`, importNodeCount));
2091
+ if (snapshot.codeFacts.reasonCodes.includes("code-facts-import-limit")) {
2092
+ const { edgeLimit } = snapshot.codeFacts;
2093
+ if (snapshot.codeFacts.coverage !== "partial")
2094
+ issues.push(`${prefix}.codeFacts.coverage must be partial when the import limit was reached`);
2095
+ if (edgeLimit === null || importNodeCount !== null && importNodeCount <= edgeLimit) {
2096
+ issues.push(`${prefix}.codeFacts.importNodeCount must exceed edgeLimit when the import limit was reached`);
2097
+ }
2098
+ }
2016
2099
  for (const [field, value] of Object.entries(snapshot.repositorySummary)) {
2017
2100
  issues.push(...nonNegativeIntegerIssues(`${prefix}.repositorySummary.${field}`, value));
2018
2101
  }
@@ -2467,6 +2550,7 @@ var init_refactor = __esm(() => {
2467
2550
  "executionEvidenceRefs",
2468
2551
  "expectedHeadSha",
2469
2552
  "expectedWorktreeDigest",
2553
+ "importNodeLimit",
2470
2554
  "recommendationId",
2471
2555
  "schemaVersion"
2472
2556
  ];
@@ -2479,6 +2563,7 @@ var init_refactor = __esm(() => {
2479
2563
  ];
2480
2564
  REFACTOR_SCALE_REASON_CODES = [
2481
2565
  "caller-coverage-unknown",
2566
+ "code-facts-import-limit",
2482
2567
  "code-facts-missing",
2483
2568
  "code-facts-truncated",
2484
2569
  "major-change-detected",
@@ -2712,6 +2797,7 @@ var init_validator = __esm(() => {
2712
2797
  var init_src = __esm(() => {
2713
2798
  init_control_plane_routes();
2714
2799
  init_architecture();
2800
+ init_docs_anchors();
2715
2801
  init_github_governance();
2716
2802
  init_ledger();
2717
2803
  init_ports();
@@ -13366,14 +13452,18 @@ function sanitizeGithubDeveloperReviewState(state, statePath) {
13366
13452
  assertNoReviewSecrets(data, []);
13367
13453
  return data;
13368
13454
  }
13455
+ function isStandardBase64Value(token) {
13456
+ return token.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(token) && /[A-Za-z0-9+]{20}/.test(token);
13457
+ }
13369
13458
  function assertNoCliSecretMaterial(value) {
13370
13459
  const serialized = typeof value === "string" ? value : JSON.stringify(value);
13371
13460
  if (!serialized)
13372
13461
  return;
13373
13462
  if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(serialized))
13374
13463
  throw new Error("device-private-key-material-forbidden");
13375
- if (/(^|["'\s])(?:file:\/\/|\/|\.\/|\.\.\/|~\/)[^"'\s]*(?:private|device|key)[^"'\s]*/i.test(serialized)) {
13376
- throw new Error("device-private-key-file-ref-forbidden");
13464
+ for (const [, token] of serialized.matchAll(/(?:^|["'\s])((?:file:\/\/|\.{0,2}\/|~\/)[^"'\s]*)/gi)) {
13465
+ if (/private|device|key/i.test(token) && !isStandardBase64Value(token))
13466
+ throw new Error("device-private-key-file-ref-forbidden");
13377
13467
  }
13378
13468
  if (/(access|refresh|token)_[A-Za-z0-9_-]+/.test(serialized))
13379
13469
  throw new Error("github-token-material-forbidden");
@@ -15017,7 +15107,7 @@ function parseScalar(value) {
15017
15107
 
15018
15108
  // packages/local-runtime/git-adapter/src/index.ts
15019
15109
  import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
15020
- import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync4 } from "node:fs";
15110
+ import { existsSync as existsSync6, lstatSync as lstatSync3, mkdirSync as mkdirSync4, mkdtempSync, readdirSync as readdirSync4, readFileSync as readFileSync7, rmSync as rmSync4 } from "node:fs";
15021
15111
  import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
15022
15112
  import { tmpdir } from "node:os";
15023
15113
 
@@ -16363,29 +16453,86 @@ function validateStep(input) {
16363
16453
  input.diagnostics.push({ code: "selector-declaration-ambiguous", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16364
16454
  return;
16365
16455
  }
16366
- const evidence = input.evidence.filter((entry) => evidenceSelectorKey(entry) === selectorKey && entry.nodeId === declarations[0].nodeId);
16367
- if (evidence.length === 0) {
16368
- input.diagnostics.push({ code: "selector-evidence-missing", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16369
- return;
16370
- }
16371
- if (evidence.length > 1) {
16372
- input.diagnostics.push({ code: "selector-evidence-ambiguous", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16373
- return;
16374
- }
16375
- if (evidence[0].ambiguous) {
16376
- input.diagnostics.push({ code: "selector-evidence-ambiguous", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16377
- return;
16378
- }
16379
- if (evidence[0].truncated) {
16380
- input.diagnostics.push({ code: "selector-evidence-truncated", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16381
- return;
16382
- }
16383
- if (!evidence[0].matched || evidence[0].callSites.length === 0) {
16384
- input.diagnostics.push({ code: "selector-evidence-unmatched", flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16456
+ const status = architectureSelectorProofStatus(input.evidence.filter((entry) => evidenceSelectorKey(entry) === selectorKey && entry.nodeId === declarations[0].nodeId));
16457
+ if (status !== "proven") {
16458
+ input.diagnostics.push({ code: SELECTOR_STATUS_DIAGNOSTICS[status], flowId: input.flowId, stepId: input.step.id, detail: selectorKey });
16385
16459
  return;
16386
16460
  }
16387
16461
  input.provenSelectorKeys.add(selectorKey);
16388
16462
  }
16463
+ var SELECTOR_STATUS_DIAGNOSTICS = {
16464
+ "ambiguous-symbol": "selector-evidence-ambiguous",
16465
+ "evidence-missing": "selector-evidence-missing",
16466
+ "evidence-truncated": "selector-evidence-truncated",
16467
+ "false-builtin-call": "selector-evidence-unmatched",
16468
+ "no-direct-call": "selector-evidence-unmatched",
16469
+ "only-references": "selector-evidence-unmatched"
16470
+ };
16471
+ function architectureSelectorProofStatus(evidence) {
16472
+ if (evidence.length === 0)
16473
+ return "evidence-missing";
16474
+ if (evidence.length > 1 || evidence[0].ambiguous)
16475
+ return "ambiguous-symbol";
16476
+ if (evidence[0].truncated)
16477
+ return "evidence-truncated";
16478
+ if (evidence[0].matched && evidence[0].callSites.length > 0)
16479
+ return "proven";
16480
+ if ((evidence[0].rejectedBuiltinCallSites ?? []).length > 0)
16481
+ return "false-builtin-call";
16482
+ if ((evidence[0].referenceSites ?? []).length > 0)
16483
+ return "only-references";
16484
+ return "no-direct-call";
16485
+ }
16486
+ function checkSemanticFlowSelectors(input) {
16487
+ const stepsByKey = new Map;
16488
+ for (const flow of [...input.flows].sort((left, right) => left.id.localeCompare(right.id))) {
16489
+ if (flow.applicability !== "required")
16490
+ continue;
16491
+ for (const step of [...flow.steps, ...flow.outcomes.flatMap((outcome) => outcome.steps)]) {
16492
+ const key = evidenceSelectorKey(step.evidence);
16493
+ stepsByKey.set(key, [...stepsByKey.get(key) ?? [], { flowId: flow.id, stepId: step.id }]);
16494
+ }
16495
+ }
16496
+ const selectors = input.nodes.flatMap((node) => (node.source?.entrypoints ?? []).flatMap((entrypoint) => entrypoint.symbols.flatMap((source) => source.sinks.map((sink) => {
16497
+ const key = evidenceSelectorKey({ entrypointId: entrypoint.id, sourceSymbol: source.name, sinkId: sink.id });
16498
+ const evidence = input.evidence.filter((entry) => evidenceSelectorKey(entry) === key && entry.nodeId === node.id);
16499
+ const single = evidence.length === 1 ? evidence[0] : undefined;
16500
+ return {
16501
+ nodeId: node.id,
16502
+ entrypointId: entrypoint.id,
16503
+ sourcePath: entrypoint.path,
16504
+ sourceSymbol: source.name,
16505
+ sinkId: sink.id,
16506
+ sinkPath: sink.path,
16507
+ sinkSymbol: sink.symbol,
16508
+ status: architectureSelectorProofStatus(evidence),
16509
+ callSites: single?.callSites ?? [],
16510
+ referenceSites: single?.referenceSites ?? [],
16511
+ rejectedBuiltinCallSites: single?.rejectedBuiltinCallSites ?? [],
16512
+ flowSteps: stepsByKey.get(key) ?? []
16513
+ };
16514
+ })))).sort((left, right) => compareStrings(left.nodeId, right.nodeId) || compareStrings(left.entrypointId, right.entrypointId) || compareStrings(left.sourceSymbol, right.sourceSymbol) || compareStrings(left.sinkId, right.sinkId));
16515
+ const capabilityIds = [...new Set(input.flows.map((flow) => flow.capabilityId))].sort(compareStrings);
16516
+ const capabilities = capabilityIds.map((capabilityId) => {
16517
+ const { p2 } = compileSemanticCapabilityDiagrams({ capabilityId, nodes: input.nodes, relations: input.relations, flows: input.flows, evidence: input.evidence });
16518
+ return { capabilityId, p2Status: p2.status, diagnostics: p2.diagnostics };
16519
+ });
16520
+ const unproven = selectors.filter((selector) => selector.status !== "proven");
16521
+ return {
16522
+ ok: capabilities.every((capability) => capability.p2Status !== "unprovable"),
16523
+ summary: {
16524
+ selectors: selectors.length,
16525
+ proven: selectors.length - unproven.length,
16526
+ unproven: unproven.length,
16527
+ usedUnproven: unproven.filter((selector) => selector.flowSteps.length > 0).length
16528
+ },
16529
+ selectors,
16530
+ capabilities
16531
+ };
16532
+ }
16533
+ function compareStrings(left, right) {
16534
+ return left < right ? -1 : left > right ? 1 : 0;
16535
+ }
16389
16536
  function proofRelevantSelectorEvidence(evidence) {
16390
16537
  return evidence.map((entry) => ({
16391
16538
  nodeId: entry.nodeId,
@@ -16486,7 +16633,7 @@ function mermaidStableId(prefix, value) {
16486
16633
  return `${prefix}_${slug}_${digestJson(value).slice(7, 15)}`;
16487
16634
  }
16488
16635
  function escapeMermaidLabel(value) {
16489
- return value.replace(/["#;,]/g, (character) => ({ '"': "'", "#": "#", ";": ";", ",": "," })[character]);
16636
+ return value.replace(/["#;,]/g, (character) => ({ '"': "#quot;", "#": "#35;", ";": "#59;", ",": "#44;" })[character]);
16490
16637
  }
16491
16638
 
16492
16639
  // packages/core/projection-engine/src/major-change.ts
@@ -17142,6 +17289,374 @@ function diffHunks(ops) {
17142
17289
  }
17143
17290
  return hunks;
17144
17291
  }
17292
+ // packages/core/projection-engine/src/prose-anchors.ts
17293
+ init_src();
17294
+ var IDENTIFIER = "[A-Za-z_][A-Za-z0-9_$]*";
17295
+ var QUALIFIED_IDENTIFIER = `${IDENTIFIER}(?:\\.${IDENTIFIER})*`;
17296
+ var IDENTIFIER_PATTERN = new RegExp(`^${IDENTIFIER}$`);
17297
+ var QUALIFIED_PATTERN = new RegExp(`^${QUALIFIED_IDENTIFIER}$`);
17298
+ var CALL_PATTERN = new RegExp(`^(${QUALIFIED_IDENTIFIER})\\(\\)$`);
17299
+ var PATH_PATTERN = /^[A-Za-z0-9_.@+\-/]+$/;
17300
+ var LINE_FRAGMENT = /^L(\d+)(?:-L?(\d+))?$/;
17301
+ var LINE_SUFFIX = /^(.*?):(\d+)(?:-(\d+))?$/;
17302
+ var FILE_EXTENSION = /\.[A-Za-z0-9]+$/;
17303
+ var BINDING_GAP = /^\s*[([,:–—-]?\s*$/;
17304
+ var GENERATED_BEGIN = /<!-- BEGIN ARCHCONTEXT:generated target="([^"]+)"[^>]*-->/;
17305
+ function checkProseAnchors(input) {
17306
+ const nodes = [];
17307
+ let documents = 0;
17308
+ for (const node of [...input.nodes].sort((left, right) => left.nodeId < right.nodeId ? -1 : left.nodeId > right.nodeId ? 1 : 0)) {
17309
+ if (node.document)
17310
+ documents += 1;
17311
+ const result = checkNode(node, input);
17312
+ if (result.citationCount > 0)
17313
+ nodes.push(result);
17314
+ }
17315
+ const findings = nodes.flatMap((node) => node.findings);
17316
+ const byKind = Object.fromEntries(DOCS_ANCHOR_FINDING_KINDS.map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]));
17317
+ return {
17318
+ schemaVersion: DOCS_ANCHORS_REPORT_SCHEMA_VERSION,
17319
+ profile: input.profile,
17320
+ codeGraph: input.codeGraph.availability === "ready" ? { availability: "ready" } : { availability: "unavailable", reasonCode: input.codeGraph.reasonCode },
17321
+ summary: {
17322
+ nodes: input.nodes.length,
17323
+ documents,
17324
+ citations: nodes.reduce((total, node) => total + node.citationCount, 0),
17325
+ findings: findings.length,
17326
+ unchecked: nodes.reduce((total, node) => total + node.unchecked.length, 0),
17327
+ byKind
17328
+ },
17329
+ nodes
17330
+ };
17331
+ }
17332
+ function extractNodeProseCitations(node) {
17333
+ const located = [];
17334
+ if (typeof node.summary === "string") {
17335
+ for (const line of node.summary.split(/\r?\n/)) {
17336
+ located.push(...parseLineCitations(line).map((parsed) => ({ origin: { kind: "node-summary" }, parsed })));
17337
+ }
17338
+ }
17339
+ for (const [index, responsibility] of (node.responsibilities ?? []).entries()) {
17340
+ if (typeof responsibility !== "string")
17341
+ continue;
17342
+ for (const line of responsibility.split(/\r?\n/)) {
17343
+ located.push(...parseLineCitations(line).map((parsed) => ({ origin: { kind: "node-responsibility", index }, parsed })));
17344
+ }
17345
+ }
17346
+ if (node.document) {
17347
+ for (const { line, text } of humanOwnedMarkdownLines(node.document.body)) {
17348
+ located.push(...parseLineCitations(text).map((parsed) => ({ origin: { kind: "document", path: node.document.path, line }, parsed })));
17349
+ }
17350
+ }
17351
+ return located;
17352
+ }
17353
+ function humanOwnedMarkdownLines(body) {
17354
+ const lines = [];
17355
+ let generatedEnd;
17356
+ let fence;
17357
+ let inComment = false;
17358
+ for (const [index, raw] of body.split(/\r?\n/).entries()) {
17359
+ if (generatedEnd !== undefined) {
17360
+ if (raw.includes(generatedEnd))
17361
+ generatedEnd = undefined;
17362
+ continue;
17363
+ }
17364
+ const begin = GENERATED_BEGIN.exec(raw);
17365
+ if (begin) {
17366
+ const end = `<!-- END ARCHCONTEXT:generated target="${begin[1]}" -->`;
17367
+ if (!raw.slice(begin.index + begin[0].length).includes(end))
17368
+ generatedEnd = end;
17369
+ continue;
17370
+ }
17371
+ if (fence) {
17372
+ const close = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(raw);
17373
+ if (close && close[1][0] === fence.char && close[1].length >= fence.length)
17374
+ fence = undefined;
17375
+ continue;
17376
+ }
17377
+ const open = /^ {0,3}(`{3,}|~{3,})/.exec(raw);
17378
+ if (open && !inComment) {
17379
+ fence = { char: open[1][0], length: open[1].length };
17380
+ continue;
17381
+ }
17382
+ const stripped = stripHtmlComments(raw, inComment);
17383
+ inComment = stripped.inComment;
17384
+ if (stripped.text.trim() !== "")
17385
+ lines.push({ line: index + 1, text: stripped.text });
17386
+ }
17387
+ return lines;
17388
+ }
17389
+ function stripHtmlComments(raw, inComment) {
17390
+ let text = "";
17391
+ let rest = raw;
17392
+ let open = inComment;
17393
+ while (rest.length > 0) {
17394
+ if (open) {
17395
+ const close = rest.indexOf("-->");
17396
+ if (close < 0)
17397
+ return { text, inComment: true };
17398
+ rest = rest.slice(close + 3);
17399
+ open = false;
17400
+ continue;
17401
+ }
17402
+ const start = rest.indexOf("<!--");
17403
+ if (start < 0)
17404
+ return { text: text + rest, inComment: false };
17405
+ text += rest.slice(0, start);
17406
+ rest = rest.slice(start + 4);
17407
+ open = true;
17408
+ }
17409
+ return { text, inComment: open };
17410
+ }
17411
+ function inlineCodeSpans(text) {
17412
+ const spans = [];
17413
+ let index = 0;
17414
+ while (index < text.length) {
17415
+ if (text[index] !== "`") {
17416
+ index += 1;
17417
+ continue;
17418
+ }
17419
+ if (precedingBackslashes(text, index) % 2 === 1) {
17420
+ index += 1;
17421
+ continue;
17422
+ }
17423
+ let runEnd = index;
17424
+ while (text[runEnd] === "`")
17425
+ runEnd += 1;
17426
+ const ticks = runEnd - index;
17427
+ let cursor = runEnd;
17428
+ let close = -1;
17429
+ while (cursor < text.length) {
17430
+ if (text[cursor] !== "`") {
17431
+ cursor += 1;
17432
+ continue;
17433
+ }
17434
+ let closeEnd = cursor;
17435
+ while (text[closeEnd] === "`")
17436
+ closeEnd += 1;
17437
+ if (closeEnd - cursor === ticks) {
17438
+ close = cursor;
17439
+ break;
17440
+ }
17441
+ cursor = closeEnd;
17442
+ }
17443
+ if (close < 0) {
17444
+ index = runEnd;
17445
+ continue;
17446
+ }
17447
+ let content = text.slice(runEnd, close);
17448
+ if (content.length > 2 && content.startsWith(" ") && content.endsWith(" "))
17449
+ content = content.slice(1, -1);
17450
+ spans.push({ content, start: index, end: close + ticks });
17451
+ index = close + ticks;
17452
+ }
17453
+ return spans;
17454
+ }
17455
+ function precedingBackslashes(text, index) {
17456
+ let count = 0;
17457
+ while (index - count - 1 >= 0 && text[index - count - 1] === "\\")
17458
+ count += 1;
17459
+ return count;
17460
+ }
17461
+ function parseLineCitations(text) {
17462
+ const spans = inlineCodeSpans(text);
17463
+ const parsed = spans.map((span) => parseCitationText(span.content));
17464
+ const consumed = new Set;
17465
+ for (const [index, citation] of parsed.entries()) {
17466
+ if (citation?.form !== "path" || citation.line === undefined || citation.symbol !== undefined)
17467
+ continue;
17468
+ const bind = (other, gap) => {
17469
+ const candidate = parsed[other];
17470
+ if (consumed.has(other) || !candidate || candidate.form === "path" || !BINDING_GAP.test(gap))
17471
+ return false;
17472
+ citation.symbol = candidate.symbol;
17473
+ consumed.add(other);
17474
+ return true;
17475
+ };
17476
+ if (index + 1 < spans.length && bind(index + 1, text.slice(spans[index].end, spans[index + 1].start)))
17477
+ continue;
17478
+ if (index > 0)
17479
+ bind(index - 1, text.slice(spans[index - 1].end, spans[index].start));
17480
+ }
17481
+ return parsed.flatMap((citation, index) => citation && !consumed.has(index) ? [citation] : []);
17482
+ }
17483
+ function parseCitationText(raw) {
17484
+ const text = raw.trim();
17485
+ if (text === "" || /\s/.test(text))
17486
+ return;
17487
+ const call = CALL_PATTERN.exec(text);
17488
+ if (call)
17489
+ return { form: "call", text, symbol: call[1] };
17490
+ if (IDENTIFIER_PATTERN.test(text))
17491
+ return { form: "identifier", text, symbol: text };
17492
+ let rest = text;
17493
+ let line;
17494
+ let endLine;
17495
+ let symbol;
17496
+ let explicit = false;
17497
+ const hash = rest.indexOf("#");
17498
+ if (hash >= 0) {
17499
+ const fragment = rest.slice(hash + 1);
17500
+ rest = rest.slice(0, hash);
17501
+ const lineFragment = LINE_FRAGMENT.exec(fragment);
17502
+ if (lineFragment) {
17503
+ line = Number(lineFragment[1]);
17504
+ endLine = lineFragment[2] === undefined ? undefined : Number(lineFragment[2]);
17505
+ } else if (QUALIFIED_PATTERN.test(fragment)) {
17506
+ symbol = fragment;
17507
+ } else {
17508
+ return;
17509
+ }
17510
+ explicit = true;
17511
+ }
17512
+ const lineSuffix = LINE_SUFFIX.exec(rest);
17513
+ if (lineSuffix) {
17514
+ if (line !== undefined)
17515
+ return;
17516
+ rest = lineSuffix[1];
17517
+ line = Number(lineSuffix[2]);
17518
+ endLine = lineSuffix[3] === undefined ? undefined : Number(lineSuffix[3]);
17519
+ explicit = true;
17520
+ }
17521
+ if (line !== undefined && (line < 1 || endLine !== undefined && endLine < line))
17522
+ return;
17523
+ const written = rest.replace(/^\.\//, "");
17524
+ if (!PATH_PATTERN.test(written) || written.startsWith("/"))
17525
+ return;
17526
+ const directory = written.endsWith("/");
17527
+ const path = directory ? written.slice(0, -1) : written;
17528
+ const segments = path.split("/");
17529
+ if (path === "" || segments.some((segment) => segment === "" || /^\.+$/.test(segment)))
17530
+ return;
17531
+ if (!explicit && segments.length < 2)
17532
+ return;
17533
+ if (explicit && segments.length < 2 && !FILE_EXTENSION.test(path))
17534
+ return;
17535
+ return {
17536
+ form: "path",
17537
+ text,
17538
+ path,
17539
+ directory,
17540
+ explicit,
17541
+ ...line === undefined ? {} : { line },
17542
+ ...endLine === undefined ? {} : { endLine },
17543
+ ...symbol === undefined ? {} : { symbol }
17544
+ };
17545
+ }
17546
+ function checkNode(node, input) {
17547
+ const findings = [];
17548
+ const unchecked = [];
17549
+ let citationCount = 0;
17550
+ for (const { origin, parsed } of extractNodeProseCitations(node)) {
17551
+ const outcome = parsed.form === "path" ? checkPathCitation(parsed, node, input) : checkSymbolCitation(parsed, input.codeGraph);
17552
+ if (!outcome.counted)
17553
+ continue;
17554
+ citationCount += 1;
17555
+ for (const finding of outcome.findings)
17556
+ findings.push({ ...finding, origin, citation: outcome.citation });
17557
+ for (const reasonCode of outcome.unchecked)
17558
+ unchecked.push({ reasonCode, origin, citation: outcome.citation });
17559
+ }
17560
+ return {
17561
+ nodeId: node.nodeId,
17562
+ ...node.document ? { documentPath: node.document.path } : {},
17563
+ citationCount,
17564
+ findings,
17565
+ unchecked
17566
+ };
17567
+ }
17568
+ function checkSymbolCitation(parsed, codeGraph) {
17569
+ const citation = { text: parsed.text, symbol: parsed.symbol };
17570
+ if (codeGraph.availability !== "ready") {
17571
+ return parsed.form === "call" ? { counted: true, citation, findings: [], unchecked: ["codegraph-unavailable"] } : { counted: false, citation, findings: [], unchecked: [] };
17572
+ }
17573
+ const { name, container } = splitSymbol(parsed.symbol);
17574
+ const declared = matchingDeclarations(codeGraph.symbols.declarationsNamed(name), container).length > 0;
17575
+ if (parsed.form === "identifier")
17576
+ return { counted: declared, citation, findings: [], unchecked: [] };
17577
+ const missing = !codeGraph.symbols.appearsInSource(name);
17578
+ return { counted: true, citation, findings: missing ? [{ kind: "missing-symbol" }] : [], unchecked: [] };
17579
+ }
17580
+ function checkPathCitation(parsed, node, input) {
17581
+ const citation = {
17582
+ text: parsed.text,
17583
+ path: parsed.path,
17584
+ ...parsed.line === undefined ? {} : { line: parsed.line },
17585
+ ...parsed.endLine === undefined ? {} : { endLine: parsed.endLine },
17586
+ ...parsed.symbol === undefined ? {} : { symbol: parsed.symbol }
17587
+ };
17588
+ const resolution = resolveCitedPath(parsed, node, input.tree);
17589
+ if (resolution.status === "ignored")
17590
+ return { counted: false, citation, findings: [], unchecked: [] };
17591
+ if (resolution.status === "ambiguous")
17592
+ return { counted: true, citation, findings: [], unchecked: ["path-ambiguous"] };
17593
+ const needsFile = parsed.line !== undefined || parsed.symbol !== undefined;
17594
+ if (resolution.status === "missing" || needsFile && resolution.kind !== "file") {
17595
+ return { counted: true, citation, findings: [{ kind: "missing-path" }], unchecked: [] };
17596
+ }
17597
+ const resolved = resolution;
17598
+ if (resolved.path !== parsed.path)
17599
+ citation.resolvedPath = resolved.path;
17600
+ if (!needsFile)
17601
+ return { counted: true, citation, findings: [], unchecked: [] };
17602
+ const lineFinding = () => {
17603
+ if (parsed.line === undefined)
17604
+ return [];
17605
+ const lineCount = input.tree.lineCount(resolved.path);
17606
+ return (parsed.endLine ?? parsed.line) > lineCount ? [{ kind: "line-out-of-range", lineCount }] : [];
17607
+ };
17608
+ if (parsed.symbol === undefined)
17609
+ return { counted: true, citation, findings: lineFinding(), unchecked: [] };
17610
+ if (input.codeGraph.availability !== "ready") {
17611
+ return { counted: true, citation, findings: lineFinding(), unchecked: ["codegraph-unavailable"] };
17612
+ }
17613
+ const symbols = input.codeGraph.symbols;
17614
+ if (!symbols.supportsPath(resolved.path)) {
17615
+ return { counted: true, citation, findings: lineFinding(), unchecked: ["language-unsupported"] };
17616
+ }
17617
+ const { name, container } = splitSymbol(parsed.symbol);
17618
+ const declarations = matchingDeclarations(symbols.declarationsInFile(resolved.path, name), container);
17619
+ if (declarations.length === 0)
17620
+ return { counted: true, citation, findings: [{ kind: "missing-symbol" }], unchecked: [] };
17621
+ const line = parsed.line;
17622
+ if (line === undefined || declarations.some((declaration) => declaration.startLine <= line && line <= declaration.endLine)) {
17623
+ return { counted: true, citation, findings: [], unchecked: [] };
17624
+ }
17625
+ const currentLines = [...new Set(declarations.map((declaration) => declaration.startLine))].sort((left, right) => left - right);
17626
+ return { counted: true, citation, findings: [{ kind: "line-moved", currentLines }], unchecked: [] };
17627
+ }
17628
+ function resolveCitedPath(parsed, node, tree) {
17629
+ const direct = tree.kind(parsed.path);
17630
+ if (direct && !(parsed.directory && direct === "file"))
17631
+ return { status: "resolved", path: parsed.path, kind: direct };
17632
+ const missing = () => tree.isIgnored(parsed.path) ? { status: "ignored" } : { status: "missing" };
17633
+ const anchored = parsed.path.includes("/") && tree.hasTopLevelEntry(parsed.path.split("/")[0]);
17634
+ if (anchored)
17635
+ return missing();
17636
+ if (!parsed.explicit)
17637
+ return { status: "ignored" };
17638
+ let matches = tree.filesEndingWith(parsed.path);
17639
+ if (matches.length > 1 && node.footprint) {
17640
+ const footprint = new Set(node.footprint);
17641
+ const inFootprint = matches.filter((path) => footprint.has(path));
17642
+ if (inFootprint.length === 1)
17643
+ matches = inFootprint;
17644
+ }
17645
+ if (matches.length === 0)
17646
+ return missing();
17647
+ if (matches.length > 1)
17648
+ return { status: "ambiguous" };
17649
+ return { status: "resolved", path: matches[0], kind: "file" };
17650
+ }
17651
+ function splitSymbol(symbol) {
17652
+ const segments = symbol.split(".");
17653
+ return segments.length > 1 ? { name: segments[segments.length - 1], container: segments[segments.length - 2] } : { name: symbol };
17654
+ }
17655
+ function matchingDeclarations(declarations, container) {
17656
+ if (container === undefined)
17657
+ return declarations;
17658
+ return declarations.filter((declaration) => declaration.qualifiedName.split(/::|\.|#|\//).includes(container));
17659
+ }
17145
17660
 
17146
17661
  // packages/core/projection-engine/src/index.ts
17147
17662
  function nativeNodeSource(node) {
@@ -17164,15 +17679,10 @@ function renderArchitectureDocumentationProjection(input) {
17164
17679
  const generatedAt = input.generatedAt ?? "1970-01-01T00:00:00.000Z";
17165
17680
  const scaleSignalsByNodeId = new Map((input.sourceScaleSignals ?? []).map((signal) => [signal.nodeId, signal]));
17166
17681
  const scaleByNodeId = new Map([...scaleSignalsByNodeId].map(([nodeId, signal]) => [nodeId, architectureCapabilityScale(signal)]));
17682
+ const semanticNodes = semanticArchitectureNodes(model);
17167
17683
  const semanticCompilationsByNodeId = new Map(model.nodes.map((node) => [node.id, compileSemanticCapabilityDiagrams({
17168
17684
  capabilityId: node.id,
17169
- nodes: model.nodes.map((entry) => ({
17170
- id: entry.id,
17171
- kind: entry.kind,
17172
- name: entry.name,
17173
- ...entry.parent ? { parent: entry.parent } : {},
17174
- ...nativeNodeSource(entry) ? { source: nativeNodeSource(entry) } : {}
17175
- })),
17685
+ nodes: semanticNodes,
17176
17686
  relations: model.relations,
17177
17687
  flows: model.flows ?? [],
17178
17688
  evidence: input.selectorEvidence
@@ -17187,7 +17697,8 @@ function renderArchitectureDocumentationProjection(input) {
17187
17697
  resulting: semanticState,
17188
17698
  acceptedChange: input.refreshContext?.acceptedChange
17189
17699
  });
17190
- const targetDrafts = architectureDocumentationTargetDrafts(model, layout2);
17700
+ const docsLanguage = input.docsLanguage === undefined ? undefined : assertArchitectureDocsLanguage(input.docsLanguage);
17701
+ const targetDrafts = architectureDocumentationTargetDrafts(model, layout2, docsLanguage);
17191
17702
  const stampedNodeIds = new Set(architectureProjectionStampedNodeIds(model, layout2.profile));
17192
17703
  const footprintDigestByNodeId = new Map(input.sourceFootprints.map((entry) => [entry.nodeId, entry.digest]));
17193
17704
  const rendered = targetDrafts.map((draft) => {
@@ -17426,7 +17937,8 @@ function loadArchitectureDocumentationInputs(root, profile = "default") {
17426
17937
  return {
17427
17938
  model,
17428
17939
  decisions: loadArchitectureDecisionRecords(root),
17429
- existingFiles: loadArchitectureFilesForLayout(root, layout2)
17940
+ existingFiles: loadArchitectureFilesForLayout(root, layout2),
17941
+ docsLanguage: loadArchitectureDocumentationLanguage(root)
17430
17942
  };
17431
17943
  }
17432
17944
  function loadCapabilitySourceScaleSignals(root, model, sourceFiles) {
@@ -17514,6 +18026,28 @@ function loadArchitectureDocumentationProfile(root) {
17514
18026
  }
17515
18027
  return manifest.profile;
17516
18028
  }
18029
+ var ARCHITECTURE_DOCS_LANGUAGES = ["zh-Hant", "zh-Hans", "en"];
18030
+ var ARCHCONTEXT_MANIFEST_PATH = ".archcontext/manifest.yaml";
18031
+ function loadArchitectureDocumentationLanguage(root) {
18032
+ const path = resolve7(root, ARCHCONTEXT_MANIFEST_PATH);
18033
+ if (!existsSync5(path))
18034
+ return;
18035
+ let manifest;
18036
+ try {
18037
+ manifest = parseJsonOrStableYaml(readFileSync6(path, "utf8"), ARCHCONTEXT_MANIFEST_PATH);
18038
+ } catch {
18039
+ return;
18040
+ }
18041
+ const docs = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.docs : undefined;
18042
+ const language = docs && typeof docs === "object" && !Array.isArray(docs) ? docs.language : undefined;
18043
+ return language === undefined ? undefined : assertArchitectureDocsLanguage(language);
18044
+ }
18045
+ function assertArchitectureDocsLanguage(value) {
18046
+ if (!ARCHITECTURE_DOCS_LANGUAGES.includes(value)) {
18047
+ throw new Error(`architecture-docs-language-invalid: docs.language must be one of ${ARCHITECTURE_DOCS_LANGUAGES.join(", ")} (got ${JSON.stringify(value)})`);
18048
+ }
18049
+ return value;
18050
+ }
17517
18051
  function loadArchitectureDecisionRecords(root) {
17518
18052
  const dir = resolve7(root, "docs/adr");
17519
18053
  if (!existsSync5(dir))
@@ -17663,6 +18197,28 @@ function loadArchitectureProjectionManifestStamps(root) {
17663
18197
  ...isV3Provenance ? { provenance } : {}
17664
18198
  };
17665
18199
  }
18200
+ function semanticArchitectureNodes(model) {
18201
+ return model.nodes.map((entry) => ({
18202
+ id: entry.id,
18203
+ kind: entry.kind,
18204
+ name: entry.name,
18205
+ ...entry.parent ? { parent: entry.parent } : {},
18206
+ ...nativeNodeSource(entry) ? { source: nativeNodeSource(entry) } : {}
18207
+ }));
18208
+ }
18209
+ function checkArchitectureFlowSelectors(input) {
18210
+ const model = normalizeNativeModel(input.model);
18211
+ return {
18212
+ schemaVersion: ARCHITECTURE_FLOW_SELECTOR_CHECK_SCHEMA_VERSION,
18213
+ codeGraph: input.codeGraph,
18214
+ ...checkSemanticFlowSelectors({
18215
+ nodes: semanticArchitectureNodes(model),
18216
+ relations: model.relations,
18217
+ flows: model.flows ?? [],
18218
+ evidence: input.selectorEvidence
18219
+ })
18220
+ };
18221
+ }
17666
18222
  function normalizeNativeModel(model) {
17667
18223
  return {
17668
18224
  nodes: [...model.nodes].sort((a, b) => a.id.localeCompare(b.id)),
@@ -17903,8 +18459,8 @@ function readYamlObjects(dir) {
17903
18459
  function escapeMermaid(value) {
17904
18460
  return value.replace(/"/g, "'");
17905
18461
  }
17906
- function architectureDocumentationTargetDrafts(model, layout2) {
17907
- return layout2.targets.map((target) => target.type === "entity-summary" ? { ...target, skeleton: entitySummarySkeleton(model.nodes.find((node) => node.id === target.scope.id)) } : target);
18462
+ function architectureDocumentationTargetDrafts(model, layout2, docsLanguage) {
18463
+ return layout2.targets.map((target) => target.type === "entity-summary" ? { ...target, skeleton: entitySummarySkeleton(model.nodes.find((node) => node.id === target.scope.id), docsLanguage) } : target);
17908
18464
  }
17909
18465
  function projectionTarget(input) {
17910
18466
  return {
@@ -17932,6 +18488,7 @@ function renderTargetGeneratedBody(target, model, input) {
17932
18488
  return renderEntitySummary(node, model, {
17933
18489
  scaleSignal: input.scaleSignalsByNodeId.get(node.id),
17934
18490
  scale: input.scaleByNodeId.get(node.id),
18491
+ scaleByNodeId: input.scaleByNodeId,
17935
18492
  semanticCompilation: input.semanticCompilationsByNodeId.get(node.id)
17936
18493
  });
17937
18494
  }
@@ -17992,6 +18549,7 @@ function renderEntitySummary(node, model, input) {
17992
18549
  const incoming = model.relations.filter((relation) => relation.target === node.id);
17993
18550
  const outgoing = model.relations.filter((relation) => relation.source === node.id);
17994
18551
  const localContracts = contractFilesForNode(node);
18552
+ const components = model.nodes.filter((entry) => entry.parent === node.id && entry.kind === "component");
17995
18553
  const lines = [
17996
18554
  `> **狀態**:${node.status ? `\`${node.status}\`` : "未宣告(`status` 缺失)"}`,
17997
18555
  `> **Capability ID**:\`${node.id}\`(kind \`${node.kind}\`)`,
@@ -18013,6 +18571,7 @@ function renderEntitySummary(node, model, input) {
18013
18571
  "| --- | --- | --- |",
18014
18572
  ...entrypoints.flatMap((entry) => entry.symbols.map((symbol) => `| \`${entry.id}\` | \`${entry.path}#${symbol.name}\` | ${symbol.sinks.map((sink) => `\`${sink.id}\` → \`${sink.path}#${sink.symbol}\``).join("、")} |`))
18015
18573
  ] : ["- 未宣告 `source.entrypoints`,入口清單無法從架構模型推導。"],
18574
+ ...renderComponentEntrypoints(components),
18016
18575
  "",
18017
18576
  "### 1.3 規模信號",
18018
18577
  "",
@@ -18032,6 +18591,8 @@ function renderEntitySummary(node, model, input) {
18032
18591
  "入向關係:",
18033
18592
  "",
18034
18593
  ...incoming.length === 0 ? ["- 無。"] : incoming.map((relation) => `- \`${relation.kind}\` ← \`${relation.source}\` — ${relation.intent}`),
18594
+ ...renderComponentOutboundRelations(components, model.relations),
18595
+ ...renderComponentTable(components, input.scaleByNodeId),
18035
18596
  "",
18036
18597
  "## 2. P2:端到端數據流",
18037
18598
  "",
@@ -18041,6 +18602,56 @@ function renderEntitySummary(node, model, input) {
18041
18602
  `)}
18042
18603
  `;
18043
18604
  }
18605
+ function renderComponentEntrypoints(components) {
18606
+ if (components.length === 0)
18607
+ return [];
18608
+ const rows = components.flatMap((component) => (nativeNodeSource(component)?.entrypoints ?? []).flatMap((entry) => entry.symbols.map((symbol) => `| \`${component.id}\` | \`${entry.id}\` | \`${entry.path}#${symbol.name}\` | ${symbol.sinks.map((sink) => `\`${sink.id}\` → \`${sink.path}#${sink.symbol}\``).join("、")} |`)));
18609
+ return [
18610
+ "",
18611
+ "組件入口:",
18612
+ "",
18613
+ ...rows.length > 0 ? ["| 組件 | 宣告入口 | 錨點 | 職責 |", "| --- | --- | --- | --- |", ...rows] : ["- 組件均未宣告 `source.entrypoints`。"]
18614
+ ];
18615
+ }
18616
+ function renderComponentOutboundRelations(components, relations) {
18617
+ if (components.length === 0)
18618
+ return [];
18619
+ return [
18620
+ "",
18621
+ "組件出向關係:",
18622
+ "",
18623
+ ...components.flatMap((component) => {
18624
+ const outgoing = relations.filter((relation) => relation.source === component.id);
18625
+ return [
18626
+ `- \`${component.id}\`:`,
18627
+ ...outgoing.length === 0 ? [" - 無。"] : outgoing.map((relation) => ` - \`${relation.kind}\` → \`${relation.target}\` — ${relation.intent}`)
18628
+ ];
18629
+ })
18630
+ ];
18631
+ }
18632
+ function renderComponentTable(components, scaleByNodeId) {
18633
+ if (components.length === 0)
18634
+ return [];
18635
+ return [
18636
+ "",
18637
+ "### 1.5 組件結構",
18638
+ "",
18639
+ "| 組件 | 名稱 | 規模量級 | 宣告入口 |",
18640
+ "| --- | --- | --- | --- |",
18641
+ ...components.map((component) => {
18642
+ const source = nativeNodeSource(component);
18643
+ let scaleCell = "未宣告 `source.include`";
18644
+ if ((source?.include ?? []).length > 0) {
18645
+ const scale = scaleByNodeId.get(component.id);
18646
+ if (!scale)
18647
+ throw new Error(`architecture-docs-projection-scale-signal-missing: ${component.id}`);
18648
+ scaleCell = `\`${scaleMagnitudeBucketLabel(scale.fileCountBucket)}\` 個文件 / \`${scaleMagnitudeBucketLabel(scale.lineCountBucket)}\` 行`;
18649
+ }
18650
+ const entrypointIds = (source?.entrypoints ?? []).map((entry) => `\`${entry.id}\``);
18651
+ return `| \`${component.id}\` | ${component.name.replaceAll("|", "\\|")} | ${scaleCell} | ${entrypointIds.length > 0 ? entrypointIds.join("、") : "未宣告"} |`;
18652
+ })
18653
+ ];
18654
+ }
18044
18655
  function renderSemanticP1Section(compilation) {
18045
18656
  if (compilation.p1.status === "proven") {
18046
18657
  return [
@@ -18081,16 +18692,23 @@ function entitySummaryTitle(node) {
18081
18692
  const segments = node.id.replace(/^capability\./, "").split(".").filter((segment) => segment !== "");
18082
18693
  return segments.length > 0 ? segments.join("/") : node.id;
18083
18694
  }
18084
- function entitySummarySkeleton(node) {
18695
+ var ENTITY_SUMMARY_SKELETON_TEXT = {
18696
+ legacy: { titleSuffix: " 架構文檔", p3: "## 3. P3:設計決策與不變量", history: "## 4. 歷史決策記錄(append-only)" },
18697
+ "zh-Hant": { titleSuffix: " 架構文檔", p3: "## 3. P3:設計決策與不變量", history: "## 4. 歷史決策記錄(append-only)" },
18698
+ "zh-Hans": { titleSuffix: " 架构文档", p3: "## 3. P3:设计决策与不变量", history: "## 4. 历史决策记录(append-only)" },
18699
+ en: { titleSuffix: " Architecture", p3: "## 3. P3: Design Decisions and Invariants", history: "## 4. Decision History (append-only)" }
18700
+ };
18701
+ function entitySummarySkeleton(node, docsLanguage) {
18702
+ const text = ENTITY_SUMMARY_SKELETON_TEXT[docsLanguage ?? "legacy"];
18085
18703
  return {
18086
- prefix: `# ${entitySummaryTitle(node)} 架構文檔
18704
+ prefix: `# ${entitySummaryTitle(node)}${text.titleSuffix}
18087
18705
 
18088
18706
  `,
18089
18707
  suffix: [
18090
18708
  "",
18091
- "## 3. P3:設計決策與不變量",
18709
+ text.p3,
18092
18710
  "",
18093
- "## 4. 歷史決策記錄(append-only)",
18711
+ text.history,
18094
18712
  "",
18095
18713
  "## Optimization Backlog",
18096
18714
  ""
@@ -18098,6 +18716,9 @@ function entitySummarySkeleton(node) {
18098
18716
  `)
18099
18717
  };
18100
18718
  }
18719
+ function entitySummarySkeletonVariants(node) {
18720
+ return [undefined, ...ARCHITECTURE_DOCS_LANGUAGES].map((language) => entitySummarySkeleton(node, language));
18721
+ }
18101
18722
  function renderRelationSummary(relation, model) {
18102
18723
  const source = model.nodes.find((node) => node.id === relation.source);
18103
18724
  const target = model.nodes.find((node) => node.id === relation.target);
@@ -18263,7 +18884,7 @@ function architectureDocumentationProjectionDrift(input) {
18263
18884
  path: existing.path,
18264
18885
  targetId: region.targetId,
18265
18886
  actualDigest,
18266
- disposition: orphanHoldsOnlyIntactGeneratedRegion(existing.body, region.targetId, orphanSkeleton(committedEntityTargets, existing.path, region.targetId)) ? "delete" : "human-review"
18887
+ disposition: orphanHoldsOnlyIntactGeneratedRegion(existing.body, region.targetId, orphanSkeletons(committedEntityTargets, existing.path, region.targetId)) ? "delete" : "human-review"
18267
18888
  });
18268
18889
  }
18269
18890
  }
@@ -18299,17 +18920,17 @@ function committedEntitySummaryTargets(manifestBody) {
18299
18920
  }
18300
18921
  return targets;
18301
18922
  }
18302
- function orphanSkeleton(committedEntityTargets, path, targetId) {
18923
+ function orphanSkeletons(committedEntityTargets, path, targetId) {
18303
18924
  const committed = committedEntityTargets.get(targetId);
18304
- return committed && committed.path === path ? entitySummarySkeleton({ id: committed.nodeId }) : undefined;
18925
+ return committed && committed.path === path ? entitySummarySkeletonVariants({ id: committed.nodeId }) : [];
18305
18926
  }
18306
- function orphanHoldsOnlyIntactGeneratedRegion(body, targetId, skeleton) {
18927
+ function orphanHoldsOnlyIntactGeneratedRegion(body, targetId, skeletons) {
18307
18928
  const region = findGeneratedRegion(body, targetId);
18308
18929
  if (!region)
18309
18930
  return false;
18310
18931
  const before = body.slice(0, region.start);
18311
18932
  const after = body.slice(region.end);
18312
- const generatedOnly = `${before}${after}`.trim() === "" || skeleton !== undefined && before === skeleton.prefix && after === skeleton.suffix;
18933
+ const generatedOnly = `${before}${after}`.trim() === "" || skeletons.some((skeleton) => before === skeleton.prefix && after === skeleton.suffix);
18313
18934
  if (!generatedOnly)
18314
18935
  return false;
18315
18936
  const metadata = parseGeneratedRegionMetadata(region.startMarker);
@@ -18545,26 +19166,78 @@ function findAgentContextRegion(body, nodeId) {
18545
19166
  };
18546
19167
  }
18547
19168
  function resolveArchitectureOwnerForPath(nodes, path) {
18548
- const candidates = [];
18549
- for (const node of nodes) {
19169
+ return resolveArchitectureOwnersForPaths(nodes, [path])[0];
19170
+ }
19171
+ function resolveArchitectureOwnersForPaths(nodes, paths) {
19172
+ const declaring = nodes.flatMap((node) => {
18550
19173
  const source = nativeNodeSource(node);
18551
19174
  const include = source?.include ?? [];
18552
19175
  if (include.length === 0)
18553
- continue;
18554
- const excluded = (source?.exclude ?? []).some((pattern) => matchesGlob(path, pattern));
18555
- if (excluded)
18556
- continue;
18557
- const specificity = include.filter((pattern) => matchesGlob(path, pattern)).reduce((max, pattern) => Math.max(max, globLiteralPrefixLength(pattern)), -1);
18558
- if (specificity >= 0)
18559
- candidates.push({ node, specificity });
19176
+ return [];
19177
+ return [{
19178
+ node,
19179
+ include: include.map((pattern) => ({ regex: globToRegExp(pattern), specificity: globLiteralPrefixLength(pattern) })),
19180
+ exclude: (source?.exclude ?? []).map(globToRegExp)
19181
+ }];
19182
+ });
19183
+ const parents = new Map(nodes.map((node) => [node.id, node.parent]));
19184
+ const kinds = new Map(nodes.map((node) => [node.id, node.kind]));
19185
+ const owningCapabilityOf = (id) => {
19186
+ for (const ancestor of ancestorChain(id, parents))
19187
+ if (kinds.get(ancestor) === "capability")
19188
+ return ancestor;
19189
+ return null;
19190
+ };
19191
+ return paths.map((path) => {
19192
+ const candidates = [];
19193
+ for (const entry of declaring) {
19194
+ if (entry.exclude.some((regex) => regex.test(path)))
19195
+ continue;
19196
+ const specificity = entry.include.filter((pattern) => pattern.regex.test(path)).reduce((max, pattern) => Math.max(max, pattern.specificity), -1);
19197
+ if (specificity >= 0)
19198
+ candidates.push({ node: entry.node, specificity });
19199
+ }
19200
+ if (candidates.length === 0)
19201
+ return { status: "no-match", owningCapabilityId: null };
19202
+ const chainOwner = deepestOnOneParentChain(candidates.map((candidate) => candidate.node.id), parents);
19203
+ if (chainOwner !== undefined) {
19204
+ const node = candidates.find((candidate) => candidate.node.id === chainOwner).node;
19205
+ return { status: "matched", node, owningCapabilityId: owningCapabilityOf(node.id) };
19206
+ }
19207
+ const chains = new Map(candidates.map((candidate) => [candidate.node.id, ancestorChain(candidate.node.id, parents)]));
19208
+ const isStrictAncestorOf = (ancestor, descendant) => ancestor !== descendant && chains.get(descendant).has(ancestor) && !chains.get(ancestor).has(descendant);
19209
+ const leaves = candidates.filter((candidate) => !candidates.some((other) => isStrictAncestorOf(candidate.node.id, other.node.id)));
19210
+ const contenders = leaves.length > 0 ? leaves : candidates;
19211
+ const maxSpecificity = contenders.reduce((max, candidate) => Math.max(max, candidate.specificity), -1);
19212
+ const winners = contenders.filter((candidate) => candidate.specificity === maxSpecificity).map((candidate) => candidate.node);
19213
+ if (winners.length === 1)
19214
+ return { status: "matched", node: winners[0], owningCapabilityId: owningCapabilityOf(winners[0].id) };
19215
+ const capabilities = new Set(winners.map((node) => owningCapabilityOf(node.id)));
19216
+ return { status: "ambiguous", candidates: winners, owningCapabilityId: capabilities.size === 1 ? [...capabilities][0] : null };
19217
+ });
19218
+ }
19219
+ function deepestOnOneParentChain(candidateIds, parents) {
19220
+ if (candidateIds.length === 0)
19221
+ return;
19222
+ const depthOf = (id) => ancestorChain(id, parents).size - 1;
19223
+ const deepest = candidateIds.reduce((best, id) => {
19224
+ const bestDepth = depthOf(best);
19225
+ const depth = depthOf(id);
19226
+ if (depth !== bestDepth)
19227
+ return depth > bestDepth ? id : best;
19228
+ return id < best ? id : best;
19229
+ });
19230
+ const chain = ancestorChain(deepest, parents);
19231
+ return candidateIds.every((id) => chain.has(id)) ? deepest : undefined;
19232
+ }
19233
+ function ancestorChain(id, parents) {
19234
+ const chain = new Set;
19235
+ let current = id;
19236
+ while (current !== undefined && parents.has(current) && !chain.has(current)) {
19237
+ chain.add(current);
19238
+ current = parents.get(current);
18560
19239
  }
18561
- if (candidates.length === 0)
18562
- return { status: "no-match" };
18563
- const maxSpecificity = candidates.reduce((max, candidate) => Math.max(max, candidate.specificity), -1);
18564
- const winners = candidates.filter((candidate) => candidate.specificity === maxSpecificity).map((candidate) => candidate.node);
18565
- if (winners.length > 1)
18566
- return { status: "ambiguous", candidates: winners };
18567
- return { status: "matched", node: winners[0] };
19240
+ return chain;
18568
19241
  }
18569
19242
  function matchesGlob(path, pattern) {
18570
19243
  return globToRegExp(pattern).test(path);
@@ -18752,12 +19425,23 @@ function readTrackedSourceFiles(root, options = {}) {
18752
19425
  }
18753
19426
  function readWorkspacePackages(root) {
18754
19427
  const manifest = JSON.parse(readFileSync7(join4(root, "package.json"), "utf8"));
18755
- return (manifest.workspaces ?? []).map((directory) => {
19428
+ return (manifest.workspaces ?? []).flatMap((entry) => expandWorkspaceEntry(root, entry)).map((directory) => {
18756
19429
  const path = join4(root, ...directory.split("/"), "package.json");
18757
19430
  const packageManifest = JSON.parse(readFileSync7(path, "utf8"));
18758
19431
  return { name: packageManifest.name, root: directory, exports: packageManifest.exports ?? {} };
18759
19432
  }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
18760
19433
  }
19434
+ function expandWorkspaceEntry(root, entry) {
19435
+ if (!entry.includes("*"))
19436
+ return [entry];
19437
+ const base = entry.slice(0, -2);
19438
+ if (!entry.endsWith("/*") || base.includes("*"))
19439
+ throw new Error(`workspace-glob-unsupported: ${entry}`);
19440
+ const directory = join4(root, ...base.split("/"));
19441
+ if (!existsSync6(directory))
19442
+ return [];
19443
+ return readdirSync4(directory, { withFileTypes: true }).filter((child) => child.isDirectory() && existsSync6(join4(directory, child.name, "package.json"))).map((child) => `${base}/${child.name}`).sort();
19444
+ }
18761
19445
  function isDetachedHead(root) {
18762
19446
  return runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]).trim() === "HEAD";
18763
19447
  }
@@ -18991,7 +19675,8 @@ var CODEGRAPH_TELEMETRY_ENV = "DO_NOT_TRACK";
18991
19675
  var CODEGRAPH_TELEMETRY_DISABLED_VALUE = "1";
18992
19676
  var DEFAULT_CODEGRAPH_BINARY = "codegraph";
18993
19677
  var requireFromAdapter = createRequire2(import.meta.url);
18994
- var { CodeGraph } = requireFromAdapter(REQUIRED_CODEGRAPH_PACKAGE);
19678
+ var codeGraphSdk = requireFromAdapter(REQUIRED_CODEGRAPH_PACKAGE);
19679
+ var { CodeGraph } = codeGraphSdk;
18995
19680
  var CODEGRAPH_OUTPUT_MAX_BYTES = 32 * 1024 * 1024;
18996
19681
  var CODEGRAPH_QUERY_TIMEOUT_MS = 30000;
18997
19682
  function disableCodeGraphTelemetryByDefault(env = process.env) {
@@ -19502,7 +20187,8 @@ function architectureDeltaTask(git) {
19502
20187
  ...git.paths.flatMap((change) => [change.path, change.previousPath ?? ""])
19503
20188
  ].filter(Boolean)).join(" ");
19504
20189
  }
19505
- var CODEGRAPH_IMPORT_NODE_QUERY_LIMIT = 5000;
20190
+ var CODEGRAPH_IMPORT_NODE_READ_LIMIT = 50000;
20191
+ var CODEGRAPH_IMPORT_NODE_OUTPUT_BYTES = 4 * 1024;
19506
20192
  function prepareArchitectureDocumentationProjectionSnapshot(root, model, options = {}) {
19507
20193
  const baseHeadSha = readProjectionHeadSha(root);
19508
20194
  const sourceFiles = listProjectionSourceFiles(root);
@@ -19567,7 +20253,7 @@ function assertProjectionCodeFactsAvailable(root, prepared) {
19567
20253
  function prepareProjectionCodeFacts(root, model, options) {
19568
20254
  const invocation = options.binary ? codeGraphCliInvocation(options.binary, root, "") : packagedCodeGraphCliInvocation();
19569
20255
  const binaryPath = invocation.argsPrefix[0] ?? invocation.command;
19570
- const actualVersion = runProjectionCodeGraph(invocation, root, ["--version"], options.timeouts?.versionMs ?? 5000).trim();
20256
+ const actualVersion = runProjectionCodeGraph(invocation, root, ["--version"], options.timeouts?.versionMs ?? 1e4).trim();
19571
20257
  if (actualVersion !== REQUIRED_CODEGRAPH_VERSION) {
19572
20258
  throw new Error(`CodeGraph ${REQUIRED_CODEGRAPH_VERSION} required, got actual binary ${actualVersion || "unknown"}`);
19573
20259
  }
@@ -19608,7 +20294,9 @@ function prepareProjectionCodeFacts(root, model, options) {
19608
20294
  const inputs = loadCapabilityCodeGraphProjectionInputs(root, model, {
19609
20295
  ...options.sourceFiles ? { sourceFiles: options.sourceFiles } : {},
19610
20296
  binary: binaryPath,
19611
- importNodeLimit: options.importNodeLimit
20297
+ importNodeLimit: options.importNodeLimit,
20298
+ importNodeCount: statusImportNodeCount(postStatus),
20299
+ ...options.importGraphs === false ? { importGraphs: false } : {}
19612
20300
  });
19613
20301
  const finalStatus = readProjectionCodeGraphStatus(invocation, root, statusTimeout);
19614
20302
  assertProjectionCodeGraphStatus(finalStatus, root, "post-query");
@@ -19743,8 +20431,36 @@ function digestFile(path) {
19743
20431
  function codeGraphIndexAvailable(workspaceRoot) {
19744
20432
  return existsSync7(join5(workspaceRoot, ".codegraph"));
19745
20433
  }
20434
+ var NON_DECLARATION_KINDS = new Set(["file", "import", "export", "parameter"]);
20435
+ function openCodeGraphSymbolReader(workspaceRoot, openIndex = (root, options) => CodeGraph.openSync(root, options)) {
20436
+ if (!codeGraphIndexAvailable(workspaceRoot))
20437
+ return { availability: "unavailable", reasonCode: "index-missing" };
20438
+ disableCodeGraphTelemetryByDefault();
20439
+ let graph;
20440
+ try {
20441
+ graph = openIndex(workspaceRoot, { readOnly: true });
20442
+ } catch {
20443
+ return { availability: "unavailable", reasonCode: "index-open-failed" };
20444
+ }
20445
+ const declarations = (nodes) => nodes.filter((node) => !NON_DECLARATION_KINDS.has(node.kind)).map((node) => ({ name: node.name, qualifiedName: node.qualifiedName, startLine: node.startLine, endLine: node.endLine })).sort((left, right) => left.startLine - right.startLine || left.qualifiedName.localeCompare(right.qualifiedName));
20446
+ return {
20447
+ availability: "ready",
20448
+ reader: {
20449
+ supportsPath: (path) => codeGraphSdk.isLanguageSupported(codeGraphSdk.detectLanguage(path)),
20450
+ declarationsInSource: (path, content) => {
20451
+ try {
20452
+ return declarations(graph.extractFromSource(path, content).nodes);
20453
+ } catch {
20454
+ return;
20455
+ }
20456
+ },
20457
+ declarationsNamed: (name) => declarations(graph.getNodesByName(name).filter((node) => node.name === name)),
20458
+ close: () => graph.close()
20459
+ }
20460
+ };
20461
+ }
19746
20462
  function loadCapabilityCodeGraphProjectionInputs(root, model, options = {}) {
19747
- const declaresFootprint = model.nodes.some((node) => (nativeNodeSource(node)?.include ?? []).length > 0);
20463
+ const declaresFootprint = options.importGraphs !== false && model.nodes.some((node) => (nativeNodeSource(node)?.include ?? []).length > 0);
19748
20464
  const footprints = declaresFootprint ? loadCapabilitySourceFootprints(root, model, options.sourceFiles ?? listProjectionSourceFiles(root)) : [];
19749
20465
  const entrypointNodes = model.nodes.map((node) => ({ nodeId: node.id, entrypoints: nativeNodeSource(node)?.entrypoints ?? [] })).filter((entry) => entry.entrypoints.length > 0).sort((left, right) => left.nodeId.localeCompare(right.nodeId));
19750
20466
  if (footprints.length === 0 && entrypointNodes.length === 0)
@@ -19753,11 +20469,11 @@ function loadCapabilityCodeGraphProjectionInputs(root, model, options = {}) {
19753
20469
  return { importGraphs: [], selectorEvidence: [] };
19754
20470
  disableCodeGraphTelemetryByDefault();
19755
20471
  const binary = options.binary ?? DEFAULT_CODEGRAPH_BINARY;
19756
- const importGraphs = footprints.length > 0 ? capabilityImportGraphs(root, binary, footprints, options.importNodeLimit ?? CODEGRAPH_IMPORT_NODE_QUERY_LIMIT) : [];
20472
+ const importGraphs = footprints.length > 0 ? capabilityImportGraphs(root, binary, footprints, options.importNodeLimit ?? CODEGRAPH_IMPORT_NODE_READ_LIMIT, options.importNodeCount ?? null) : [];
19757
20473
  const selectorIndex = entrypointNodes.length > 0 ? options.selectorIndexFactory?.(root) ?? CodeGraph.openSync(root) : undefined;
19758
20474
  let selectorEvidence;
19759
20475
  try {
19760
- selectorEvidence = entrypointNodes.flatMap((entry) => entry.entrypoints.flatMap((entrypoint) => entrypoint.symbols.flatMap((source) => source.sinks.map((sink) => exactSelectorEvidence(selectorIndex, {
20476
+ selectorEvidence = entrypointNodes.flatMap((entry) => entry.entrypoints.flatMap((entrypoint) => entrypoint.symbols.flatMap((source) => source.sinks.map((sink) => exactSelectorEvidence(root, selectorIndex, {
19761
20477
  nodeId: entry.nodeId,
19762
20478
  entrypointId: entrypoint.id,
19763
20479
  sourcePath: entrypoint.path,
@@ -19772,10 +20488,21 @@ function loadCapabilityCodeGraphProjectionInputs(root, model, options = {}) {
19772
20488
  return { importGraphs, selectorEvidence };
19773
20489
  }
19774
20490
  function repositoryImportPairs(root, binary, limit, worktreeDigest) {
20491
+ if (!Number.isInteger(limit) || limit < 1)
20492
+ throw new Error(`import node limit must be a positive integer, got ${limit}`);
19775
20493
  if (!codeGraphIndexAvailable(root)) {
19776
- return { pairs: [], truncated: true, availability: "unavailable", indexedWorktreeDigest: null };
20494
+ return {
20495
+ pairs: [],
20496
+ truncated: true,
20497
+ availability: "unavailable",
20498
+ indexedWorktreeDigest: null,
20499
+ importNodeCount: null,
20500
+ importNodeLimit: limit,
20501
+ importNodeLimitExceeded: false
20502
+ };
19777
20503
  }
19778
- const nodes = codeGraphImportNodes(root, binary, limit);
20504
+ const status = readProjectionCodeGraphStatus(codeGraphCliInvocation(binary, root, ""), root, CODEGRAPH_QUERY_TIMEOUT_MS);
20505
+ const nodes = codeGraphImportPopulation(root, binary, limit, statusImportNodeCount(status));
19779
20506
  const pairs = new Map;
19780
20507
  for (const node of nodes.imports) {
19781
20508
  if (node.kind !== "import")
@@ -19787,18 +20514,43 @@ function repositoryImportPairs(root, binary, limit, worktreeDigest) {
19787
20514
  const firstLine = line === undefined ? previousLine : previousLine === undefined ? line : Math.min(line, previousLine);
19788
20515
  pairs.set(key, { from: node.filePath, specifier: node.name, to, ...firstLine === undefined ? {} : { line: firstLine } });
19789
20516
  }
19790
- const status = readProjectionCodeGraphStatus(codeGraphCliInvocation(binary, root, ""), root, CODEGRAPH_QUERY_TIMEOUT_MS);
19791
20517
  const pending = status.pendingChanges;
19792
20518
  const clean = status.initialized && pending !== undefined && pending.added === 0 && pending.modified === 0 && pending.removed === 0;
19793
20519
  return {
19794
20520
  pairs: [...pairs.keys()].sort().map((key) => pairs.get(key)),
19795
20521
  truncated: nodes.truncated,
19796
20522
  availability: clean ? "ready" : "unavailable",
19797
- indexedWorktreeDigest: clean ? worktreeDigest : null
20523
+ indexedWorktreeDigest: clean ? worktreeDigest : null,
20524
+ importNodeCount: nodes.count,
20525
+ importNodeLimit: limit,
20526
+ importNodeLimitExceeded: nodes.limitExceeded
19798
20527
  };
19799
20528
  }
19800
- function capabilityImportGraphs(root, binary, footprints, limit) {
19801
- const nodes = codeGraphImportNodes(root, binary, limit);
20529
+ function statusImportNodeCount(status) {
20530
+ if (!status.nodesByKind || typeof status.nodesByKind !== "object")
20531
+ return null;
20532
+ const count = status.nodesByKind.import;
20533
+ return Number.isInteger(count) && count >= 0 ? count : null;
20534
+ }
20535
+ function codeGraphImportPopulation(root, binary, limit, reportedCount) {
20536
+ const queryLimit = Math.min(reportedCount ?? limit, limit) + 1;
20537
+ const output = runCodeGraphCli(binary, root, ["query", "-p", root, "-j", "-l", String(queryLimit), "-k", "import", "import"], Math.max(CODEGRAPH_OUTPUT_MAX_BYTES, queryLimit * CODEGRAPH_IMPORT_NODE_OUTPUT_BYTES));
20538
+ const imports = JSON.parse(output).map(({ node }) => node);
20539
+ const limitExceeded = (reportedCount ?? imports.length) > limit;
20540
+ if (limitExceeded) {
20541
+ return { imports: imports.slice(0, limit), count: reportedCount, truncated: imports.length < limit, limitExceeded };
20542
+ }
20543
+ return {
20544
+ imports,
20545
+ count: reportedCount ?? imports.length,
20546
+ truncated: reportedCount !== null && imports.length !== reportedCount,
20547
+ limitExceeded
20548
+ };
20549
+ }
20550
+ function capabilityImportGraphs(root, binary, footprints, limit, reportedCount) {
20551
+ if (!Number.isInteger(limit) || limit < 1)
20552
+ throw new Error(`import node limit must be a positive integer, got ${limit}`);
20553
+ const nodes = codeGraphImportPopulation(root, binary, limit, reportedCount);
19802
20554
  const pairs = resolvedImportPairs(root, nodes.imports);
19803
20555
  return footprints.map((footprint) => {
19804
20556
  const scope = new Set(footprint.files);
@@ -19812,39 +20564,147 @@ function capabilityImportGraphs(root, binary, footprints, limit) {
19812
20564
  nodeId: footprint.nodeId,
19813
20565
  files: footprint.files,
19814
20566
  edges: [...edges.values()].sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to)),
19815
- truncated: nodes.truncated
20567
+ truncated: nodes.truncated,
20568
+ ...nodes.limitExceeded ? { importNodeLimitExceeded: true } : {}
19816
20569
  };
19817
20570
  });
19818
20571
  }
19819
- function codeGraphImportNodes(root, binary, limit) {
19820
- const output = runCodeGraphCli(binary, root, ["query", "-p", root, "-j", "-l", String(limit), "-k", "import", "import"]);
19821
- const parsed = JSON.parse(output);
19822
- return { imports: parsed.map(({ node }) => node), truncated: parsed.length >= limit };
19823
- }
19824
- function exactSelectorEvidence(index, selector) {
19825
- const sources = index.getNodesByName(selector.sourceSymbol).filter((node) => normalizedIndexPath(node.filePath) === normalizedIndexPath(selector.sourcePath));
19826
- const exactCalls = sources.flatMap((source) => index.getOutgoingEdges(source.id).filter((edge) => edge.kind === "calls").flatMap((edge) => {
20572
+ var RUNTIME_BUILTIN_CALL_NAMES = new Set([
20573
+ "basename",
20574
+ "dirname",
20575
+ "extname",
20576
+ "format",
20577
+ "isAbsolute",
20578
+ "join",
20579
+ "normalize",
20580
+ "parse",
20581
+ "relative",
20582
+ "resolve",
20583
+ "toNamespacedPath",
20584
+ "access",
20585
+ "accessSync",
20586
+ "appendFile",
20587
+ "appendFileSync",
20588
+ "chmod",
20589
+ "chmodSync",
20590
+ "close",
20591
+ "closeSync",
20592
+ "copyFile",
20593
+ "copyFileSync",
20594
+ "cp",
20595
+ "cpSync",
20596
+ "createReadStream",
20597
+ "createWriteStream",
20598
+ "exists",
20599
+ "existsSync",
20600
+ "fsync",
20601
+ "fsyncSync",
20602
+ "lstat",
20603
+ "lstatSync",
20604
+ "mkdir",
20605
+ "mkdirSync",
20606
+ "mkdtemp",
20607
+ "mkdtempSync",
20608
+ "open",
20609
+ "openSync",
20610
+ "opendir",
20611
+ "opendirSync",
20612
+ "read",
20613
+ "readFile",
20614
+ "readFileSync",
20615
+ "readSync",
20616
+ "readdir",
20617
+ "readdirSync",
20618
+ "readlink",
20619
+ "readlinkSync",
20620
+ "realpath",
20621
+ "realpathSync",
20622
+ "rename",
20623
+ "renameSync",
20624
+ "rm",
20625
+ "rmSync",
20626
+ "rmdir",
20627
+ "rmdirSync",
20628
+ "stat",
20629
+ "statSync",
20630
+ "symlink",
20631
+ "symlinkSync",
20632
+ "unlink",
20633
+ "unlinkSync",
20634
+ "watch",
20635
+ "write",
20636
+ "writeFile",
20637
+ "writeFileSync",
20638
+ "writeSync",
20639
+ "exec",
20640
+ "execFile",
20641
+ "execFileSync",
20642
+ "execSync",
20643
+ "fork",
20644
+ "spawn",
20645
+ "spawnSync",
20646
+ "homedir",
20647
+ "hostname",
20648
+ "platform",
20649
+ "tmpdir",
20650
+ "fileURLToPath",
20651
+ "pathToFileURL",
20652
+ "createHash",
20653
+ "randomBytes",
20654
+ "randomUUID",
20655
+ "clearInterval",
20656
+ "clearTimeout",
20657
+ "fetch",
20658
+ "queueMicrotask",
20659
+ "setInterval",
20660
+ "setTimeout",
20661
+ "structuredClone"
20662
+ ]);
20663
+ function exactSelectorEvidence(root, index, selector) {
20664
+ const sources = indexedSymbolsAt(index, selector.sourceSymbol, selector.sourcePath);
20665
+ const sinkDeclarations = indexedSymbolsAt(index, selector.sinkSymbol, selector.sinkPath);
20666
+ const exactEdges = sources.flatMap((source) => index.getOutgoingEdges(source.id).filter((edge) => edge.kind === "calls" || edge.kind === "references").flatMap((edge) => {
19827
20667
  const target = index.getNode(edge.target);
19828
20668
  return target && target.name === selector.sinkSymbol && normalizedIndexPath(target.filePath) === normalizedIndexPath(selector.sinkPath) ? [{ edge, target }] : [];
19829
20669
  }));
20670
+ const calls = exactEdges.filter(({ edge }) => edge.kind === "calls");
20671
+ const falseBuiltinCalls = calls.length > 0 && isFalseBuiltinCall(root, index, sources[0].filePath, selector);
20672
+ const exactCalls = falseBuiltinCalls ? [] : calls;
20673
+ const references = exactCalls.length === 0 ? exactEdges.filter(({ edge }) => edge.kind === "references") : [];
19830
20674
  const sinkIdentities = new Set(exactCalls.map(({ target }) => target.id));
19831
- const ambiguous = sources.length > 1 || sinkIdentities.size > 1;
19832
- const callSites = [...new Map(exactCalls.map(({ edge }) => {
19833
- const callSite = { path: selector.sourcePath, ...edge.line === undefined ? {} : { line: edge.line } };
19834
- return [`${callSite.path}\x00${callSite.line ?? ""}`, callSite];
19835
- })).values()].sort((left, right) => left.path.localeCompare(right.path) || (left.line ?? 0) - (right.line ?? 0));
20675
+ const ambiguous = sources.length > 1 || sinkDeclarations.length > 1 || sinkIdentities.size > 1;
19836
20676
  return {
19837
20677
  ...selector,
19838
20678
  matched: !ambiguous && sinkIdentities.size === 1,
19839
20679
  ambiguous,
19840
20680
  truncated: false,
19841
- callSites
20681
+ callSites: edgeSites(selector.sourcePath, exactCalls),
20682
+ ...falseBuiltinCalls ? { rejectedBuiltinCallSites: edgeSites(selector.sourcePath, calls) } : {},
20683
+ ...references.length > 0 ? { referenceSites: edgeSites(selector.sourcePath, references) } : {}
19842
20684
  };
19843
20685
  }
20686
+ function indexedSymbolsAt(index, name, path) {
20687
+ return index.getNodesByName(name).filter((node) => normalizedIndexPath(node.filePath) === normalizedIndexPath(path));
20688
+ }
20689
+ function edgeSites(sourcePath, edges) {
20690
+ return [...new Map(edges.map(({ edge }) => {
20691
+ const site = { path: sourcePath, ...edge.line === undefined ? {} : { line: edge.line } };
20692
+ return [`${site.path}\x00${site.line ?? ""}`, site];
20693
+ })).values()].sort((left, right) => left.path.localeCompare(right.path) || (left.line ?? 0) - (right.line ?? 0));
20694
+ }
20695
+ function isFalseBuiltinCall(root, index, indexedSourcePath, selector) {
20696
+ if (!RUNTIME_BUILTIN_CALL_NAMES.has(selector.sinkSymbol))
20697
+ return false;
20698
+ const sourcePath = normalizedIndexPath(selector.sourcePath);
20699
+ const sinkPath = normalizedIndexPath(selector.sinkPath);
20700
+ if (sourcePath === sinkPath)
20701
+ return false;
20702
+ return !index.getNodesInFile(indexedSourcePath).some((node) => node.kind === "import" && [node.name, node.name.replace(/\.[cm]?js$/, "")].some((specifier) => resolveImportTarget(root, sourcePath, specifier) === sinkPath));
20703
+ }
19844
20704
  function normalizedIndexPath(path) {
19845
20705
  return path.replaceAll("\\", "/").replace(/^\.\//, "");
19846
20706
  }
19847
- function runCodeGraphCli(binary, workspaceRoot, args) {
20707
+ function runCodeGraphCli(binary, workspaceRoot, args, maxBuffer = CODEGRAPH_OUTPUT_MAX_BYTES) {
19848
20708
  disableCodeGraphTelemetryByDefault();
19849
20709
  const invocation = codeGraphCliInvocation(binary, workspaceRoot);
19850
20710
  try {
@@ -19852,7 +20712,7 @@ function runCodeGraphCli(binary, workspaceRoot, args) {
19852
20712
  cwd: workspaceRoot,
19853
20713
  encoding: "utf8",
19854
20714
  stdio: ["ignore", "pipe", "pipe"],
19855
- maxBuffer: CODEGRAPH_OUTPUT_MAX_BYTES,
20715
+ maxBuffer,
19856
20716
  timeout: CODEGRAPH_QUERY_TIMEOUT_MS
19857
20717
  });
19858
20718
  } catch (error) {
@@ -20464,14 +21324,15 @@ function localEgressStatus(env = process.env, live = {}) {
20464
21324
  }
20465
21325
 
20466
21326
  // packages/surfaces/cli/src/main.ts
20467
- import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
20468
- import { accessSync as accessSync2, closeSync as closeSync10, constants as constants2, existsSync as existsSync27, mkdirSync as mkdirSync13, openSync as openSync10, readFileSync as readFileSync27, rmSync as rmSync16, statSync as statSync10 } from "fs";
20469
- import { dirname as dirname15, join as join18, resolve as resolve28 } from "path";
21327
+ import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
21328
+ import { accessSync as accessSync2, closeSync as closeSync10, constants as constants2, existsSync as existsSync28, mkdirSync as mkdirSync13, openSync as openSync10, readFileSync as readFileSync28, rmSync as rmSync15, statSync as statSync10 } from "fs";
21329
+ import { dirname as dirname15, join as join18, resolve as resolve29 } from "path";
20470
21330
  import { fileURLToPath as fileURLToPath2 } from "url";
20471
21331
 
20472
21332
  // packages/contracts/src/index.ts
20473
21333
  init_control_plane_routes();
20474
21334
  init_architecture();
21335
+ init_docs_anchors();
20475
21336
  init_github_governance();
20476
21337
  init_ledger();
20477
21338
  init_ports();
@@ -20597,7 +21458,7 @@ function assertAllowedArchContextPath2(root, relativePath, scope) {
20597
21458
  // packages/core/architecture-domain/src/index.ts
20598
21459
  init_src();
20599
21460
  import { createHash as createHash6 } from "node:crypto";
20600
- import { readdirSync as readdirSync4, readFileSync as readFileSync10, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
21461
+ import { readdirSync as readdirSync5, readFileSync as readFileSync10, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
20601
21462
  import { relative as relative7, resolve as resolve12, sep as sep6 } from "node:path";
20602
21463
  var DEFAULT_IGNORES2 = new Set([
20603
21464
  ".git",
@@ -20658,7 +21519,7 @@ function listRepoFiles2(root, ignore = DEFAULT_IGNORES2) {
20658
21519
  walk(resolve12(root));
20659
21520
  return out.sort();
20660
21521
  function walk(dir) {
20661
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
21522
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
20662
21523
  const absolute = resolve12(dir, entry.name);
20663
21524
  const rel = relative7(root, absolute).split(sep6).join("/");
20664
21525
  if (!rel || shouldIgnorePath2(entry.name, rel, ignore))
@@ -22792,7 +23653,7 @@ var UNTRUSTED_TOOL_ESCAPE_KEYS = new Set([
22792
23653
  ]);
22793
23654
 
22794
23655
  // packages/cloud/hardening/src/index.ts
22795
- import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
23656
+ import { readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
22796
23657
  import { resolve as resolve13 } from "node:path";
22797
23658
  init_src2();
22798
23659
  init_src();
@@ -22888,7 +23749,7 @@ function listFiles(root) {
22888
23749
  walk(".");
22889
23750
  return out;
22890
23751
  function walk(relativeDir) {
22891
- for (const entry of readdirSync5(resolve13(root, relativeDir), { withFileTypes: true })) {
23752
+ for (const entry of readdirSync6(resolve13(root, relativeDir), { withFileTypes: true })) {
22892
23753
  if ([".git", "node_modules", "coverage"].includes(entry.name))
22893
23754
  continue;
22894
23755
  const child = relativeDir === "." ? entry.name : `${relativeDir}/${entry.name}`;
@@ -22905,6 +23766,11 @@ function listFiles(root) {
22905
23766
  import { execFileSync as execFileSync4 } from "node:child_process";
22906
23767
  import { closeSync as closeSync5, constants, existsSync as existsSync10, fstatSync as fstatSync2, openSync as openSync5, readFileSync as readFileSync12, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "node:fs";
22907
23768
  import { join as join6 } from "node:path";
23769
+ import { setTimeout as sleep } from "node:timers/promises";
23770
+ var WINDOWS_CREATE_TIMEOUT_MS = 60000;
23771
+ var WINDOWS_READ_TIMEOUT_MS = 1e4;
23772
+ var REMOVE_HANDLE_RELEASE_MARGIN_MS = 2000;
23773
+ var REMOVE_RETRY_DELAY_MS = 25;
22908
23774
  var WINDOWS_CONTROL_FILE_SCRIPT = String.raw`
22909
23775
  $ErrorActionPreference = 'Stop'
22910
23776
  # A pwsh -> Node/Bun -> powershell.exe launch inherits incompatible PS7 module paths.
@@ -22982,7 +23848,7 @@ function windowsControlFile(operation, path, body) {
22982
23848
  encoding: "utf8",
22983
23849
  windowsHide: true,
22984
23850
  stdio: ["pipe", "pipe", "pipe"],
22985
- timeout: operation === "create" ? 30000 : 1e4,
23851
+ timeout: operation === "create" ? WINDOWS_CREATE_TIMEOUT_MS : WINDOWS_READ_TIMEOUT_MS,
22986
23852
  maxBuffer: 1024 * 1024
22987
23853
  });
22988
23854
  } catch (error) {
@@ -23041,6 +23907,21 @@ function readPrivateControlFile(path) {
23041
23907
  return;
23042
23908
  }
23043
23909
  }
23910
+ async function removePrivateControlFile(path) {
23911
+ const deadline = Date.now() + WINDOWS_READ_TIMEOUT_MS + REMOVE_HANDLE_RELEASE_MARGIN_MS;
23912
+ for (;; ) {
23913
+ try {
23914
+ rmSync6(path, { force: true });
23915
+ return;
23916
+ } catch (error) {
23917
+ const code = error.code;
23918
+ const sharingFailure = process.platform === "win32" && (code === "EBUSY" || code === "EPERM" || code === "EACCES");
23919
+ if (!sharingFailure || Date.now() >= deadline)
23920
+ throw error;
23921
+ }
23922
+ await sleep(REMOVE_RETRY_DELAY_MS);
23923
+ }
23924
+ }
23044
23925
 
23045
23926
  // packages/local-runtime/process-liveness/src/index.ts
23046
23927
  function isProcessAlive(pid) {
@@ -25871,7 +26752,7 @@ function isIgnorableDirectoryFsyncError2(error) {
25871
26752
 
25872
26753
  // packages/local-runtime/git-adapter/src/index.ts
25873
26754
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
25874
- import { existsSync as existsSync12, lstatSync as lstatSync6, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync8 } from "node:fs";
26755
+ import { existsSync as existsSync12, lstatSync as lstatSync6, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, rmSync as rmSync8 } from "node:fs";
25875
26756
  import { dirname as dirname8, join as join8, resolve as resolve15 } from "node:path";
25876
26757
  init_src();
25877
26758
  var GIT_CAT_FILE_MAX_BYTES2 = 512 * 1024 * 1024;
@@ -27427,7 +28308,7 @@ function unresolvedChangeSetJournalSummary(journals) {
27427
28308
  }
27428
28309
 
27429
28310
  // packages/local-runtime/runtime-daemon/src/external-documentation.ts
27430
- import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync7 } from "node:fs";
28311
+ import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync17, statSync as statSync7 } from "node:fs";
27431
28312
  import { resolve as resolve18 } from "node:path";
27432
28313
 
27433
28314
  // packages/core/context-compiler/src/index.ts
@@ -27435,7 +28316,7 @@ init_src();
27435
28316
 
27436
28317
  // packages/core/practice-catalog/src/index.ts
27437
28318
  init_src();
27438
- import { existsSync as existsSync13, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync15, realpathSync as realpathSync8 } from "node:fs";
28319
+ import { existsSync as existsSync13, lstatSync as lstatSync7, readdirSync as readdirSync8, readFileSync as readFileSync15, realpathSync as realpathSync8 } from "node:fs";
27439
28320
  import { dirname as dirname9, relative as relative9, resolve as resolve16, sep as sep7 } from "node:path";
27440
28321
  import { fileURLToPath } from "node:url";
27441
28322
  var PRACTICE_CATALOG_VERSION = "2026.06.0";
@@ -28046,7 +28927,7 @@ function listDataFiles(dir, allowedRoot, errors) {
28046
28927
  return;
28047
28928
  }
28048
28929
  if (stat.isDirectory()) {
28049
- for (const entry of readdirSync6(current, { withFileTypes: true }))
28930
+ for (const entry of readdirSync8(current, { withFileTypes: true }))
28050
28931
  walk(resolve16(current, entry.name));
28051
28932
  return;
28052
28933
  }
@@ -28660,7 +29541,7 @@ function checkResult(input, result) {
28660
29541
  }
28661
29542
  // packages/core/practice-engine/src/enforcement.ts
28662
29543
  init_src();
28663
- import { existsSync as existsSync14, lstatSync as lstatSync8, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "node:fs";
29544
+ import { existsSync as existsSync14, lstatSync as lstatSync8, readdirSync as readdirSync9, readFileSync as readFileSync16 } from "node:fs";
28664
29545
  import { basename as basename4, join as join9, relative as relative10, resolve as resolve17, sep as sep8 } from "node:path";
28665
29546
  var ENFORCEMENT_RANK = { advisory: 0, checkpoint: 1, complete: 2 };
28666
29547
  var POLICY_MODES = new Set(["advisory", "active", "fail-open", "fail-closed"]);
@@ -28702,7 +29583,7 @@ function loadPracticeWaivers(root) {
28702
29583
  throw new Error("practice-waiver-path-not-directory");
28703
29584
  const waivers = [];
28704
29585
  const ownerRegistry = loadPracticeWaiverOwnerRegistry(root);
28705
- for (const entry of readdirSync7(dir, { withFileTypes: true })) {
29586
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
28706
29587
  if (!entry.isFile() || !/\.(?:ya?ml|json)$/.test(entry.name))
28707
29588
  continue;
28708
29589
  const path = join9(dir, entry.name);
@@ -29005,7 +29886,7 @@ function parseJsonYamlFile(path) {
29005
29886
  function collectFiles(root, relativeDir) {
29006
29887
  const dir = resolve17(root, relativeDir);
29007
29888
  const out = [];
29008
- for (const entry of readdirSync7(dir, { withFileTypes: true })) {
29889
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
29009
29890
  const child = `${relativeDir}/${entry.name}`;
29010
29891
  if (entry.isDirectory())
29011
29892
  out.push(...collectFiles(root, child));
@@ -30571,7 +31452,7 @@ function expandWorkspacePackageJson(root, pattern) {
30571
31452
  const base = resolve18(root, pattern.slice(0, -2));
30572
31453
  if (!existsSync15(base) || !statSync7(base).isDirectory())
30573
31454
  return [];
30574
- return readdirSync8(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve18(base, entry.name, "package.json")).filter((path) => existsSync15(path));
31455
+ return readdirSync10(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve18(base, entry.name, "package.json")).filter((path) => existsSync15(path));
30575
31456
  }
30576
31457
  function exactVersionFromManifest(manifest, packageName) {
30577
31458
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
@@ -31349,7 +32230,7 @@ function parsePracticeCheckpointBaselineState(state, repositoryId, taskSessionId
31349
32230
  // packages/local-runtime/runtime-daemon/src/developer-review-run.ts
31350
32231
  init_src();
31351
32232
  import { randomBytes as randomBytes2 } from "node:crypto";
31352
- import { chmodSync as chmodSync6, existsSync as existsSync18, lstatSync as lstatSync9, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readdirSync as readdirSync9, readFileSync as readFileSync18, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "node:fs";
32233
+ import { chmodSync as chmodSync6, existsSync as existsSync18, lstatSync as lstatSync9, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, readdirSync as readdirSync11, readFileSync as readFileSync18, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "node:fs";
31353
32234
  import { tmpdir as tmpdir2 } from "node:os";
31354
32235
  import { basename as basename5, dirname as dirname10, join as join11, resolve as resolve20, sep as sep9 } from "node:path";
31355
32236
  // packages/local-runtime/runtime-daemon/src/rpc-protocol.ts
@@ -31911,6 +32792,17 @@ function acquireDaemonLock(lockPath, root) {
31911
32792
  throw new Error(`archctxd already running for ${root}; lock=${lockPath}`);
31912
32793
  }
31913
32794
  }
32795
+ function liveDaemonLockOwnerPid(root = process.cwd()) {
32796
+ try {
32797
+ const body = readPrivateControlFile(defaultDaemonLockPath(root));
32798
+ if (body === undefined)
32799
+ return;
32800
+ const pid = JSON.parse(body).pid;
32801
+ return typeof pid === "number" && pid > 0 && isProcessAlive(pid) ? pid : undefined;
32802
+ } catch {
32803
+ return;
32804
+ }
32805
+ }
31914
32806
  function isValidRuntimeRpcConnection(value) {
31915
32807
  return value.schemaVersion === RUNTIME_RPC_VERSION && value.protocol === "http-loopback" && value.version === 1 && typeof value.url === "string" && value.url.startsWith("http://127.0.0.1:") && typeof value.token === "string" && value.token.length > 0 && typeof value.pid === "number" && typeof value.connectionPath === "string" && typeof value.lockPath === "string";
31916
32808
  }
@@ -32120,7 +33012,7 @@ class DeveloperReviewRunService {
32120
33012
  };
32121
33013
  if (!existsSync18(stateDir))
32122
33014
  return recovery;
32123
- for (const entry of readdirSync9(stateDir).sort()) {
33015
+ for (const entry of readdirSync11(stateDir).sort()) {
32124
33016
  if (!entry.endsWith(".json"))
32125
33017
  continue;
32126
33018
  const manifestPath = join11(stateDir, entry);
@@ -32148,7 +33040,7 @@ class DeveloperReviewRunService {
32148
33040
  recovery.rejected.push(`${entry}: ${error instanceof Error ? error.message : String(error)}`);
32149
33041
  }
32150
33042
  }
32151
- for (const entry of readdirSync9(stateDir).sort()) {
33043
+ for (const entry of readdirSync11(stateDir).sort()) {
32152
33044
  if (!entry.endsWith(".lock"))
32153
33045
  continue;
32154
33046
  const lockPath = join11(stateDir, entry);
@@ -33344,7 +34236,7 @@ init_src();
33344
34236
 
33345
34237
  // packages/local-runtime/model-store-yaml/src/index.ts
33346
34238
  init_src();
33347
- import { existsSync as existsSync19, lstatSync as lstatSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync10, readFileSync as readFileSync19, rmSync as rmSync11 } from "node:fs";
34239
+ import { existsSync as existsSync19, lstatSync as lstatSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync12, readFileSync as readFileSync19, rmSync as rmSync11 } from "node:fs";
33348
34240
  import { dirname as dirname11, resolve as resolve21 } from "node:path";
33349
34241
  var GENERATED_ARCHITECTURE_PATH = ".archcontext/generated/ARCHITECTURE.md";
33350
34242
  var GENERATED_ARCHITECTURE_BODY = [
@@ -33569,7 +34461,7 @@ function collectGeneratedProjectionFiles(root) {
33569
34461
  const generatedRoot = ".archcontext/generated";
33570
34462
  if (!existsSync19(resolve21(root, generatedRoot)))
33571
34463
  return [];
33572
- const walk = (relativeDir) => readdirSync10(resolve21(root, relativeDir), { withFileTypes: true }).flatMap((entry) => {
34464
+ const walk = (relativeDir) => readdirSync12(resolve21(root, relativeDir), { withFileTypes: true }).flatMap((entry) => {
33573
34465
  const child = `${relativeDir}/${entry.name}`;
33574
34466
  if (entry.isDirectory())
33575
34467
  return walk(child);
@@ -33664,7 +34556,7 @@ function collectArchContextFiles(root, entries) {
33664
34556
  function walk(relativeDir) {
33665
34557
  const out = [];
33666
34558
  const dir = resolve21(root, relativeDir);
33667
- for (const entry of readdirSync10(dir, { withFileTypes: true })) {
34559
+ for (const entry of readdirSync12(dir, { withFileTypes: true })) {
33668
34560
  const child = `${relativeDir}/${entry.name}`;
33669
34561
  if (entry.isDirectory())
33670
34562
  out.push(...walk(child));
@@ -33681,7 +34573,7 @@ function isCollectableModelFile(path) {
33681
34573
  }
33682
34574
  function readdirOrFile(path) {
33683
34575
  try {
33684
- readdirSync10(path);
34576
+ readdirSync12(path);
33685
34577
  return "directory";
33686
34578
  } catch {
33687
34579
  return "file";
@@ -41814,6 +42706,9 @@ function runNodeInvestigationTransport(input, options) {
41814
42706
  child.once("exit", (code) => {
41815
42707
  settleResolve(unwrapClaudeCodeEnvelope(code ?? 1, stdout, stderr));
41816
42708
  });
42709
+ child.stdin?.on("error", () => {
42710
+ return;
42711
+ });
41817
42712
  child.stdin?.end(input.stdin);
41818
42713
  });
41819
42714
  }
@@ -42854,6 +43749,7 @@ function buildArchitectureDocsProjection(_host, root, generatedAt, profile = "de
42854
43749
  profile,
42855
43750
  decisions: loaded.decisions,
42856
43751
  existingFiles: loaded.existingFiles,
43752
+ docsLanguage: loaded.docsLanguage,
42857
43753
  sourceFootprints,
42858
43754
  sourceScaleSignals: loadCapabilitySourceScaleSignals(root, loaded.model, codeGraphInputs.sourceFiles),
42859
43755
  importGraphs: codeGraphInputs.importGraphs,
@@ -43954,6 +44850,7 @@ function buildRuntimeProjectionRecoveryFixedPoint(root) {
43954
44850
  profile: REPO_HARNESS_PROJECTION_PROFILE,
43955
44851
  decisions: loaded.decisions,
43956
44852
  existingFiles: loaded.existingFiles,
44853
+ docsLanguage: loaded.docsLanguage,
43957
44854
  sourceFootprints: loadCapabilitySourceFootprintDigests(root, loaded.model, codeGraphInputs.sourceFiles),
43958
44855
  sourceScaleSignals: loadCapabilitySourceScaleSignals(root, loaded.model, codeGraphInputs.sourceFiles),
43959
44856
  importGraphs: codeGraphInputs.importGraphs,
@@ -44068,12 +44965,123 @@ function runtimeProjectionOwnedOutputDigest(projection) {
44068
44965
  files: projection.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
44069
44966
  });
44070
44967
  }
44071
- // packages/core/recommendation-engine/src/index.ts
44072
- init_src();
44073
-
44074
- // packages/core/refactor-assessment/src/index.ts
44968
+ // packages/local-runtime/runtime-daemon/src/docs-anchors.ts
44075
44969
  init_src();
44076
-
44970
+ import { spawnSync as spawnSync3 } from "node:child_process";
44971
+ import { readFileSync as readFileSync24 } from "node:fs";
44972
+ import { resolve as resolve25 } from "node:path";
44973
+ function runDocsAnchorsCommand(cwd, options = {}) {
44974
+ let opened;
44975
+ try {
44976
+ const root = findRepositoryRoot(cwd);
44977
+ const profile = options.profile ?? committedArchitectureDocsProfile(root);
44978
+ const model = loadNativeModelFromArchContext(root);
44979
+ const layout = resolveArchitectureDocumentationLayout({ nodes: model.nodes, relations: model.relations, profile });
44980
+ const documents = new Map(loadArchitectureFilesForLayout(root, layout).map((file) => [file.path, file.body]));
44981
+ const files = listProjectionSourceFiles(root);
44982
+ const footprints = new Map(loadCapabilitySourceFootprints(root, model, files).map((footprint) => [footprint.nodeId, footprint.files]));
44983
+ const contents = fileContentReader(root);
44984
+ const tree = gitVisibleTree(root, files, contents);
44985
+ opened = openCodeGraphSymbolReader(root);
44986
+ const codeGraph = opened.availability === "ready" ? { availability: "ready", symbols: proseAnchorSymbols(opened.reader, files, contents) } : { availability: "unavailable", reasonCode: opened.reasonCode };
44987
+ const report = checkProseAnchors({
44988
+ profile,
44989
+ codeGraph,
44990
+ tree,
44991
+ nodes: model.nodes.map((node) => {
44992
+ const documentPath = layout.entityPathByNodeId.get(node.id);
44993
+ const body = documentPath === undefined ? undefined : documents.get(documentPath);
44994
+ const responsibilities = Array.isArray(node.responsibilities) ? node.responsibilities.filter((entry) => typeof entry === "string") : undefined;
44995
+ return {
44996
+ nodeId: node.id,
44997
+ ...typeof node.summary === "string" ? { summary: node.summary } : {},
44998
+ ...responsibilities ? { responsibilities } : {},
44999
+ ...footprints.has(node.id) ? { footprint: footprints.get(node.id) } : {},
45000
+ ...documentPath !== undefined && body !== undefined ? { document: { path: documentPath, body } } : {}
45001
+ };
45002
+ })
45003
+ });
45004
+ const issues = docsAnchorsReportIssues(report);
45005
+ if (issues.length > 0)
45006
+ throw new Error(`docs-anchors-report-invalid: ${issues.join("; ")}`);
45007
+ return okEnvelope("docs.anchors", report);
45008
+ } catch (error) {
45009
+ return projectionFailureEnvelope("docs.anchors", error);
45010
+ } finally {
45011
+ if (opened?.availability === "ready")
45012
+ opened.reader.close();
45013
+ }
45014
+ }
45015
+ function fileContentReader(root) {
45016
+ const cache = new Map;
45017
+ return (path) => {
45018
+ let content = cache.get(path);
45019
+ if (content === undefined) {
45020
+ content = readFileSync24(resolve25(root, path), "utf8").replace(/\r\n/g, `
45021
+ `);
45022
+ cache.set(path, content);
45023
+ }
45024
+ return content;
45025
+ };
45026
+ }
45027
+ function gitVisibleTree(root, files, contents) {
45028
+ const fileSet = new Set(files);
45029
+ const directories = new Set;
45030
+ const topLevel = new Set;
45031
+ const ignored = new Map;
45032
+ for (const file of files) {
45033
+ const segments = file.split("/");
45034
+ topLevel.add(segments[0]);
45035
+ for (let index = 1;index < segments.length; index += 1)
45036
+ directories.add(segments.slice(0, index).join("/"));
45037
+ }
45038
+ return {
45039
+ kind: (path) => fileSet.has(path) ? "file" : directories.has(path) ? "directory" : undefined,
45040
+ hasTopLevelEntry: (segment) => topLevel.has(segment),
45041
+ filesEndingWith: (suffix) => files.filter((file) => file === suffix || file.endsWith(`/${suffix}`)),
45042
+ lineCount: (path) => {
45043
+ const content = contents(path);
45044
+ if (content === "")
45045
+ return 0;
45046
+ return content.split(`
45047
+ `).length - (content.endsWith(`
45048
+ `) ? 1 : 0);
45049
+ },
45050
+ isIgnored: (path) => {
45051
+ let found = ignored.get(path);
45052
+ if (found === undefined) {
45053
+ found = spawnSync3("git", ["check-ignore", "--", path, `${path}/`], { cwd: root, stdio: "ignore" }).status === 0;
45054
+ ignored.set(path, found);
45055
+ }
45056
+ return found;
45057
+ }
45058
+ };
45059
+ }
45060
+ var SOURCE_SEARCH_EXCLUDED_PREFIXES = [".archcontext/"];
45061
+ function proseAnchorSymbols(reader, files, contents) {
45062
+ const sourceFiles = files.filter((file) => !SOURCE_SEARCH_EXCLUDED_PREFIXES.some((prefix) => file.startsWith(prefix)) && reader.supportsPath(file));
45063
+ const appears = new Map;
45064
+ const parsed = new Map;
45065
+ const declarationsIn = (path) => {
45066
+ if (!parsed.has(path))
45067
+ parsed.set(path, reader.supportsPath(path) ? reader.declarationsInSource(path, contents(path)) : undefined);
45068
+ return parsed.get(path);
45069
+ };
45070
+ return {
45071
+ supportsPath: (path) => declarationsIn(path) !== undefined,
45072
+ declarationsInFile: (path, name) => (declarationsIn(path) ?? []).filter((declaration) => declaration.name === name),
45073
+ declarationsNamed: (name) => reader.declarationsNamed(name),
45074
+ appearsInSource: (name) => {
45075
+ let found = appears.get(name);
45076
+ if (found === undefined) {
45077
+ const word = new RegExp(`(?<![A-Za-z0-9_$])${name.replace(/[$]/g, "\\$")}(?![A-Za-z0-9_$])`);
45078
+ found = sourceFiles.some((file) => word.test(contents(file)));
45079
+ appears.set(name, found);
45080
+ }
45081
+ return found;
45082
+ }
45083
+ };
45084
+ }
44077
45085
  // packages/core/module-statistics/src/index.ts
44078
45086
  init_src();
44079
45087
 
@@ -44221,10 +45229,10 @@ function tarjanComponents(nodeIds, successors) {
44221
45229
  }
44222
45230
 
44223
45231
  // packages/core/module-statistics/src/ownership.ts
45232
+ init_src();
44224
45233
  function resolveOwnership(nodes, paths) {
44225
45234
  const declaring = nodes.filter((node) => (nativeNodeSource(node)?.include ?? []).length > 0);
44226
45235
  const parents = new Map(nodes.map((node) => [node.id, node.parent]));
44227
- const depths = new Map(nodes.map((node) => [node.id, depthOf(node.id, parents)]));
44228
45236
  const roots = declaredSourceRoots(declaring);
44229
45237
  const excludes = declaring.flatMap((node) => nativeNodeSource(node)?.exclude ?? []);
44230
45238
  const byPath = new Map;
@@ -44243,7 +45251,7 @@ function resolveOwnership(nodes, paths) {
44243
45251
  byPath.set(path, { owners: [], ambiguous: false });
44244
45252
  continue;
44245
45253
  }
44246
- const resolution = resolveCandidates(candidates, parents, depths);
45254
+ const resolution = resolveCandidates(candidates, parents);
44247
45255
  ownedFileCount += 1;
44248
45256
  if (resolution.ambiguous)
44249
45257
  multiplyOwnedFileCount += 1;
@@ -44290,38 +45298,90 @@ function claims(node, path) {
44290
45298
  return false;
44291
45299
  return include.some((pattern) => matchesGlob(path, pattern));
44292
45300
  }
44293
- function resolveCandidates(candidates, parents, depths) {
44294
- if (candidates.length === 1)
44295
- return { owners: [candidates[0].id], ambiguous: false };
44296
- const deepest = candidates.reduce((best, node) => {
44297
- const bestDepth = depths.get(best.id) ?? 0;
44298
- const nodeDepth = depths.get(node.id) ?? 0;
44299
- if (nodeDepth !== bestDepth)
44300
- return nodeDepth > bestDepth ? node : best;
44301
- return node.id < best.id ? node : best;
44302
- });
44303
- const chain = ancestorChain(deepest.id, parents);
44304
- if (candidates.every((node) => chain.has(node.id)))
44305
- return { owners: [deepest.id], ambiguous: false };
44306
- return { owners: candidates.map((node) => node.id).sort(), ambiguous: true };
44307
- }
44308
- function ancestorChain(id, parents) {
44309
- const chain = new Set;
44310
- let current = id;
44311
- while (current !== undefined && parents.has(current) && !chain.has(current)) {
44312
- chain.add(current);
44313
- current = parents.get(current);
44314
- }
44315
- return chain;
45301
+ function resolveCandidates(candidates, parents) {
45302
+ const ids = candidates.map((node) => node.id);
45303
+ const owner = deepestOnOneParentChain(ids, parents);
45304
+ if (owner !== undefined)
45305
+ return { owners: [owner], ambiguous: false };
45306
+ return { owners: ids.sort(), ambiguous: true };
45307
+ }
45308
+ function ownershipDelta(before, after, paths, pathLimit = CHANGESET_OWNERSHIP_DELTA_PATH_LIMIT) {
45309
+ const changedNodeIds = ownershipRelevantChanges(before, after);
45310
+ const beforeIndex = resolveOwnership(before, paths);
45311
+ const afterIndex = changedNodeIds.length === 0 ? beforeIndex : resolveOwnership(after, paths);
45312
+ const categories = {
45313
+ becameOwned: [],
45314
+ becameContested: [],
45315
+ ownerChanged: [],
45316
+ becameUnowned: []
45317
+ };
45318
+ for (const path of [...paths].sort()) {
45319
+ const was = beforeIndex.byPath.get(path) ?? { owners: [], ambiguous: false };
45320
+ const now = afterIndex.byPath.get(path) ?? { owners: [], ambiguous: false };
45321
+ if (sameOwners(was.owners, now.owners))
45322
+ continue;
45323
+ const file = { path, before: [...was.owners].sort(), after: [...now.owners].sort() };
45324
+ if (now.ambiguous && !was.ambiguous)
45325
+ categories.becameContested.push(file);
45326
+ else if (was.owners.length === 0)
45327
+ categories.becameOwned.push(file);
45328
+ else if (now.owners.length === 0)
45329
+ categories.becameUnowned.push(file);
45330
+ else
45331
+ categories.ownerChanged.push(file);
45332
+ }
45333
+ const summary = (index) => ({
45334
+ ownedFileCount: index.ownedFileCount,
45335
+ multiplyOwnedFileCount: index.multiplyOwnedFileCount,
45336
+ unownedFileCount: index.unownedFileCount
45337
+ });
45338
+ const bounded = (files) => {
45339
+ const listed = files.slice(0, pathLimit);
45340
+ return { count: files.length, files: listed, omittedCount: files.length - listed.length };
45341
+ };
45342
+ return {
45343
+ schemaVersion: CHANGESET_OWNERSHIP_DELTA_SCHEMA_VERSION,
45344
+ status: "computed",
45345
+ ownershipRule: "parent-chain",
45346
+ fileUniverse: "head-tracked-files",
45347
+ trackedFileCount: paths.length,
45348
+ changedNodeIds,
45349
+ pathLimit,
45350
+ before: summary(beforeIndex),
45351
+ after: summary(afterIndex),
45352
+ becameOwned: bounded(categories.becameOwned),
45353
+ becameContested: bounded(categories.becameContested),
45354
+ ownerChanged: bounded(categories.ownerChanged),
45355
+ becameUnowned: bounded(categories.becameUnowned)
45356
+ };
45357
+ }
45358
+ function ownershipRelevantChanges(before, after) {
45359
+ const key = (node) => {
45360
+ if (node === undefined)
45361
+ return "absent";
45362
+ const source = nativeNodeSource(node);
45363
+ return digestJson({
45364
+ include: source?.include ?? [],
45365
+ exclude: source?.exclude ?? [],
45366
+ parent: node.parent ?? null
45367
+ });
45368
+ };
45369
+ const beforeById = new Map(before.map((node) => [node.id, node]));
45370
+ const afterById = new Map(after.map((node) => [node.id, node]));
45371
+ const ids = new Set([...beforeById.keys(), ...afterById.keys()]);
45372
+ return [...ids].filter((id) => key(beforeById.get(id)) !== key(afterById.get(id))).sort();
44316
45373
  }
44317
- function depthOf(id, parents) {
44318
- return ancestorChain(id, parents).size - 1;
45374
+ function sameOwners(left, right) {
45375
+ if (left.length !== right.length)
45376
+ return false;
45377
+ const sortedRight = [...right].sort();
45378
+ return [...left].sort().every((owner, index) => owner === sortedRight[index]);
44319
45379
  }
44320
45380
 
44321
45381
  // packages/core/module-statistics/src/index.ts
44322
45382
  function evaluateDependencyConstraints(input) {
44323
45383
  const constraints = [...input.constraints].sort((left, right) => compare(left.id, right.id));
44324
- const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated);
45384
+ const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded ?? false);
44325
45385
  if (constraints.length === 0) {
44326
45386
  return {
44327
45387
  schemaVersion: "archcontext.dependency-constraint-evaluation/v1",
@@ -44389,7 +45449,87 @@ function evaluateDependencyConstraints(input) {
44389
45449
  violations: sortedViolations
44390
45450
  };
44391
45451
  }
44392
- function certifyCodeFacts(codeFacts, worktreeDigest, truncated) {
45452
+ function measureCapabilityDependencies(input) {
45453
+ const certification = certifyCodeFacts(input.codeFacts, input.worktreeDigest, input.truncated, input.importNodeLimitExceeded);
45454
+ const paths = new Set(input.files);
45455
+ const edges = (certification.measurable ? resolveEdges(input.importEdges, input.workspacePackages, paths) : []).filter((edge) => paths.has(edge.from) && (edge.to === null || paths.has(edge.to)));
45456
+ const ownership = resolveOwnership(input.nodes, [...paths].sort());
45457
+ const parents = new Map(input.nodes.map((node) => [node.id, node.parent]));
45458
+ const kinds = new Map(input.nodes.map((node) => [node.id, node.kind]));
45459
+ const capabilityOf = (owner) => {
45460
+ for (const ancestor of ancestorChain(owner, parents))
45461
+ if (kinds.get(ancestor) === "capability")
45462
+ return ancestor;
45463
+ return null;
45464
+ };
45465
+ const ownersOf = (path) => ownership.byPath.get(path)?.owners ?? [];
45466
+ const within = (path, nodeId) => nodeId === undefined || ownersOf(path).some((owner) => ancestorChain(owner, parents).has(nodeId));
45467
+ const attribute = (path) => {
45468
+ const owners = ownersOf(path);
45469
+ if (owners.length === 0)
45470
+ return { gap: "unownedEndpoint" };
45471
+ const capabilities = new Set(owners.map(capabilityOf));
45472
+ if (capabilities.size > 1)
45473
+ return { gap: "contestedEndpoint" };
45474
+ const [capability] = capabilities;
45475
+ return capability === null ? { gap: "noCapabilityEndpoint" } : { capability };
45476
+ };
45477
+ const unattributed = { unownedEndpoint: 0, contestedEndpoint: 0, noCapabilityEndpoint: 0 };
45478
+ const cells = new Map;
45479
+ const attributedEdges = [];
45480
+ const seen = new Set;
45481
+ let unresolvedCount = 0;
45482
+ for (const edge of edges) {
45483
+ if (!within(edge.from, input.from))
45484
+ continue;
45485
+ if (edge.to === null) {
45486
+ if (input.into === undefined && isRepositoryLocalSpecifier(edge.specifier, input.workspacePackages))
45487
+ unresolvedCount += 1;
45488
+ continue;
45489
+ }
45490
+ if (edge.to === edge.from || !within(edge.to, input.into))
45491
+ continue;
45492
+ const key = JSON.stringify([edge.from, edge.to]);
45493
+ if (seen.has(key))
45494
+ continue;
45495
+ seen.add(key);
45496
+ const from = attribute(edge.from);
45497
+ const to = attribute(edge.to);
45498
+ if ("gap" in from || "gap" in to) {
45499
+ unattributed["gap" in from ? from.gap : to.gap] += 1;
45500
+ continue;
45501
+ }
45502
+ const cellKey = JSON.stringify([from.capability, to.capability]);
45503
+ const cell = cells.get(cellKey) ?? { from: from.capability, to: to.capability, imports: 0, calls: null };
45504
+ cell.imports += 1;
45505
+ cells.set(cellKey, cell);
45506
+ attributedEdges.push({ fromPath: edge.from, toPath: edge.to, fromCapability: from.capability, toCapability: to.capability });
45507
+ }
45508
+ const edgeLimit = input.edgeLimit ?? CAPABILITY_DEPENDENCIES_EDGE_LIMIT;
45509
+ attributedEdges.sort((left, right) => compare(left.fromPath, right.fromPath) || compare(left.toPath, right.toPath));
45510
+ const listed = attributedEdges.slice(0, edgeLimit);
45511
+ return {
45512
+ schemaVersion: CAPABILITY_DEPENDENCIES_SCHEMA_VERSION,
45513
+ ownershipRule: "parent-chain",
45514
+ filter: { from: input.from ?? null, into: input.into ?? null },
45515
+ capabilities: input.nodes.filter((node) => node.kind === "capability").map((node) => node.id).sort(compare),
45516
+ matrix: [...cells.values()].sort((left, right) => compare(left.from, right.from) || compare(left.to, right.to)),
45517
+ imports: {
45518
+ coverage: certification.coverage,
45519
+ reasonCodes: [...certification.reasonCodes].sort(),
45520
+ truncated: input.truncated,
45521
+ importNodeCount: input.importNodeCount,
45522
+ importNodeLimit: input.importNodeLimit,
45523
+ importNodeLimitExceeded: input.importNodeLimitExceeded,
45524
+ edgeCount: seen.size,
45525
+ unresolvedCount,
45526
+ unattributed
45527
+ },
45528
+ calls: { coverage: "unknown", reasonCodes: [CAPABILITY_DEPENDENCIES_CALLS_UNAVAILABLE_REASON] },
45529
+ edges: { limit: edgeLimit, count: attributedEdges.length, omittedCount: attributedEdges.length - listed.length, items: listed }
45530
+ };
45531
+ }
45532
+ function certifyCodeFacts(codeFacts, worktreeDigest, truncated, importNodeLimitExceeded) {
44393
45533
  const attested = codeFacts.indexedWorktreeDigest !== null && codeFacts.indexedWorktreeDigest === worktreeDigest;
44394
45534
  const measurable = codeFacts.availability === "ready" && attested;
44395
45535
  const reasonCodes = [];
@@ -44397,9 +45537,12 @@ function certifyCodeFacts(codeFacts, worktreeDigest, truncated) {
44397
45537
  reasonCodes.push("code-facts-unavailable");
44398
45538
  else if (!attested)
44399
45539
  reasonCodes.push("code-facts-stale");
45540
+ if (measurable && importNodeLimitExceeded)
45541
+ reasonCodes.push("code-facts-import-limit");
44400
45542
  if (measurable && truncated)
44401
45543
  reasonCodes.push("code-facts-truncated");
44402
- return { measurable, coverage: !measurable ? "unknown" : truncated ? "partial" : "complete", reasonCodes };
45544
+ const partial = truncated || importNodeLimitExceeded;
45545
+ return { measurable, coverage: !measurable ? "unknown" : partial ? "partial" : "complete", reasonCodes };
44403
45546
  }
44404
45547
  function isRepositoryLocalSpecifier(specifier, workspacePackages) {
44405
45548
  if (specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../"))
@@ -44414,7 +45557,8 @@ function measureModuleStatistics(input) {
44414
45557
  const trackedFiles = [...input.trackedFiles].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
44415
45558
  const linesByPath = new Map(trackedFiles.map((file) => [file.path, file.lineCount]));
44416
45559
  const ownership = resolveOwnership(nodes, trackedFiles.map((file) => file.path));
44417
- const { measurable, coverage } = certifyCodeFacts(input.codeFacts, input.worktree.worktreeDigest, input.truncated);
45560
+ const importNodeLimitExceeded = input.importNodeLimitExceeded ?? false;
45561
+ const { measurable, coverage } = certifyCodeFacts(input.codeFacts, input.worktree.worktreeDigest, input.truncated, importNodeLimitExceeded);
44418
45562
  const edges = measurable ? resolveEdges(input.importEdges, input.workspacePackages, new Set(linesByPath.keys())) : [];
44419
45563
  const constraints = input.constraints ?? [];
44420
45564
  const directionEvaluation = constraints.length === 0 ? undefined : evaluateDependencyConstraints({
@@ -44425,6 +45569,7 @@ function measureModuleStatistics(input) {
44425
45569
  importEdges: input.importEdges,
44426
45570
  workspacePackages: input.workspacePackages,
44427
45571
  truncated: input.truncated,
45572
+ importNodeLimitExceeded,
44428
45573
  codeFacts: input.codeFacts
44429
45574
  });
44430
45575
  const directionViolations = directionEvaluation === undefined ? undefined : countViolationsByNode(directionEvaluation);
@@ -44447,7 +45592,8 @@ function measureModuleStatistics(input) {
44447
45592
  coverage,
44448
45593
  truncated: coverage === "unknown" ? true : input.truncated,
44449
45594
  edgeLimit: input.edgeLimit,
44450
- reasonCodes: reasonCodes({ coverage, truncated: input.truncated, measurable, modules, ownership })
45595
+ ...input.importNodeCount === undefined ? {} : { importNodeCount: input.importNodeCount },
45596
+ reasonCodes: reasonCodes({ coverage, truncated: input.truncated, importNodeLimitExceeded, measurable, modules, ownership })
44451
45597
  };
44452
45598
  const draft = {
44453
45599
  schemaVersion: MODULE_STATISTICS_SCHEMA_VERSION,
@@ -44557,6 +45703,8 @@ function reasonCodes(context) {
44557
45703
  codes.add("code-facts-missing");
44558
45704
  if (context.truncated || context.coverage === "unknown")
44559
45705
  codes.add("code-facts-truncated");
45706
+ if (context.measurable && context.importNodeLimitExceeded)
45707
+ codes.add("code-facts-import-limit");
44560
45708
  if (context.modules.some((module) => !module.footprintDeclared))
44561
45709
  codes.add("node-footprint-undeclared");
44562
45710
  if (context.ownership.multiplyOwnedFileCount > 0)
@@ -44566,6 +45714,16 @@ function reasonCodes(context) {
44566
45714
  return [...codes].sort();
44567
45715
  }
44568
45716
 
45717
+ // packages/local-runtime/runtime-daemon/src/refactor-scan.ts
45718
+ init_src();
45719
+ import { execFileSync as execFileSync9 } from "node:child_process";
45720
+ import { createHash as createHash10 } from "node:crypto";
45721
+ import { existsSync as existsSync24, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "node:fs";
45722
+ import { join as join16 } from "node:path";
45723
+
45724
+ // packages/core/refactor-assessment/src/index.ts
45725
+ init_src();
45726
+
44569
45727
  // packages/core/refactor-assessment/src/evidence.ts
44570
45728
  init_src();
44571
45729
  class ObservationEvidenceBuilder {
@@ -45320,7 +46478,8 @@ function buildObservations(snapshot, structure, model) {
45320
46478
  subjectSelectorId: repositorySubject,
45321
46479
  metrics: {
45322
46480
  unresolvedImportCount: snapshot.repositorySummary.unresolvedImportCount,
45323
- edgeLimit: snapshot.codeFacts.edgeLimit
46481
+ edgeLimit: snapshot.codeFacts.edgeLimit,
46482
+ ...snapshot.codeFacts.importNodeCount === undefined ? {} : { importNodeCount: snapshot.codeFacts.importNodeCount }
45324
46483
  },
45325
46484
  evidence: evidence.evidenceGap()
45326
46485
  });
@@ -45359,8 +46518,12 @@ function classifyProposal(context) {
45359
46518
  codes.add("node-footprint-undeclared");
45360
46519
  if (coverage === "unknown")
45361
46520
  codes.add("code-facts-missing");
45362
- if (coverage === "partial")
45363
- codes.add("code-facts-truncated");
46521
+ if (coverage === "partial") {
46522
+ const limited = context.snapshot.codeFacts.reasonCodes.includes("code-facts-import-limit");
46523
+ codes.add(limited ? "code-facts-import-limit" : "code-facts-truncated");
46524
+ if (limited && context.snapshot.codeFacts.truncated)
46525
+ codes.add("code-facts-truncated");
46526
+ }
45364
46527
  if (scopeOwnership.contested.length > 0)
45365
46528
  codes.add("ownership-ambiguous");
45366
46529
  if (derivation.unresolvedTargets.length > 0)
@@ -45502,7 +46665,11 @@ function isPresent2(value) {
45502
46665
  return value !== undefined;
45503
46666
  }
45504
46667
 
46668
+ // packages/local-runtime/runtime-daemon/src/refactor-recording.ts
46669
+ init_src();
46670
+
45505
46671
  // packages/core/recommendation-engine/src/index.ts
46672
+ init_src();
45506
46673
  var RECOMMENDATION_SCHEDULER_ENGINE_VERSION = "archcontext.recommendation-scheduler/v1";
45507
46674
  var ACTIVE_RECOMMENDATION_STATUSES = new Set(["open", "acknowledged", "deferred"]);
45508
46675
  var REFACTOR_ACTIVE_RECOMMENDATION_STATUSES = new Set([
@@ -46176,11 +47343,16 @@ function observationExplanation(observation, affectedNodeIds) {
46176
47343
  const examples = evidence.unresolvedImports.slice(0, EXPLANATION_EXAMPLE_LIMIT);
46177
47344
  return [
46178
47345
  `Code facts coverage is ${evidence.coverage} (${evidence.reasonCodes.join(", ")}), so structural metrics${affectedNodeIds.length > 0 ? ` for ${affectedNodeIds.join(", ")}` : ""} are incomplete.`,
47346
+ ...evidence.reasonCodes.includes("code-facts-import-limit") ? [importLimitText(observation.metrics)] : [],
46179
47347
  ...evidence.totalCount === 0 ? [] : [`${evidence.totalCount} import specifier(s) did not resolve to a tracked file.`, ...examples.map((entry) => `${locationText(entry.fromPath, entry.fromLine)} imports ${entry.specifier}.`), ...more(examples.length)]
46180
47348
  ];
46181
47349
  }
46182
47350
  }
46183
47351
  }
47352
+ function importLimitText(metrics) {
47353
+ const count = typeof metrics.importNodeCount === "number" ? `${metrics.importNodeCount} import nodes` : "more import nodes";
47354
+ return `The index holds ${count}, more than the scan import node limit of ${metrics.edgeLimit ?? "unknown"}. Run archctx refactor scan --import-node-limit <n> with a larger limit; a new CodeGraph index does not change this.`;
47355
+ }
46184
47356
  function evidenceLength(evidence) {
46185
47357
  switch (evidence.kind) {
46186
47358
  case "cycle":
@@ -46365,7 +47537,6 @@ function latestRecommendationV3ByFingerprint(previous) {
46365
47537
  }
46366
47538
 
46367
47539
  // packages/local-runtime/runtime-daemon/src/refactor-recording.ts
46368
- init_src();
46369
47540
  var REFACTOR_ASSESSMENT_REGISTRY_CAPACITY = 8;
46370
47541
  var REFACTOR_CLASSIFIER_RULESET_SCHEMA_VERSION = "archcontext.refactor-classifier-ruleset/v1";
46371
47542
  var REFACTOR_SCAN_EVENT_TYPE = "architecture.refactor.scan";
@@ -46606,17 +47777,15 @@ function digestSuffix4(digest) {
46606
47777
  }
46607
47778
 
46608
47779
  // packages/local-runtime/runtime-daemon/src/refactor-scan.ts
46609
- init_src();
46610
- import { execFileSync as execFileSync9 } from "node:child_process";
46611
- import { createHash as createHash10 } from "node:crypto";
46612
- import { existsSync as existsSync24, readdirSync as readdirSync11, readFileSync as readFileSync24 } from "node:fs";
46613
- import { join as join16 } from "node:path";
46614
47780
  var CODEGRAPH_BINARY = "codegraph";
46615
47781
  var CODEGRAPH_VERSION_TIMEOUT_MS = 15000;
46616
47782
  var REPOSITORY_REFACTOR_REQUEST = {
46617
47783
  schemaVersion: REFACTOR_REQUEST_SCHEMA_VERSION,
46618
47784
  scope: { kind: "repository" }
46619
47785
  };
47786
+ function repositoryRefactorRequest(importNodeLimit) {
47787
+ return importNodeLimit === undefined ? REPOSITORY_REFACTOR_REQUEST : { ...REPOSITORY_REFACTOR_REQUEST, importNodeLimit };
47788
+ }
46620
47789
 
46621
47790
  class RefactorScanError extends Error {
46622
47791
  code;
@@ -46640,7 +47809,7 @@ function runRefactorScan(input) {
46640
47809
  const constraints = readDependencyConstraints(listModelFiles(input.root)).constraints;
46641
47810
  const createdAt = readHeadCommitterDate(input.root);
46642
47811
  const requestId = refactorRequestId(input.request);
46643
- const edgeLimit = input.edgeLimit ?? CODEGRAPH_IMPORT_NODE_QUERY_LIMIT;
47812
+ const edgeLimit = input.request.importNodeLimit ?? CODEGRAPH_IMPORT_NODE_READ_LIMIT;
46644
47813
  const trackedFiles = readTrackedSourceFiles(input.root);
46645
47814
  const codeFacts = readCodeFacts(input.root, input.worktree.worktreeDigest, edgeLimit);
46646
47815
  const { snapshot, structure } = measureModuleStatistics({
@@ -46652,6 +47821,8 @@ function runRefactorScan(input) {
46652
47821
  workspacePackages: declaredWorkspacePackages(input.root),
46653
47822
  truncated: codeFacts.truncated,
46654
47823
  edgeLimit,
47824
+ importNodeCount: codeFacts.importNodeCount,
47825
+ importNodeLimitExceeded: codeFacts.importNodeLimitExceeded,
46655
47826
  codeFacts: {
46656
47827
  version: codeFacts.version,
46657
47828
  binaryDigest: codeFacts.binaryDigest,
@@ -46707,7 +47878,8 @@ function runRefactorScan(input) {
46707
47878
  maxEvidenceSampleLimit: REFACTOR_OBSERVATION_EVIDENCE_LIMIT,
46708
47879
  ...plan.candidateBudget
46709
47880
  },
46710
- trackedFileCount: trackedFiles.length
47881
+ trackedFileCount: trackedFiles.length,
47882
+ untrackedExcludedCount: countFilesOutsideHead(input.root)
46711
47883
  };
46712
47884
  }
46713
47885
  function evaluateReviewDependencyConstraints(input) {
@@ -46723,11 +47895,20 @@ function evaluateReviewDependencyConstraints(input) {
46723
47895
  codeFacts: { availability: "unavailable", indexedWorktreeDigest: null }
46724
47896
  });
46725
47897
  }
47898
+ const edgeLimit = input.edgeLimit ?? CODEGRAPH_IMPORT_NODE_READ_LIMIT;
46726
47899
  let imports;
46727
47900
  try {
46728
- imports = repositoryImportPairs(input.root, CODEGRAPH_BINARY, input.edgeLimit ?? CODEGRAPH_IMPORT_NODE_QUERY_LIMIT, input.worktreeDigest);
47901
+ imports = repositoryImportPairs(input.root, CODEGRAPH_BINARY, edgeLimit, input.worktreeDigest);
46729
47902
  } catch {
46730
- imports = { pairs: [], truncated: true, availability: "unavailable", indexedWorktreeDigest: null };
47903
+ imports = {
47904
+ pairs: [],
47905
+ truncated: true,
47906
+ availability: "unavailable",
47907
+ indexedWorktreeDigest: null,
47908
+ importNodeCount: null,
47909
+ importNodeLimit: edgeLimit,
47910
+ importNodeLimitExceeded: false
47911
+ };
46731
47912
  }
46732
47913
  const workspaces = reviewWorkspacePackages(input.root);
46733
47914
  return evaluateDependencyConstraints({
@@ -46737,6 +47918,7 @@ function evaluateReviewDependencyConstraints(input) {
46737
47918
  workspacePackages: workspaces.packages,
46738
47919
  workspacePackagesResolved: workspaces.resolved,
46739
47920
  truncated: imports.truncated,
47921
+ importNodeLimitExceeded: imports.importNodeLimitExceeded,
46740
47922
  codeFacts: { availability: imports.availability, indexedWorktreeDigest: imports.indexedWorktreeDigest }
46741
47923
  });
46742
47924
  }
@@ -46744,7 +47926,7 @@ function reviewWorkspacePackages(root) {
46744
47926
  if (!existsSync24(join16(root, "package.json")))
46745
47927
  return { packages: [], resolved: true };
46746
47928
  try {
46747
- const manifest = JSON.parse(readFileSync24(join16(root, "package.json"), "utf8"));
47929
+ const manifest = JSON.parse(readFileSync25(join16(root, "package.json"), "utf8"));
46748
47930
  const declared = Array.isArray(manifest.workspaces) ? manifest.workspaces : manifest.workspaces?.packages ?? [];
46749
47931
  if (!Array.isArray(declared))
46750
47932
  throw new Error("workspaces must be a list");
@@ -46757,10 +47939,10 @@ function reviewWorkspacePackages(root) {
46757
47939
  const parent = /^([^*?[\]{}!]+)\/\*$/.exec(pattern)?.[1];
46758
47940
  if (parent === undefined)
46759
47941
  throw new Error(`unsupported workspace pattern: ${entry}`);
46760
- return readdirSync11(join16(root, parent), { withFileTypes: true }).filter((child) => child.isDirectory() && existsSync24(join16(root, parent, child.name, "package.json"))).map((child) => `${parent}/${child.name}`);
47942
+ return readdirSync13(join16(root, parent), { withFileTypes: true }).filter((child) => child.isDirectory() && existsSync24(join16(root, parent, child.name, "package.json"))).map((child) => `${parent}/${child.name}`);
46761
47943
  });
46762
47944
  const packages = directories.flatMap((directory) => {
46763
- const packageManifest = JSON.parse(readFileSync24(join16(root, ...directory.split("/"), "package.json"), "utf8"));
47945
+ const packageManifest = JSON.parse(readFileSync25(join16(root, ...directory.split("/"), "package.json"), "utf8"));
46764
47946
  if (typeof packageManifest.name !== "string")
46765
47947
  return [];
46766
47948
  const exports = typeof packageManifest.exports === "string" ? { ".": packageManifest.exports } : Object.fromEntries(Object.entries(packageManifest.exports ?? {}).filter((entry) => typeof entry[1] === "string"));
@@ -46797,6 +47979,10 @@ function readWorktreeFiles(root) {
46797
47979
  }
46798
47980
  return [...new Set(output.split("\x00").filter(Boolean))].sort();
46799
47981
  }
47982
+ function countFilesOutsideHead(root) {
47983
+ const head = new Set(readTrackedTreeEntries(root).map((entry) => entry.path));
47984
+ return readWorktreeFiles(root).filter((path) => !head.has(path)).length;
47985
+ }
46800
47986
  function declaredWorkspacePackages(root) {
46801
47987
  return existsSync24(join16(root, "package.json")) ? readWorkspacePackages(root) : [];
46802
47988
  }
@@ -46827,6 +48013,8 @@ function readCodeFacts(root, worktreeDigest, edgeLimit) {
46827
48013
  return {
46828
48014
  pairs: imports.pairs,
46829
48015
  truncated: imports.truncated,
48016
+ importNodeCount: imports.importNodeCount,
48017
+ importNodeLimitExceeded: imports.importNodeLimitExceeded,
46830
48018
  availability: imports.availability,
46831
48019
  indexedWorktreeDigest: imports.indexedWorktreeDigest,
46832
48020
  ...attestation
@@ -46843,10 +48031,108 @@ function attestCodeGraphBinary(root) {
46843
48031
  const binaryPath = invocation.argsPrefix[0] ?? invocation.command;
46844
48032
  return {
46845
48033
  version,
46846
- binaryDigest: `sha256:${createHash10("sha256").update(readFileSync24(binaryPath)).digest("hex")}`
48034
+ binaryDigest: `sha256:${createHash10("sha256").update(readFileSync25(binaryPath)).digest("hex")}`
46847
48035
  };
46848
48036
  }
46849
48037
 
48038
+ // packages/local-runtime/runtime-daemon/src/capability-dependencies.ts
48039
+ var CODEGRAPH_BINARY2 = "codegraph";
48040
+
48041
+ class CapabilityDependenciesError extends Error {
48042
+ code;
48043
+ constructor(code, message) {
48044
+ super(message);
48045
+ this.code = code;
48046
+ this.name = "CapabilityDependenciesError";
48047
+ }
48048
+ }
48049
+ function readCapabilityDependencies(root, request = {}) {
48050
+ const importNodeLimit = request.importNodeLimit ?? CODEGRAPH_IMPORT_NODE_READ_LIMIT;
48051
+ for (const [name, value] of [["importNodeLimit", request.importNodeLimit], ["edgeLimit", request.edgeLimit]]) {
48052
+ if (value !== undefined && (!Number.isInteger(value) || value < (name === "edgeLimit" ? 0 : 1))) {
48053
+ throw new CapabilityDependenciesError("AC_SCHEMA_INVALID", `${name} must be a ${name === "edgeLimit" ? "non-negative" : "positive"} integer, got ${value}`);
48054
+ }
48055
+ }
48056
+ const model = loadModel2(root);
48057
+ const nodeIds = new Set(model.nodes.map((node) => node.id));
48058
+ for (const [flag, nodeId] of [["--from", request.from], ["--into", request.into]]) {
48059
+ if (nodeId !== undefined && !nodeIds.has(nodeId)) {
48060
+ throw new CapabilityDependenciesError("AC_SCHEMA_INVALID", `deps ${flag} names no node in the model: ${nodeId}`);
48061
+ }
48062
+ }
48063
+ const headSha = readHeadSha(root);
48064
+ const worktreeDigest = computeWorktreeDigest(root);
48065
+ const files = readTrackedTreeEntries(root).filter((entry) => entry.type === "blob").map((entry) => entry.path);
48066
+ const imports = readImports(root, importNodeLimit, worktreeDigest);
48067
+ const matrix = measureCapabilityDependencies({
48068
+ nodes: model.nodes,
48069
+ worktreeDigest,
48070
+ files,
48071
+ importEdges: imports.pairs,
48072
+ workspacePackages: readWorkspaces(root),
48073
+ truncated: imports.truncated,
48074
+ importNodeCount: imports.importNodeCount,
48075
+ importNodeLimit: imports.importNodeLimit,
48076
+ importNodeLimitExceeded: imports.importNodeLimitExceeded,
48077
+ codeFacts: { availability: imports.availability, indexedWorktreeDigest: imports.indexedWorktreeDigest },
48078
+ ...request.from === undefined ? {} : { from: request.from },
48079
+ ...request.into === undefined ? {} : { into: request.into },
48080
+ ...request.edgeLimit === undefined ? {} : { edgeLimit: request.edgeLimit }
48081
+ });
48082
+ return { ...matrix, worktree: { headSha, worktreeDigest }, privacy: { writes: "none" } };
48083
+ }
48084
+ function readWorkspaces(root) {
48085
+ try {
48086
+ return declaredWorkspacePackages(root);
48087
+ } catch (error) {
48088
+ throw new CapabilityDependenciesError("AC_SCHEMA_INVALID", `workspace packages are unreadable: ${error instanceof Error ? error.message : String(error)}`);
48089
+ }
48090
+ }
48091
+ function loadModel2(root) {
48092
+ try {
48093
+ return loadNativeModelFromArchContext(root);
48094
+ } catch (error) {
48095
+ throw new CapabilityDependenciesError("AC_MODEL_ADOPTION_REQUIRED", `architecture model is unreadable: ${error instanceof Error ? error.message : String(error)}`);
48096
+ }
48097
+ }
48098
+ function readImports(root, limit, worktreeDigest) {
48099
+ try {
48100
+ return repositoryImportPairs(root, CODEGRAPH_BINARY2, limit, worktreeDigest);
48101
+ } catch {
48102
+ return {
48103
+ pairs: [],
48104
+ truncated: true,
48105
+ availability: "unavailable",
48106
+ indexedWorktreeDigest: null,
48107
+ importNodeCount: null,
48108
+ importNodeLimit: limit,
48109
+ importNodeLimitExceeded: false
48110
+ };
48111
+ }
48112
+ }
48113
+ // packages/local-runtime/runtime-daemon/src/flow-selector-check.ts
48114
+ init_src();
48115
+ function runFlowSelectorCheckCommand(cwd, options = {}) {
48116
+ try {
48117
+ const root = findRepositoryRoot(cwd);
48118
+ const model = loadNativeModelFromArchContext(root);
48119
+ const sourceFiles = listProjectionSourceFiles(root);
48120
+ const prepared = prepareProjectionCodeFacts(root, model, {
48121
+ sourceTreeDigest: architectureDocumentationSourceTreeDigest(root, model, sourceFiles),
48122
+ sourceFiles,
48123
+ importGraphs: false,
48124
+ ...options.binary ? { binary: options.binary } : {}
48125
+ });
48126
+ assertProjectionCodeFactsAvailable(root, prepared);
48127
+ return okEnvelope("flows.check", checkArchitectureFlowSelectors({
48128
+ model,
48129
+ selectorEvidence: prepared.selectorEvidence,
48130
+ codeGraph: { availability: prepared.handshake.availability, version: prepared.handshake.actualVersion }
48131
+ }));
48132
+ } catch (error) {
48133
+ return projectionFailureEnvelope("flows.check", error);
48134
+ }
48135
+ }
46850
48136
  // packages/local-runtime/runtime-daemon/src/refactor-verify.ts
46851
48137
  init_src();
46852
48138
  var REFACTOR_RESOLUTION_EVENT_TYPE = "architecture.refactor.resolution";
@@ -46993,6 +48279,12 @@ function baselineSnapshotForRecommendation(evidenceState, recommendationId) {
46993
48279
  }
46994
48280
  return;
46995
48281
  }
48282
+ function baselineImportNodeLimit(snapshot) {
48283
+ if (snapshot === undefined || snapshot.codeFacts.importNodeCount === undefined)
48284
+ return;
48285
+ const { edgeLimit } = snapshot.codeFacts;
48286
+ return edgeLimit === null || edgeLimit === CODEGRAPH_IMPORT_NODE_READ_LIMIT ? undefined : edgeLimit;
48287
+ }
46996
48288
  function refactorVerifyIngressIssues(recommendation) {
46997
48289
  if (recommendation.category === "practice") {
46998
48290
  return [`recommendation ${recommendation.recommendationId} is a practice recommendation and carries no refactor outcome`];
@@ -47087,6 +48379,9 @@ class RecommendationsService {
47087
48379
  }
47088
48380
  });
47089
48381
  };
48382
+ if (input.importNodeLimit !== undefined && !(Number.isInteger(input.importNodeLimit) && input.importNodeLimit >= 1)) {
48383
+ return errorEnvelope(`recommendations.${command}`, "AC_SCHEMA_INVALID", `recommendations ${command} importNodeLimit must be a positive integer`);
48384
+ }
47090
48385
  if (command === "metrics")
47091
48386
  return readMetrics();
47092
48387
  if (command === "list")
@@ -47094,7 +48389,7 @@ class RecommendationsService {
47094
48389
  if (command === "show") {
47095
48390
  if (!input.recommendationId)
47096
48391
  return errorEnvelope("recommendations.show", "AC_SCHEMA_INVALID", "recommendations show requires --id");
47097
- return this.showRecommendation(repositoryRoot, input.recommendationId);
48392
+ return this.showRecommendation(repositoryRoot, input.recommendationId, input.importNodeLimit);
47098
48393
  }
47099
48394
  if (!isRecommendationLifecycleCliAction(command)) {
47100
48395
  return errorEnvelope("recommendations", "AC_SCHEMA_INVALID", "recommendations requires acknowledge|accept|reject|defer|waive|resolve|metrics|show|list");
@@ -47158,7 +48453,7 @@ class RecommendationsService {
47158
48453
  async decideScanCandidate(root, repositoryRoot, input, command, now) {
47159
48454
  const surface = `recommendations.${command}`;
47160
48455
  const recommendationId = input.recommendationId;
47161
- const measured = await this.measureRepository(repositoryRoot, REPOSITORY_REFACTOR_REQUEST, `recommendations ${command}`);
48456
+ const measured = await this.measureRepository(repositoryRoot, repositoryRefactorRequest(input.importNodeLimit), `recommendations ${command}`);
47162
48457
  if (!measured.ok) {
47163
48458
  return errorEnvelope(surface, measured.code, `recommendation ${recommendationId} is not recorded and the current refactor scan could not run: ${measured.message}`, measured.reasonCode);
47164
48459
  }
@@ -47239,7 +48534,7 @@ class RecommendationsService {
47239
48534
  }
47240
48535
  });
47241
48536
  }
47242
- async showRecommendation(repositoryRoot, recommendationId) {
48537
+ async showRecommendation(repositoryRoot, recommendationId, importNodeLimit) {
47243
48538
  const scope = await this.context.architectureLedgerScope(repositoryRoot);
47244
48539
  const replay = await this.context.localStore.replayArchitectureLedger({ ...scope, mode: "genesis" });
47245
48540
  const artifacts = recommendationArtifactsFromEvents(replay.events);
@@ -47257,7 +48552,7 @@ class RecommendationsService {
47257
48552
  worktree: null
47258
48553
  });
47259
48554
  }
47260
- const measured = await this.measureRepository(repositoryRoot, REPOSITORY_REFACTOR_REQUEST, "recommendations show");
48555
+ const measured = await this.measureRepository(repositoryRoot, repositoryRefactorRequest(importNodeLimit), "recommendations show");
47261
48556
  if (!measured.ok) {
47262
48557
  return errorEnvelope("recommendations.show", measured.code, `recommendation ${recommendationId} is not recorded and the current refactor scan could not run: ${measured.message}`, measured.reasonCode);
47263
48558
  }
@@ -47446,6 +48741,7 @@ class RecommendationsService {
47446
48741
  requestId: result.requestId,
47447
48742
  request,
47448
48743
  trackedFileCount: result.trackedFileCount,
48744
+ untrackedExcludedCount: result.untrackedExcludedCount,
47449
48745
  snapshot: result.snapshot,
47450
48746
  assessment: result.assessment,
47451
48747
  ...result.proposal ? { proposal: result.proposal } : {},
@@ -47612,11 +48908,13 @@ class RecommendationsService {
47612
48908
  graphDigest: replay.graphDigest
47613
48909
  });
47614
48910
  }
48911
+ const baseline = baselineSnapshotForRecommendation(replay.evidenceState, recommendation.recommendationId);
48912
+ const importNodeLimit = input.importNodeLimit ?? baselineImportNodeLimit(baseline?.snapshot);
47615
48913
  let scan;
47616
48914
  try {
47617
48915
  scan = runRefactorScan({
47618
48916
  root: repositoryRoot,
47619
- request: REPOSITORY_REFACTOR_REQUEST,
48917
+ request: repositoryRefactorRequest(importNodeLimit),
47620
48918
  repository: gitScope.repository,
47621
48919
  worktree: gitScope.worktree,
47622
48920
  previousRecommendations: artifacts.recommendations,
@@ -47627,7 +48925,6 @@ class RecommendationsService {
47627
48925
  return errorEnvelope("refactor.verify", error.code, error.message, error.reasonCode);
47628
48926
  return errorEnvelope("refactor.verify", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
47629
48927
  }
47630
- const baseline = baselineSnapshotForRecommendation(replay.evidenceState, recommendation.recommendationId);
47631
48928
  let plan;
47632
48929
  try {
47633
48930
  plan = runRefactorVerify({
@@ -47764,11 +49061,15 @@ function decodeRuntimeRefactorVerifyInput(value) {
47764
49061
  throw new RuntimeRefactorInputError(`refactor verify ${field} must be a non-empty string`);
47765
49062
  }
47766
49063
  }
49064
+ if (input.importNodeLimit !== undefined && typeof input.importNodeLimit !== "number") {
49065
+ throw new RuntimeRefactorInputError("refactor verify importNodeLimit must be a positive integer");
49066
+ }
47767
49067
  const request = {
47768
49068
  schemaVersion: REFACTOR_VERIFICATION_REQUEST_SCHEMA_VERSION,
47769
49069
  recommendationId: input.recommendationId,
47770
49070
  ...typeof input.expectedHeadSha === "string" ? { expectedHeadSha: input.expectedHeadSha } : {},
47771
49071
  ...typeof input.expectedWorktreeDigest === "string" ? { expectedWorktreeDigest: input.expectedWorktreeDigest } : {},
49072
+ ...typeof input.importNodeLimit === "number" ? { importNodeLimit: input.importNodeLimit } : {},
47772
49073
  ...input.executionEvidenceRefs === undefined ? {} : { executionEvidenceRefs: decodeRuntimeExecutionEvidenceRefs(input.executionEvidenceRefs) }
47773
49074
  };
47774
49075
  if (input.schemaVersion !== REFACTOR_VERIFICATION_REQUEST_SCHEMA_VERSION) {
@@ -47966,14 +49267,14 @@ function recommendationActorKind(input) {
47966
49267
  }
47967
49268
 
47968
49269
  // packages/local-runtime/runtime-daemon/src/changeset-authority.ts
47969
- import { existsSync as existsSync25, readFileSync as readFileSync26 } from "node:fs";
47970
- import { resolve as resolve26 } from "node:path";
49270
+ import { existsSync as existsSync25, readFileSync as readFileSync27 } from "node:fs";
49271
+ import { resolve as resolve27 } from "node:path";
47971
49272
  init_src();
47972
49273
 
47973
49274
  // packages/local-runtime/runtime-daemon/src/committed-change-acceptance.ts
47974
49275
  import { execFileSync as execFileSync10 } from "node:child_process";
47975
- import { closeSync as closeSync8, constants as fsConstants3, fstatSync as fstatSync3, lstatSync as lstatSync13, openSync as openSync9, readdirSync as readdirSync12, readFileSync as readFileSync25 } from "node:fs";
47976
- import { resolve as resolve25 } from "node:path";
49276
+ import { closeSync as closeSync8, constants as fsConstants3, fstatSync as fstatSync3, lstatSync as lstatSync13, openSync as openSync9, readdirSync as readdirSync14, readFileSync as readFileSync26 } from "node:fs";
49277
+ import { resolve as resolve26 } from "node:path";
47977
49278
  init_src();
47978
49279
  var SEMANTIC_MODEL_DIRECTORIES = ["nodes", "relations", "flows"];
47979
49280
  var SEMANTIC_MODEL_PATH = /^\.archcontext\/model\/(nodes|relations|flows)\/[^/]+\.ya?ml$/;
@@ -48030,7 +49331,7 @@ function readSemanticModelSnapshot(root, readEntry) {
48030
49331
  try {
48031
49332
  if (!lstatSync13(absolute).isDirectory())
48032
49333
  throw new Error(`semantic-model-directory-invalid: ${relativeDirectory}`);
48033
- entries = readdirSync12(absolute);
49334
+ entries = readdirSync14(absolute);
48034
49335
  } catch (error) {
48035
49336
  if (error.code === "ENOENT")
48036
49337
  continue;
@@ -48038,7 +49339,7 @@ function readSemanticModelSnapshot(root, readEntry) {
48038
49339
  }
48039
49340
  for (const entry of entries.filter((name) => /\.ya?ml$/.test(name)).sort()) {
48040
49341
  const path = `${relativeDirectory}/${entry}`;
48041
- bodies.set(path, strictUtf8(readEntry(resolve25(absolute, entry), path), path));
49342
+ bodies.set(path, strictUtf8(readEntry(resolve26(absolute, entry), path), path));
48042
49343
  }
48043
49344
  }
48044
49345
  const hashes = new Map([...bodies].map(([path, body]) => [path, digestJson({ body })]));
@@ -48051,7 +49352,7 @@ function readRegularFileBytes(absolute, path) {
48051
49352
  try {
48052
49353
  if (!fstatSync3(fd).isFile())
48053
49354
  throw new Error(`semantic-model-entry-not-regular: ${path}`);
48054
- return readFileSync25(fd);
49355
+ return readFileSync26(fd);
48055
49356
  } finally {
48056
49357
  closeSync8(fd);
48057
49358
  }
@@ -48401,6 +49702,40 @@ function sameStrings(left, right) {
48401
49702
  return left.length === right.length && left.every((value, index) => value === right[index]);
48402
49703
  }
48403
49704
 
49705
+ // packages/local-runtime/runtime-daemon/src/ownership-delta-preview.ts
49706
+ init_src();
49707
+ var NODE_DIRECTORY = ".archcontext/model/nodes/";
49708
+ var NODE_FILE_OPERATIONS = new Set(["create_entity", "update_entity_fields", "delete_entity"]);
49709
+ function planOwnershipDelta(root, draft) {
49710
+ const nodeOperations = draft.operations.filter((operation) => NODE_FILE_OPERATIONS.has(operation.op) && operation.path !== undefined && isNodeFile(operation.path));
49711
+ if (nodeOperations.length === 0)
49712
+ return;
49713
+ try {
49714
+ const current = new Map(listModelFiles(root).filter((file) => isNodeFile(file.path)).map((file) => [file.path, file.body]));
49715
+ const planned = new Map(current);
49716
+ for (const operation of nodeOperations) {
49717
+ if (operation.op === "delete_entity")
49718
+ planned.delete(operation.path);
49719
+ else
49720
+ planned.set(operation.path, operation.body ?? "");
49721
+ }
49722
+ const paths = readTrackedTreeEntries(root).filter((entry) => entry.type === "blob").map((entry) => entry.path);
49723
+ return ownershipDelta(loadNativeModelFromModelFiles(current).nodes, loadNativeModelFromModelFiles(planned).nodes, paths);
49724
+ } catch (error) {
49725
+ return {
49726
+ schemaVersion: CHANGESET_OWNERSHIP_DELTA_SCHEMA_VERSION,
49727
+ status: "unavailable",
49728
+ reason: error instanceof Error ? error.message : String(error)
49729
+ };
49730
+ }
49731
+ }
49732
+ function isNodeFile(path) {
49733
+ if (!path.startsWith(NODE_DIRECTORY))
49734
+ return false;
49735
+ const name = path.slice(NODE_DIRECTORY.length);
49736
+ return !name.includes("/") && /\.ya?ml$/.test(name);
49737
+ }
49738
+
48404
49739
  // packages/local-runtime/runtime-daemon/src/changeset-authority.ts
48405
49740
  class RuntimeUpdateInputError extends Error {
48406
49741
  }
@@ -48516,10 +49851,10 @@ class ChangeSetAuthorityService {
48516
49851
  return errorEnvelope("practices.waive", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
48517
49852
  }
48518
49853
  const path = `.archcontext/waivers/${waiverId}.json`;
48519
- const absolute = resolve26(session.workspace.root, path);
49854
+ const absolute = resolve27(session.workspace.root, path);
48520
49855
  const body = `${JSON.stringify(waiver, null, 2)}
48521
49856
  `;
48522
- const expectedHash = existsSync25(absolute) ? digestJson({ body: readFileSync26(absolute, "utf8") }) : "missing";
49857
+ const expectedHash = existsSync25(absolute) ? digestJson({ body: readFileSync27(absolute, "utf8") }) : "missing";
48523
49858
  const draft = this.changeSetEngine.plan({
48524
49859
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
48525
49860
  base: {
@@ -48574,10 +49909,14 @@ class ChangeSetAuthorityService {
48574
49909
  this.changesets.set(draft.id, draft);
48575
49910
  this.changeSetRoots.set(draft.id, canonicalRepositoryRoot(root));
48576
49911
  this.changeSetWorktreeDigestProfiles.set(draft.id, worktreeDigestProfile);
49912
+ const ownershipDelta = planOwnershipDelta(root, draft);
48577
49913
  return okEnvelope("plan_update", {
48578
49914
  draft,
48579
49915
  changeSetDigest: digestJson(draft),
48580
- preview: this.changeSetEngine.preview(root, draft)
49916
+ preview: {
49917
+ ...this.changeSetEngine.preview(root, draft),
49918
+ ...ownershipDelta === undefined ? {} : { ownershipDelta }
49919
+ }
48581
49920
  });
48582
49921
  }
48583
49922
  async mcpProjection(root, input, approved) {
@@ -48876,7 +50215,7 @@ function architectureLedgerWriteAppendsEvents(mode) {
48876
50215
  // packages/local-runtime/runtime-daemon/src/rpc-server.ts
48877
50216
  init_src();
48878
50217
  import { createServer as createServer2 } from "node:http";
48879
- import { closeSync as closeSync9, mkdirSync as mkdirSync12, rmSync as rmSync15 } from "node:fs";
50218
+ import { closeSync as closeSync9, existsSync as existsSync26, mkdirSync as mkdirSync12 } from "node:fs";
48880
50219
  import { dirname as dirname14 } from "node:path";
48881
50220
  import { randomBytes as randomBytes5 } from "node:crypto";
48882
50221
  var DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 30 * 60000;
@@ -48904,6 +50243,8 @@ class ArchctxRuntimeRpcServer {
48904
50243
  idleTimeoutMs;
48905
50244
  idleTimer;
48906
50245
  inFlightRpcRequests = 0;
50246
+ authorizedRequests = 0;
50247
+ idleExitPending = false;
48907
50248
  constructor(daemon, options = {}) {
48908
50249
  this.daemon = daemon;
48909
50250
  this.options = options;
@@ -48930,11 +50271,11 @@ class ArchctxRuntimeRpcServer {
48930
50271
  if (server)
48931
50272
  await new Promise((resolveClose) => server.close(() => resolveClose()));
48932
50273
  if (connection)
48933
- rmSync15(connection.connectionPath, { force: true });
50274
+ await removePrivateControlFile(connection.connectionPath);
48934
50275
  if (this.lockFd !== undefined) {
48935
50276
  closeSync9(this.lockFd);
48936
50277
  this.lockFd = undefined;
48937
- rmSync15(this.options.lockPath ?? defaultDaemonLockPath(this.options.root ?? process.cwd()), { force: true });
50278
+ await removePrivateControlFile(this.options.lockPath ?? defaultDaemonLockPath(this.options.root ?? process.cwd()));
48938
50279
  }
48939
50280
  if (startedDaemon)
48940
50281
  await this.daemon.stop().catch(() => {
@@ -48992,13 +50333,16 @@ class ArchctxRuntimeRpcServer {
48992
50333
  });
48993
50334
  }
48994
50335
  await this.daemon.stop();
50336
+ try {
50337
+ if (connection)
50338
+ await removePrivateControlFile(connection.connectionPath);
50339
+ } finally {
50340
+ if (this.lockFd !== undefined)
50341
+ closeSync9(this.lockFd);
50342
+ this.lockFd = undefined;
50343
+ }
48995
50344
  if (connection)
48996
- rmSync15(connection.connectionPath, { force: true });
48997
- if (this.lockFd !== undefined)
48998
- closeSync9(this.lockFd);
48999
- this.lockFd = undefined;
49000
- if (connection)
49001
- rmSync15(connection.lockPath, { force: true });
50345
+ await removePrivateControlFile(connection.lockPath);
49002
50346
  this.options.onStop?.();
49003
50347
  }
49004
50348
  armIdleTimer() {
@@ -49015,21 +50359,44 @@ class ArchctxRuntimeRpcServer {
49015
50359
  this.idleTimer = undefined;
49016
50360
  }
49017
50361
  async checkIdleAndMaybeExit() {
49018
- if (!this.server || !this.connection)
50362
+ if (!this.server || !this.connection || this.idleExitPending)
49019
50363
  return;
49020
50364
  if (this.inFlightRpcRequests > 0) {
49021
50365
  this.armIdleTimer();
49022
50366
  return;
49023
50367
  }
50368
+ this.idleExitPending = true;
49024
50369
  const hasActiveWork = await this.daemon.hasActiveBackgroundWork().catch(() => true);
49025
50370
  if (hasActiveWork || this.inFlightRpcRequests > 0 || !this.connection) {
50371
+ this.idleExitPending = false;
49026
50372
  this.armIdleTimer();
49027
50373
  return;
49028
50374
  }
49029
- rmSync15(this.connection.connectionPath, { force: true });
50375
+ const connection = this.connection;
50376
+ const requestsBeforeRemoval = this.authorizedRequests;
50377
+ let cleanupError;
50378
+ await removePrivateControlFile(connection.connectionPath).catch((error) => {
50379
+ cleanupError = error;
50380
+ });
50381
+ if (this.server && this.connection && (this.inFlightRpcRequests > 0 || this.authorizedRequests !== requestsBeforeRemoval)) {
50382
+ try {
50383
+ if (!existsSync26(connection.connectionPath))
50384
+ createPrivateControlFile(connection.connectionPath, JSON.stringify(connection, null, 2));
50385
+ this.idleExitPending = false;
50386
+ this.armIdleTimer();
50387
+ return;
50388
+ } catch (error) {
50389
+ cleanupError ??= error;
50390
+ }
50391
+ }
49030
50392
  try {
49031
50393
  await this.stop();
50394
+ } catch (error) {
50395
+ cleanupError ??= error;
49032
50396
  } finally {
50397
+ if (cleanupError)
50398
+ process.stderr.write(`archctxd idle exit cleanup failed: ${cleanupError.code ?? "unknown"}
50399
+ `);
49033
50400
  (this.options.exit ?? process.exit)(0);
49034
50401
  }
49035
50402
  }
@@ -49059,6 +50426,7 @@ class ArchctxRuntimeRpcServer {
49059
50426
  writeJson(response, 401, { schemaVersion: RUNTIME_RPC_VERSION, ok: false, error: "runtime RPC token required" });
49060
50427
  return;
49061
50428
  }
50429
+ this.authorizedRequests += 1;
49062
50430
  const changeSetRecovery = this.daemon.status().changeSetRecovery;
49063
50431
  writeJson(response, 200, {
49064
50432
  schemaVersion: RUNTIME_RPC_VERSION,
@@ -49082,6 +50450,7 @@ class ArchctxRuntimeRpcServer {
49082
50450
  return;
49083
50451
  }
49084
50452
  this.inFlightRpcRequests += 1;
50453
+ this.authorizedRequests += 1;
49085
50454
  try {
49086
50455
  const read = await readRpcRequestBody(request, {
49087
50456
  maxBytes: this.options.maxRequestBodyBytes ?? RUNTIME_RPC_MAX_REQUEST_BODY_BYTES,
@@ -49111,8 +50480,12 @@ class ArchctxRuntimeRpcServer {
49111
50480
  throw error;
49112
50481
  });
49113
50482
  writeJson(response, 200, result);
49114
- if (body.method === "shutdown")
49115
- setTimeout(() => void this.stop(), 0);
50483
+ if (body.method === "shutdown") {
50484
+ setTimeout(() => void this.stop().catch((error) => {
50485
+ process.stderr.write(`archctxd shutdown cleanup failed: ${error.code ?? "unknown"}
50486
+ `);
50487
+ }), 0);
50488
+ }
49116
50489
  } finally {
49117
50490
  this.inFlightRpcRequests -= 1;
49118
50491
  this.armIdleTimer();
@@ -49214,8 +50587,8 @@ async function readRpcRequestBody(request, limits) {
49214
50587
  }
49215
50588
 
49216
50589
  // packages/local-runtime/runtime-daemon/src/index.ts
49217
- import { existsSync as existsSync26 } from "node:fs";
49218
- import { resolve as resolve27 } from "node:path";
50590
+ import { existsSync as existsSync27 } from "node:fs";
50591
+ import { resolve as resolve28 } from "node:path";
49219
50592
  init_src();
49220
50593
 
49221
50594
  // packages/local-runtime/runtime-daemon/src/egress.ts
@@ -50154,7 +51527,7 @@ class ArchctxDaemon {
50154
51527
  }
50155
51528
  async restoreRepositorySessions() {
50156
51529
  for (const record of await this.localStore.listRepositorySessions()) {
50157
- if (!record.root || !existsSync26(record.root))
51530
+ if (!record.root || !existsSync27(record.root))
50158
51531
  continue;
50159
51532
  if (repositoryFingerprint(record.root) !== record.repositoryId)
50160
51533
  continue;
@@ -50419,7 +51792,7 @@ function blockedProductionInjections(deps) {
50419
51792
  ].filter((key) => (key in deps));
50420
51793
  }
50421
51794
  function completeTaskProjectionDrift(root) {
50422
- if (!existsSync26(resolve27(root, "docs/architecture/.projection-manifest.json")))
51795
+ if (!existsSync27(resolve28(root, "docs/architecture/.projection-manifest.json")))
50423
51796
  return;
50424
51797
  const profile = loadArchitectureDocumentationProfile(root);
50425
51798
  const loaded = loadArchitectureDocumentationInputs(root, profile);
@@ -50436,6 +51809,7 @@ function completeTaskProjectionDrift(root) {
50436
51809
  profile,
50437
51810
  decisions: loaded.decisions,
50438
51811
  existingFiles: loaded.existingFiles,
51812
+ docsLanguage: loaded.docsLanguage,
50439
51813
  sourceFootprints: loadCapabilitySourceFootprintDigests(root, loaded.model, codeGraphInputs.sourceFiles),
50440
51814
  sourceScaleSignals: loadCapabilitySourceScaleSignals(root, loaded.model, codeGraphInputs.sourceFiles),
50441
51815
  importGraphs: codeGraphInputs.importGraphs,
@@ -50470,7 +51844,6 @@ function completeTaskProjectionFreshness(root) {
50470
51844
  currentSourceTreeDigest: architectureDocumentationSourceTreeDigest(root, model, sourceFiles)
50471
51845
  });
50472
51846
  }
50473
-
50474
51847
  // packages/surfaces/adapter-likec4/src/index.ts
50475
51848
  init_src();
50476
51849
  function exportLikeC4Model(model) {
@@ -50691,7 +52064,7 @@ var LOCAL_MCP_TOOLS = [
50691
52064
  {
50692
52065
  name: "archcontext_plan_update",
50693
52066
  inputSchema: MCP_TOOL_INPUT_SCHEMAS.archcontext_plan_update,
50694
- description: "Create a ChangeSet draft and preview for architecture model updates. Does not write files.",
52067
+ description: "Create a ChangeSet draft and preview for architecture model updates. When the plan writes a node file, preview.ownershipDelta lists the files that become owned, change owner, become contested or become unowned. Does not write files.",
50695
52068
  annotations: { safety: "idempotent", requiresConfirmation: false }
50696
52069
  },
50697
52070
  {
@@ -51134,6 +52507,9 @@ class RuntimeVersionUnsupportedError extends Error {
51134
52507
 
51135
52508
  class RefactorFlagError extends Error {
51136
52509
  }
52510
+
52511
+ class CliUsageError extends Error {
52512
+ }
51137
52513
  var AUDIT_RUN_STATUSES = ["pending", "issuing", "issued", "failed"];
51138
52514
  var AUDIT_GLOBAL_VALUE_FLAGS = ["--format"];
51139
52515
  var AUDIT_GLOBAL_BOOLEAN_FLAGS = ["--json"];
@@ -51141,6 +52517,17 @@ var AUDIT_RUN_VALUE_FLAGS = ["--task-session-id", "--reason", "--risk", "--uncer
51141
52517
  var AUDIT_RUN_BOOLEAN_FLAGS = ["--no-wait", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
51142
52518
  var AUDIT_APPROVE_VALUE_FLAGS = ["--run-id", "--confirm-public-repo", ...AUDIT_GLOBAL_VALUE_FLAGS];
51143
52519
  var AUDIT_APPROVE_BOOLEAN_FLAGS = ["--resume", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
52520
+ var CLI_DOCS_ANCHORS_HELP = {
52521
+ usage: "archctx docs anchors [--profile default|repo-harness/v1] [--json]",
52522
+ description: "Read-only check of citations in human-owned module document regions and node summary/responsibilities. It reports missing-path, missing-symbol, line-out-of-range and line-moved per node. It is a signal, not a gate: the result stays ok when it has findings.",
52523
+ options: {
52524
+ "--profile <profile>": "Layout whose module documents are read (default: the committed manifest profile; default without a manifest).",
52525
+ "--json": "Accepted for symmetry; output is always JSON.",
52526
+ "--help, -h": "Show help without reading the repository."
52527
+ },
52528
+ writes: "none; no daemon, no local store. Without a CodeGraph index, path and line checks still run and symbol checks are reported as unchecked (codegraph-unavailable).",
52529
+ capability: "docs-anchors-v1"
52530
+ };
51144
52531
  function refactorFlagValue(args, flag) {
51145
52532
  const index = args.indexOf(flag);
51146
52533
  if (index === -1)
@@ -51153,39 +52540,6 @@ function refactorFlagValueAt(args, index, flag) {
51153
52540
  throw new RefactorFlagError(`refactor ${flag} requires a value`);
51154
52541
  return value;
51155
52542
  }
51156
- if (__require.main == __require.module) {
51157
- if (command === "capabilities") {
51158
- const result = await runCli(command, args, process.cwd());
51159
- if ("ok" in result) {
51160
- process.stdout.write(`${renderResult(result, "json")}
51161
- `);
51162
- if (result.ok === false)
51163
- process.exitCode = 1;
51164
- } else {
51165
- process.stdout.write(`${JSON.stringify(result, null, 2)}
51166
- `);
51167
- }
51168
- } else if (command === "mcp" && args.length === 0) {
51169
- await runStdioMcpLoop(stdinLines(), (line) => process.stdout.write(`${line}
51170
- `), (line) => process.stderr.write(`${line}
51171
- `), { runtimeResolver: (root) => createOrStartRuntimeRpcClient(root) });
51172
- } else if (command === "daemon" && args[0] === "start" && args.includes("--foreground")) {
51173
- await runForegroundDaemon(process.cwd(), args).catch((error) => {
51174
- const message = error instanceof Error ? error.message : String(error);
51175
- process.stderr.write(`${message}
51176
- `);
51177
- process.exitCode = isDaemonOwnedElsewhereError(message) ? DAEMON_OWNED_ELSEWHERE_EXIT_CODE : 1;
51178
- });
51179
- } else {
51180
- const result = await runCli(command, args, process.cwd()).catch((error) => errorEnvelope("cli", "AC_RUNTIME_UNAVAILABLE", error instanceof Error ? error.message : String(error)));
51181
- process.stdout.write(`${renderResult(result, readFlag(args, "--format") ?? "json")}
51182
- `);
51183
- if (result.ok === false)
51184
- process.exitCode = 1;
51185
- if (command === "resolve")
51186
- process.exitCode = resolveCommandExitCode(result);
51187
- }
51188
- }
51189
52543
  async function* stdinLines() {
51190
52544
  let buffer = "";
51191
52545
  for await (const chunk of process.stdin) {
@@ -51271,11 +52625,13 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
51271
52625
  return (await runtime()).runtimeStatus(cwd);
51272
52626
  case "projection":
51273
52627
  return await runProjectionProtocolCommand2(args, cwd, runtime);
52628
+ case "flows":
52629
+ return runFlowsCommand(args, cwd);
51274
52630
  case "repo": {
51275
52631
  const subcommand = args[0] ?? "list";
51276
52632
  if (subcommand === "add") {
51277
52633
  const root = readFlag(args, "--root") ?? cwd;
51278
- if (resolve28(root) !== resolve28(cwd)) {
52634
+ if (resolve29(root) !== resolve29(cwd)) {
51279
52635
  return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
51280
52636
  }
51281
52637
  const daemon = await runtime();
@@ -51359,6 +52715,8 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
51359
52715
  return result.ok ? { ...result, data: paginate(result.data, args) } : result;
51360
52716
  }
51361
52717
  case "docs":
52718
+ if (args[0] === "anchors")
52719
+ return runDocsAnchorsCliCommand(args.slice(1), cwd);
51362
52720
  return await runDocsCommand(args, cwd, await runtime());
51363
52721
  case "agent-context":
51364
52722
  return await runAgentContextProjectionCommand2(args, cwd, await runtime());
@@ -51517,36 +52875,30 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
51517
52875
  revocation: "archctx tunnel --revoke"
51518
52876
  }
51519
52877
  };
52878
+ case "deps":
52879
+ return runDepsCommand(args, cwd);
51520
52880
  case "resolve": {
52881
+ if (args.includes("--help") || args.includes("-h"))
52882
+ return okEnvelope("resolve", CLI_RESOLVE_HELP);
52883
+ if (args.includes("--path") && args.includes("--paths-from")) {
52884
+ return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve accepts exactly one of --path or --paths-from");
52885
+ }
52886
+ if (args.includes("--paths-from")) {
52887
+ const read = await readResolvePathsFrom(args, cwd, deps);
52888
+ if ("problem" in read)
52889
+ return errorEnvelope("resolve", "AC_SCHEMA_INVALID", read.problem);
52890
+ const model = loadNativeModelFromArchContext(cwd);
52891
+ const outcomes = resolveArchitectureOwnersForPaths(model.nodes, read.paths);
52892
+ return okEnvelope("resolve", { results: outcomes.map((outcome, index) => resolveResultData(read.paths[index], outcome)) });
52893
+ }
51521
52894
  const path = readFlag(args, "--path");
51522
52895
  if (!path)
51523
- return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve requires --path");
52896
+ return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve requires --path or --paths-from");
51524
52897
  if (!isRepoRelativePosixPath(path)) {
51525
52898
  return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve --path must be a repository-relative POSIX path");
51526
52899
  }
51527
52900
  const model = loadNativeModelFromArchContext(cwd);
51528
- const outcome = resolveArchitectureOwnerForPath(model.nodes, path);
51529
- if (outcome.status === "matched") {
51530
- const node = outcome.node;
51531
- return okEnvelope("resolve", {
51532
- matched: true,
51533
- ambiguous: false,
51534
- stableId: node.id,
51535
- kind: node.kind,
51536
- name: node.name,
51537
- ...node.source ? { source: node.source } : {},
51538
- ...node.extensions ? { extensions: node.extensions } : {}
51539
- });
51540
- }
51541
- if (outcome.status === "ambiguous") {
51542
- return okEnvelope("resolve", {
51543
- matched: false,
51544
- ambiguous: true,
51545
- path,
51546
- candidates: outcome.candidates.map((node) => node.id)
51547
- });
51548
- }
51549
- return okEnvelope("resolve", { matched: false, ambiguous: false, path });
52901
+ return okEnvelope("resolve", resolveResultData(path, resolveArchitectureOwnerForPath(model.nodes, path)));
51550
52902
  }
51551
52903
  case "help":
51552
52904
  default:
@@ -51555,8 +52907,8 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
51555
52907
  ok: true,
51556
52908
  requestId: "help",
51557
52909
  data: {
51558
- commands: ["capabilities", "projection", "init", "sync", "validate", "context", "status", "daemon", "state", "repo", "landscape", "ledger", "book", "recommendations", "refactor", "explore", "prepare", "practices", "checkpoint", "hook", "hooks", "investigate", "agents", "jobs", "audit", "plan", "apply", "hash", "review", "complete", "github", "manifest", "config", "mcp", "install", "uninstall", "doctor", "update", "paths", "privacy-audit", "export", "import", "resolve", "tunnel"],
51559
- examples: ["archctx init --name MyApp", "archctx projection run --request-json '{...}'", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body '<complete YAML>'", "archctx plan --id changeset.<id> --op delete_entity --path .archcontext/model/flows/<flow>.yaml --expected-hash sha256:<64-hex>", "archctx hash --path .archcontext/model/nodes/<node>.yaml", "archctx plan --id changeset.<id> --operations-file <operations.json>", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body-file <entity.yaml>", "archctx apply --id changeset.<id> --approved --expected-worktree-digest <draft.base.worktreeDigest>", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx recommendations list --status rejected", "archctx refactor scan --json", "archctx refactor verify --request-json '{...}' --json", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit consent", "archctx audit consent --revoke", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<host>/<owner>/<repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel"]
52910
+ commands: ["capabilities", "projection", "init", "sync", "validate", "context", "status", "daemon", "state", "repo", "landscape", "ledger", "book", "recommendations", "refactor", "explore", "prepare", "practices", "checkpoint", "hook", "hooks", "investigate", "agents", "jobs", "audit", "plan", "apply", "hash", "review", "complete", "github", "manifest", "config", "mcp", "install", "uninstall", "doctor", "update", "paths", "privacy-audit", "export", "import", "resolve", "tunnel", "flows", "deps"],
52911
+ examples: ["archctx init --name MyApp", "archctx projection run --request-json '{...}'", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body '<complete YAML>'", "archctx plan --id changeset.<id> --op delete_entity --path .archcontext/model/flows/<flow>.yaml --expected-hash sha256:<64-hex>", "archctx hash --path .archcontext/model/nodes/<node>.yaml", "archctx plan --id changeset.<id> --operations-file <operations.json>", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body-file <entity.yaml>", "archctx apply --id changeset.<id> --approved --expected-worktree-digest <draft.base.worktreeDigest>", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx recommendations list --status rejected", "archctx refactor scan --json", "archctx refactor verify --request-json '{...}' --json", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit consent", "archctx audit consent --revoke", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<host>/<owner>/<repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx docs anchors --json", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel", "archctx refactor scan --import-node-limit 100000 --json", "archctx resolve --paths-from changed-paths.txt", "archctx flows check --json", "archctx recommendations defer --id recommendation.<id> --import-node-limit 100000 --reason 'Scanned with the same limit.'", "archctx deps --json", "archctx deps --into capability.<id> --json"]
51560
52912
  }
51561
52913
  };
51562
52914
  }
@@ -51965,7 +53317,10 @@ async function runRecommendationsCommand(args, cwd, daemon) {
51965
53317
  if (!recommendationId || recommendationId.startsWith("--")) {
51966
53318
  return errorEnvelope("recommendations.show", "AC_SCHEMA_INVALID", "recommendations show requires --id");
51967
53319
  }
51968
- return daemon.recommendations(cwd, { command: "show", recommendationId });
53320
+ const importNodeLimit = recommendationsImportNodeLimit(args);
53321
+ if (importNodeLimit === null)
53322
+ return recommendationsImportNodeLimitError("show");
53323
+ return daemon.recommendations(cwd, { command: "show", recommendationId, ...importNodeLimit === undefined ? {} : { importNodeLimit } });
51969
53324
  }
51970
53325
  if (subcommand === "list") {
51971
53326
  const status = readFlag(args, "--status");
@@ -51981,6 +53336,9 @@ async function runRecommendationsCommand(args, cwd, daemon) {
51981
53336
  const reason = readFlag(args, "--reason");
51982
53337
  if (!reason)
51983
53338
  return errorEnvelope(`recommendations.${subcommand}`, "AC_SCHEMA_INVALID", `recommendations ${subcommand} requires --reason`);
53339
+ const importNodeLimit = recommendationsImportNodeLimit(args);
53340
+ if (importNodeLimit === null)
53341
+ return recommendationsImportNodeLimitError(subcommand);
51984
53342
  const input = {
51985
53343
  command: subcommand,
51986
53344
  recommendationId,
@@ -51990,11 +53348,25 @@ async function runRecommendationsCommand(args, cwd, daemon) {
51990
53348
  ...readFlag(args, "--source") === undefined ? {} : { source: readFlag(args, "--source") },
51991
53349
  ...readFlag(args, "--expected-worktree-digest") === undefined ? {} : { expectedWorktreeDigest: readFlag(args, "--expected-worktree-digest") },
51992
53350
  ...readFlag(args, "--evidence-digest") === undefined ? {} : { evidenceDigest: readFlag(args, "--evidence-digest") },
53351
+ ...importNodeLimit === undefined ? {} : { importNodeLimit },
51993
53352
  ...readFlag(args, "--agent-job-id") === undefined ? {} : { agentJobId: readFlag(args, "--agent-job-id") },
51994
53353
  ...readFlag(args, "--now") === undefined ? {} : { now: readFlag(args, "--now") }
51995
53354
  };
51996
53355
  return daemon.recommendations(cwd, input);
51997
53356
  }
53357
+ function recommendationsImportNodeLimit(args) {
53358
+ const index = args.indexOf("--import-node-limit");
53359
+ if (index === -1)
53360
+ return;
53361
+ const raw = args[index + 1];
53362
+ return raw === undefined ? null : importNodeLimitValue(raw) ?? null;
53363
+ }
53364
+ function recommendationsImportNodeLimitError(subcommand) {
53365
+ return errorEnvelope(`recommendations.${subcommand}`, "AC_SCHEMA_INVALID", `recommendations ${subcommand} --import-node-limit must be a positive integer`);
53366
+ }
53367
+ function importNodeLimitValue(raw) {
53368
+ return /^[0-9]+$/.test(raw) && Number(raw) >= 1 ? Number(raw) : undefined;
53369
+ }
51998
53370
  async function runRefactorCommand(args, cwd, runtime) {
51999
53371
  const subcommand = args[0] ?? "scan";
52000
53372
  if (subcommand !== "scan" && subcommand !== "record" && subcommand !== "verify") {
@@ -52004,17 +53376,27 @@ async function runRefactorCommand(args, cwd, runtime) {
52004
53376
  try {
52005
53377
  if (subcommand === "scan") {
52006
53378
  const raw = refactorFlagValue(args, "--request-json");
52007
- if (raw === undefined)
53379
+ const limitRaw = refactorFlagValue(args, "--import-node-limit");
53380
+ if (raw === undefined && limitRaw === undefined)
52008
53381
  return (await runtime()).refactorScan(cwd, {});
52009
- let request;
52010
- try {
52011
- request = JSON.parse(raw);
52012
- } catch (error) {
52013
- return errorEnvelope(surface, "AC_SCHEMA_INVALID", `refactor scan --request-json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
53382
+ let request = { schemaVersion: REFACTOR_REQUEST_SCHEMA_VERSION, scope: { kind: "repository" } };
53383
+ if (raw !== undefined) {
53384
+ try {
53385
+ request = JSON.parse(raw);
53386
+ } catch (error) {
53387
+ return errorEnvelope(surface, "AC_SCHEMA_INVALID", `refactor scan --request-json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
53388
+ }
52014
53389
  }
52015
53390
  if (typeof request !== "object" || request === null || Array.isArray(request)) {
52016
53391
  return errorEnvelope(surface, "AC_SCHEMA_INVALID", "refactor scan --request-json must be a RefactorRequestV1 object");
52017
53392
  }
53393
+ if (limitRaw !== undefined) {
53394
+ const importNodeLimit = importNodeLimitValue(limitRaw);
53395
+ if (importNodeLimit === undefined) {
53396
+ return errorEnvelope(surface, "AC_SCHEMA_INVALID", "refactor scan --import-node-limit must be a positive integer");
53397
+ }
53398
+ request = { ...request, importNodeLimit };
53399
+ }
52018
53400
  const issues = refactorRequestInvariantIssues(request);
52019
53401
  if (issues.length > 0)
52020
53402
  return errorEnvelope(surface, "AC_SCHEMA_INVALID", issues.join("; "));
@@ -52034,6 +53416,14 @@ async function runRefactorCommand(args, cwd, runtime) {
52034
53416
  if (typeof request !== "object" || request === null || Array.isArray(request)) {
52035
53417
  return errorEnvelope(surface, "AC_SCHEMA_INVALID", "refactor verify --request-json must be a RefactorVerificationRequestV1 object");
52036
53418
  }
53419
+ const limitRaw = refactorFlagValue(args, "--import-node-limit");
53420
+ if (limitRaw !== undefined) {
53421
+ const importNodeLimit = importNodeLimitValue(limitRaw);
53422
+ if (importNodeLimit === undefined) {
53423
+ return errorEnvelope(surface, "AC_SCHEMA_INVALID", "refactor verify --import-node-limit must be a positive integer");
53424
+ }
53425
+ request = { ...request, importNodeLimit };
53426
+ }
52037
53427
  const issues = refactorVerificationRequestInvariantIssues(request);
52038
53428
  if (issues.length > 0)
52039
53429
  return errorEnvelope(surface, "AC_SCHEMA_INVALID", issues.join("; "));
@@ -52060,7 +53450,7 @@ async function runDocsCommand(args, cwd, daemon) {
52060
53450
  return runArchitectureDocsProjectionCommand2(args, cwd, daemon);
52061
53451
  }
52062
53452
  if (!["status", "resolve", "pin", "fetch", "purge"].includes(subcommand)) {
52063
- return errorEnvelope("docs", "AC_SCHEMA_INVALID", "docs requires status|resolve|pin|fetch|purge|plan|apply|adopt|drift|clean");
53453
+ return errorEnvelope("docs", "AC_SCHEMA_INVALID", "docs requires status|resolve|pin|fetch|purge|plan|apply|adopt|drift|clean|anchors");
52064
53454
  }
52065
53455
  if (subcommand === "status") {
52066
53456
  return daemon.docs(cwd, { command: "status", provider: "context7" });
@@ -52101,6 +53491,27 @@ async function runDocsCommand(args, cwd, daemon) {
52101
53491
  all: args.includes("--all")
52102
53492
  });
52103
53493
  }
53494
+ function runDocsAnchorsCliCommand(args, cwd) {
53495
+ if (args.includes("--help") || args.includes("-h"))
53496
+ return okEnvelope("help", CLI_DOCS_ANCHORS_HELP);
53497
+ for (let index = 0;index < args.length; index += 1) {
53498
+ const arg = args[index];
53499
+ if (arg === "--json")
53500
+ continue;
53501
+ if (arg === "--profile" && args[index + 1] !== undefined && !args[index + 1].startsWith("--")) {
53502
+ index += 1;
53503
+ continue;
53504
+ }
53505
+ return errorEnvelope("docs.anchors", "AC_SCHEMA_INVALID", `docs anchors does not accept ${arg}; use --help`);
53506
+ }
53507
+ let profile;
53508
+ try {
53509
+ profile = architectureProjectionProfile(args);
53510
+ } catch (error) {
53511
+ return errorEnvelope("docs.anchors", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
53512
+ }
53513
+ return runDocsAnchorsCommand(cwd, profile ? { profile } : {});
53514
+ }
52104
53515
  async function runArchitectureDocsProjectionCommand2(args, cwd, daemon) {
52105
53516
  const action = args[0] ?? "plan";
52106
53517
  try {
@@ -52146,6 +53557,21 @@ async function runProjectionProtocolCommand2(args, cwd, runtime) {
52146
53557
  }
52147
53558
  return (await runtime()).projection(findRepositoryRoot2(cwd), { action, request });
52148
53559
  }
53560
+ function runFlowsCommand(args, cwd) {
53561
+ if (args.includes("--help") || args.includes("-h"))
53562
+ return okEnvelope("flows", {
53563
+ command: "archctx flows check [--json]",
53564
+ description: "Check every declared flow selector against the CodeGraph index without rendering a projection.",
53565
+ statuses: "Each selector reports proven, no-direct-call, only-references, false-builtin-call, ambiguous-symbol, evidence-missing or evidence-truncated. Only proven binds a flow step.",
53566
+ writes: "none; runs in-process with no daemon, local store or task session. Like projection check, it runs codegraph sync first."
53567
+ });
53568
+ const formatAt = args.indexOf("--format");
53569
+ const unexpected = args.slice(1).filter((arg, index) => arg !== "--json" && (formatAt < 0 || index + 1 !== formatAt && index !== formatAt));
53570
+ if (args[0] !== "check" || unexpected.length > 0) {
53571
+ return errorEnvelope("flows", "AC_SCHEMA_INVALID", "flows requires check [--json]; use --help");
53572
+ }
53573
+ return runFlowSelectorCheckCommand(cwd);
53574
+ }
52149
53575
  function acceptedArchitectureChange(args) {
52150
53576
  const changeSetId = readFlag(args, "--accepted-change-set-id");
52151
53577
  const eventId = readFlag(args, "--accepted-event-id");
@@ -52520,12 +53946,12 @@ function auditManifestGateRoot(cwd) {
52520
53946
  }
52521
53947
  }
52522
53948
  function auditGithubIssuesEnabled(cwd) {
52523
- const manifestPath = resolve28(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
52524
- if (!existsSync27(manifestPath))
53949
+ const manifestPath = resolve29(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
53950
+ if (!existsSync28(manifestPath))
52525
53951
  return false;
52526
53952
  let raw;
52527
53953
  try {
52528
- raw = readFileSync27(manifestPath, "utf8");
53954
+ raw = readFileSync28(manifestPath, "utf8");
52529
53955
  } catch {
52530
53956
  return false;
52531
53957
  }
@@ -52768,7 +54194,7 @@ async function runAuditCommand(args, cwd, runtime) {
52768
54194
  const elapsedSeconds = Math.round((Date.now() - (deadline - pollTimeoutMs)) / 1000);
52769
54195
  process.stderr.write(`archctx audit run: ${jobId} is still running (${elapsedSeconds}s elapsed)...
52770
54196
  `);
52771
- await sleep(pollIntervalMs);
54197
+ await sleep2(pollIntervalMs);
52772
54198
  }
52773
54199
  return errorEnvelope("audit.run", "AC_PRECONDITION_FAILED", `archctx audit run: job ${jobId} has not reached a terminal status after ${Math.round(pollTimeoutMs / 1000)}s; this is not a failure, the daemon may still be running it \u2014 check later with: archctx audit list`);
52774
54200
  }
@@ -52907,8 +54333,8 @@ async function runGithubCommand(args, cwd, deps) {
52907
54333
  }
52908
54334
  if (subcommand === "disconnect") {
52909
54335
  const connection = await deps.githubConnectionReader?.readVerifiedConnection({ repositoryRoot: cwd });
52910
- const localRecordRemoved = existsSync27(connectionPath);
52911
- rmSync16(connectionPath, { force: true });
54336
+ const localRecordRemoved = existsSync28(connectionPath);
54337
+ rmSync15(connectionPath, { force: true });
52912
54338
  return okEnvelope("github.disconnect", {
52913
54339
  connected: connection !== undefined,
52914
54340
  localRecordRemoved,
@@ -53187,7 +54613,7 @@ function readPullRequestNumber(args) {
53187
54613
  return;
53188
54614
  const value = Number(raw);
53189
54615
  if (!Number.isInteger(value) || value < 1)
53190
- throw new Error("--pr must be a positive integer");
54616
+ throw new CliUsageError("--pr must be a positive integer");
53191
54617
  return value;
53192
54618
  }
53193
54619
  async function digestReviewChallenge(challenge) {
@@ -53219,7 +54645,7 @@ async function readReviewChallengeV2Arg(args, cwd, commandName = "github verify-
53219
54645
  if (!inline && !challengePath)
53220
54646
  return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
53221
54647
  try {
53222
- const raw = inline ?? readFileSync27(resolve28(cwd, challengePath), "utf8");
54648
+ const raw = inline ?? readFileSync28(resolve29(cwd, challengePath), "utf8");
53223
54649
  const parsed = JSON.parse(raw);
53224
54650
  const attestation = await Promise.resolve().then(() => (init_src6(), {}));
53225
54651
  const assertReviewChallengeV2 = assertReviewChallengeV22;
@@ -53600,7 +55026,7 @@ function readLatestPackageVersion(env) {
53600
55026
  if (env[LATEST_VERSION_ENV]) {
53601
55027
  return { source: "env", version: env[LATEST_VERSION_ENV] };
53602
55028
  }
53603
- const result = withLocalEgress("npm-update-check", () => spawnSync3("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
55029
+ const result = withLocalEgress("npm-update-check", () => spawnSync4("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
53604
55030
  encoding: "utf8",
53605
55031
  timeout: NPM_VIEW_TIMEOUT_MS,
53606
55032
  shell: false
@@ -53694,10 +55120,10 @@ function doctorSqlite(cwd) {
53694
55120
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
53695
55121
  return {
53696
55122
  path,
53697
- exists: existsSync27(path),
55123
+ exists: existsSync28(path),
53698
55124
  migrations: productVersionManifest().runtime.sqliteMigrations,
53699
55125
  legacyPath: paths.legacyLocalStorePath,
53700
- legacyExists: existsSync27(paths.legacyLocalStorePath),
55126
+ legacyExists: existsSync28(paths.legacyLocalStorePath),
53701
55127
  legacyLocalStore
53702
55128
  };
53703
55129
  }
@@ -53728,7 +55154,7 @@ function runtimePathsReport(cwd) {
53728
55154
  };
53729
55155
  }
53730
55156
  function pathAccess(path) {
53731
- const exists = existsSync27(path);
55157
+ const exists = existsSync28(path);
53732
55158
  return {
53733
55159
  path,
53734
55160
  exists,
@@ -53789,14 +55215,14 @@ async function createOrStartRuntimeRpcClient(cwd) {
53789
55215
  throw new RuntimeVersionUnsupportedError(fileIssue);
53790
55216
  const client = createRuntimeRpcClientFromConnectionFile(cwd);
53791
55217
  if (client) {
53792
- const health = await client.health().catch(() => {
53793
- return;
53794
- });
55218
+ const { health, unresponsivePid } = await probeDaemonHealth(client);
53795
55219
  const healthIssue = runtimeRpcCompatibilityIssueFromHealth(cwd, client, health);
53796
55220
  if (healthIssue)
53797
55221
  throw new RuntimeVersionUnsupportedError(healthIssue);
53798
55222
  if (health?.ok === true)
53799
55223
  return client;
55224
+ if (unresponsivePid !== undefined)
55225
+ throw new Error(unresponsiveDaemonMessage(unresponsivePid));
53800
55226
  recoverStaleDaemonControlFiles(cwd, { removeUnhealthyConnection: true });
53801
55227
  } else {
53802
55228
  recoverStaleDaemonControlFiles(cwd);
@@ -53878,16 +55304,16 @@ async function runDaemonCommand(args, cwd) {
53878
55304
  return okEnvelope("daemon.status", incompatibleDaemonStatus(compatibilityIssue));
53879
55305
  }
53880
55306
  const recovery = recoverStaleDaemonControlFiles(cwd);
55307
+ const lockOwnerPid = liveDaemonLockOwnerPid(cwd);
53881
55308
  return okEnvelope("daemon.status", {
53882
55309
  running: false,
53883
55310
  protocol: "http-loopback",
53884
55311
  connectionPath: defaultDaemonConnectionPath(cwd),
55312
+ ...lockOwnerPid === undefined ? {} : { lockOwnerPid, action: orphanedDaemonMessage(lockOwnerPid) },
53885
55313
  ...recoveryData(recovery)
53886
55314
  });
53887
55315
  }
53888
- const health = await client.health().catch(() => {
53889
- return;
53890
- });
55316
+ const { health, unresponsivePid } = await probeDaemonHealth(client);
53891
55317
  const healthIssue = runtimeRpcCompatibilityIssueFromHealth(cwd, client, health);
53892
55318
  if (healthIssue) {
53893
55319
  return okEnvelope("daemon.status", incompatibleDaemonStatus(healthIssue));
@@ -53906,6 +55332,16 @@ async function runDaemonCommand(args, cwd) {
53906
55332
  token: "stored-in-connection-file"
53907
55333
  });
53908
55334
  }
55335
+ if (unresponsivePid !== undefined) {
55336
+ return okEnvelope("daemon.status", {
55337
+ running: true,
55338
+ responding: false,
55339
+ pid: unresponsivePid,
55340
+ protocol: "http-loopback",
55341
+ connectionPath: client.connectionInfo().connectionPath,
55342
+ action: unresponsiveDaemonMessage(unresponsivePid)
55343
+ });
55344
+ }
53909
55345
  const recovery = recoverStaleDaemonControlFiles(cwd, { removeUnhealthyConnection: true });
53910
55346
  return okEnvelope("daemon.status", {
53911
55347
  running: false,
@@ -53945,6 +55381,9 @@ async function startBackgroundDaemon(args, cwd) {
53945
55381
  if (discovered.compatibilityIssue?.pidAlive) {
53946
55382
  return errorEnvelope("daemon.start", "AC_RUNTIME_VERSION_UNSUPPORTED", runtimeVersionUnsupportedMessage(discovered.compatibilityIssue));
53947
55383
  }
55384
+ if (discovered.unresponsivePid !== undefined) {
55385
+ return errorEnvelope("daemon.start", "AC_RUNTIME_UNAVAILABLE", unresponsiveDaemonMessage(discovered.unresponsivePid));
55386
+ }
53948
55387
  if (discovered.info) {
53949
55388
  return okEnvelope("daemon.start", {
53950
55389
  running: true,
@@ -53986,7 +55425,7 @@ async function startBackgroundDaemon(args, cwd) {
53986
55425
  child.unref();
53987
55426
  const ready = await waitForDaemonReady(cwd, daemonStartTimeoutMs(args), () => childError !== undefined || childExit !== undefined && childExit.code !== DAEMON_OWNED_ELSEWHERE_EXIT_CODE);
53988
55427
  if (!ready) {
53989
- return errorEnvelope("daemon.start", "AC_RUNTIME_UNAVAILABLE", daemonStartFailureMessage(logPath, childExit, childError));
55428
+ return errorEnvelope("daemon.start", "AC_RUNTIME_UNAVAILABLE", daemonStartFailureMessage(cwd, logPath, child.pid, childExit, childError));
53990
55429
  }
53991
55430
  return okEnvelope("daemon.start", {
53992
55431
  running: true,
@@ -54056,9 +55495,7 @@ async function discoverRunningDaemonInfo(cwd, options = {}) {
54056
55495
  recovery: options.recoverStale ? recoverStaleDaemonControlFiles(cwd) : emptyRecovery(cwd)
54057
55496
  };
54058
55497
  }
54059
- const health = await client.health().catch(() => {
54060
- return;
54061
- });
55498
+ const { health, unresponsivePid } = await probeDaemonHealth(client);
54062
55499
  const compatibilityIssue = runtimeRpcCompatibilityIssueFromHealth(cwd, client, health) ?? cliEntryStalenessIssue(cwd);
54063
55500
  if (compatibilityIssue) {
54064
55501
  return {
@@ -54070,6 +55507,9 @@ async function discoverRunningDaemonInfo(cwd, options = {}) {
54070
55507
  if (health?.ok === true) {
54071
55508
  return { info: client.connectionInfo(), compatibilityIssue: undefined, recovery: emptyRecovery(cwd) };
54072
55509
  }
55510
+ if (unresponsivePid !== undefined) {
55511
+ return { info: undefined, compatibilityIssue: undefined, unresponsivePid, recovery: emptyRecovery(cwd) };
55512
+ }
54073
55513
  return {
54074
55514
  info: undefined,
54075
55515
  compatibilityIssue: undefined,
@@ -54135,12 +55575,30 @@ function runtimeVersionUnsupportedMessage(issue) {
54135
55575
  }
54136
55576
  return `archctxd RPC version ${issue.received} is incompatible with this CLI (${issue.expected}); run ${issue.upgradeCommand} to replace the local daemon.`;
54137
55577
  }
55578
+ async function probeDaemonHealth(client) {
55579
+ try {
55580
+ return { health: await client.health() };
55581
+ } catch (error) {
55582
+ const pid = client.connectionInfo().pid;
55583
+ const busy = error instanceof RuntimeRpcTransportError && error.code === "RPC_TIMEOUT" && typeof pid === "number" && isPidAlive(pid);
55584
+ return { health: undefined, ...busy ? { unresponsivePid: pid } : {} };
55585
+ }
55586
+ }
55587
+ function stopProcessCommand(pid) {
55588
+ return process.platform === "win32" ? `taskkill /PID ${pid}` : `kill -TERM ${pid}`;
55589
+ }
55590
+ function unresponsiveDaemonMessage(pid) {
55591
+ return `archctxd pid ${pid} is alive but did not answer its health check in time; it is likely busy, so its connection file was kept. Retry later, or stop it with \`${stopProcessCommand(pid)}\` and run the command again.`;
55592
+ }
55593
+ function orphanedDaemonMessage(pid) {
55594
+ return `the daemon lock names live pid ${pid}, but no connection file points to it; stop it with \`${stopProcessCommand(pid)}\`, then run the command again.`;
55595
+ }
54138
55596
  async function waitForPidExit(pid, timeoutMs) {
54139
55597
  const deadline = Date.now() + timeoutMs;
54140
55598
  while (Date.now() < deadline) {
54141
55599
  if (!isPidAlive(pid))
54142
55600
  return true;
54143
- await sleep(50);
55601
+ await sleep2(50);
54144
55602
  }
54145
55603
  return !isPidAlive(pid);
54146
55604
  }
@@ -54169,9 +55627,11 @@ function daemonStartTimeoutMs(args) {
54169
55627
  const parsed = Number(configured);
54170
55628
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DAEMON_START_TIMEOUT_MS;
54171
55629
  }
54172
- function daemonStartFailureMessage(logPath, childExit, childError) {
55630
+ function daemonStartFailureMessage(cwd, logPath, childPid, childExit, childError) {
54173
55631
  const childState = childError ? `childError=${childError.message}` : childExit ? `childExit=${childExit.code ?? "null"}/${childExit.signal ?? "none"}` : "childState=still-running";
54174
- return `archctxd did not become ready; ${childState}; log=${logPath}; logTail=${readFileTail(logPath)}`;
55632
+ const lockOwnerPid = existsSync28(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
55633
+ const owner = lockOwnerPid === undefined || lockOwnerPid === childPid ? "" : `; ${orphanedDaemonMessage(lockOwnerPid)}`;
55634
+ return `archctxd did not become ready; ${childState}${owner}; log=${logPath}; logTail=${readFileTail(logPath)}`;
54175
55635
  }
54176
55636
  async function waitForDaemonReady(cwd, timeoutMs, shouldStop) {
54177
55637
  const deadline = Date.now() + timeoutMs;
@@ -54181,16 +55641,16 @@ async function waitForDaemonReady(cwd, timeoutMs, shouldStop) {
54181
55641
  return ready;
54182
55642
  if (shouldStop?.())
54183
55643
  return;
54184
- await sleep(50);
55644
+ await sleep2(50);
54185
55645
  }
54186
55646
  return;
54187
55647
  }
54188
- function sleep(ms) {
55648
+ function sleep2(ms) {
54189
55649
  return new Promise((resolve) => setTimeout(resolve, ms));
54190
55650
  }
54191
55651
  function readFileTail(path, maxBytes = 4096) {
54192
55652
  try {
54193
- const content = readFileSync27(path, "utf8");
55653
+ const content = readFileSync28(path, "utf8");
54194
55654
  return content.slice(Math.max(0, content.length - maxBytes)).replace(/\s+/g, " ").trim();
54195
55655
  } catch {
54196
55656
  return "<unavailable>";
@@ -54254,19 +55714,109 @@ function resolveCommandExitCode(result) {
54254
55714
  if (!result || result.ok !== true)
54255
55715
  return 1;
54256
55716
  const data = result.data;
55717
+ if (Array.isArray(data?.results))
55718
+ return data.results.some((entry) => entry.ambiguous === true) ? 2 : 0;
54257
55719
  if (data?.matched === true)
54258
55720
  return 0;
54259
55721
  if (data?.ambiguous === true)
54260
55722
  return 2;
54261
55723
  return 1;
54262
55724
  }
55725
+ var CLI_RESOLVE_HELP = {
55726
+ command: "archctx resolve (--path <repo-relative path> | --paths-from <file> | --paths-from -)",
55727
+ rule: "ADR-0043. source.exclude applies first. When every matching node lies on one parent chain, the deepest node owns the path; refactor scan uses the same rule. When matching nodes lie on different chains, an ancestor yields to its own descendant, then the longest literal source.include prefix wins, and an equal-length tie is ambiguous.",
55728
+ batch: "--paths-from reads one repository-relative POSIX path per line from a file (relative to the current directory) or from stdin (-). Blank lines are ignored and surrounding whitespace is trimmed. The model loads once. data.results has one entry per input path, in input order. The command reads no daemon state and no CodeGraph index.",
55729
+ result: "Each result carries path, matched, ambiguous and owningCapabilityId. A match adds stableId, kind, name, source and extensions of the most specific node. An ambiguous result adds candidates. owningCapabilityId is the nearest kind: capability node on the owner's parent chain (the owner itself when it is a capability), or null. For an ambiguous result it is set only when all candidates share one owning capability.",
55730
+ exitCodes: "--path: 0 matched, 1 no match or error, 2 ambiguous. --paths-from: 0 when no path is ambiguous (unowned paths included), 2 when any path is ambiguous, 1 on error."
55731
+ };
55732
+ function resolveResultData(path, outcome) {
55733
+ if (outcome.status === "matched") {
55734
+ const node = outcome.node;
55735
+ return {
55736
+ path,
55737
+ matched: true,
55738
+ ambiguous: false,
55739
+ stableId: node.id,
55740
+ kind: node.kind,
55741
+ name: node.name,
55742
+ owningCapabilityId: outcome.owningCapabilityId,
55743
+ ...node.source ? { source: node.source } : {},
55744
+ ...node.extensions ? { extensions: node.extensions } : {}
55745
+ };
55746
+ }
55747
+ if (outcome.status === "ambiguous") {
55748
+ return { path, matched: false, ambiguous: true, candidates: outcome.candidates.map((node) => node.id), owningCapabilityId: outcome.owningCapabilityId };
55749
+ }
55750
+ return { path, matched: false, ambiguous: false, owningCapabilityId: null };
55751
+ }
55752
+ async function readResolvePathsFrom(args, cwd, deps) {
55753
+ const source = readFlag(args, "--paths-from");
55754
+ if (source === undefined || source.startsWith("--"))
55755
+ return { problem: "resolve --paths-from requires a file path or - for stdin" };
55756
+ let content;
55757
+ try {
55758
+ content = source === "-" ? await (deps.readStdin ? deps.readStdin() : readFileSync28(0, "utf8")) : readFileSync28(resolve29(cwd, source), "utf8");
55759
+ } catch (error) {
55760
+ return { problem: `resolve --paths-from ${source} is not readable: ${error instanceof Error ? error.message : String(error)}` };
55761
+ }
55762
+ const paths = [];
55763
+ for (const [index, line] of content.split(`
55764
+ `).entries()) {
55765
+ const path = line.trim();
55766
+ if (path === "")
55767
+ continue;
55768
+ if (!isRepoRelativePosixPath(path))
55769
+ return { problem: `resolve --paths-from line ${index + 1} must be a repository-relative POSIX path: ${path}` };
55770
+ paths.push(path);
55771
+ }
55772
+ return { paths };
55773
+ }
54263
55774
  var CLI_PLAN_ENTITY_FLAGS = ["--op", "--path", "--expected-hash", "--body", "--body-file"];
55775
+ var CLI_DEPS_HELP = {
55776
+ command: "archctx deps [--from <node-id>] [--into <node-id>] [--import-node-limit <n>] [--edge-limit <n>] [--json]",
55777
+ description: "Read-only capability x capability import matrix. The command reads the model files, the HEAD tree and the CodeGraph index. It writes nothing and needs no daemon.",
55778
+ rule: "Each file gets its owners from the parent-chain rule refactor scan uses. Each owner rolls up to the nearest kind: capability node on its parent chain. An edge counts in data.matrix only when both files roll up to exactly one capability. Other edges count in data.imports.unattributed by cause.",
55779
+ filters: "--from <node-id> keeps edges whose importing file the node or a descendant owns. --into <node-id> keeps edges whose imported file the node or a descendant owns. An unknown node id is AC_SCHEMA_INVALID.",
55780
+ imports: "data.imports reports coverage, reasonCodes, truncated, importNodeCount, importNodeLimit and importNodeLimitExceeded from the CodeGraph import read. A missing or stale index gives coverage unknown and an empty matrix. --import-node-limit raises the read cap (default 50000).",
55781
+ calls: "Call counts are not available in v1. data.calls.coverage is unknown with reason call-edges-not-exposed, and every cell has calls: null.",
55782
+ edges: "data.edges lists the attributed file-level edges behind the matrix, sorted by path, up to --edge-limit (default 200). data.edges.count is the full total."
55783
+ };
55784
+ function runDepsCommand(args, cwd) {
55785
+ if (args.includes("--help") || args.includes("-h"))
55786
+ return okEnvelope("deps", CLI_DEPS_HELP);
55787
+ const importNodeLimit = readOptionalPositiveIntegerFlag(args, "--import-node-limit", "deps");
55788
+ if (!importNodeLimit.ok)
55789
+ return importNodeLimit.envelope;
55790
+ const edgeLimit = readOptionalNonNegativeIntegerFlag(args, "--edge-limit", "deps");
55791
+ if (!edgeLimit.ok)
55792
+ return edgeLimit.envelope;
55793
+ const from = readFlag(args, "--from");
55794
+ const into = readFlag(args, "--into");
55795
+ for (const [flag, value] of [["--from", from], ["--into", into]]) {
55796
+ if (args.includes(flag) && (value === undefined || value.startsWith("--"))) {
55797
+ return errorEnvelope("deps", "AC_SCHEMA_INVALID", `deps ${flag} requires a node id`);
55798
+ }
55799
+ }
55800
+ try {
55801
+ return okEnvelope("deps", readCapabilityDependencies(findRepositoryRoot2(cwd), {
55802
+ ...from === undefined ? {} : { from },
55803
+ ...into === undefined ? {} : { into },
55804
+ ...importNodeLimit.value === undefined ? {} : { importNodeLimit: importNodeLimit.value },
55805
+ ...edgeLimit.value === undefined ? {} : { edgeLimit: edgeLimit.value }
55806
+ }));
55807
+ } catch (error) {
55808
+ if (error instanceof CapabilityDependenciesError)
55809
+ return errorEnvelope("deps", error.code, error.message);
55810
+ throw error;
55811
+ }
55812
+ }
54264
55813
  var CLI_PLAN_HELP = {
54265
- command: "archctx plan --id <changeset.id> (--op <create_entity|update_entity_fields|delete_entity> --path <file> [--expected-hash sha256:<64-hex>] [--body <yaml> | --body - | --body-file <path>] | --operations-file <operations.json>)",
54266
- operations: "--operations-file takes a repository-relative path to a JSON array of operations in the archcontext_plan_update MCP shape and plans them as one ChangeSet; it cannot be combined with --op, --path, --expected-hash, --body or --body-file.",
54267
- body: "--body is the complete new YAML document, not a field patch. Give exactly one of --body <yaml>, --body - (stdin) or --body-file <path>. --body-file and --operations-file read only repository-relative files inside the repository (no absolute or .. paths, no symlinks); content from outside the repository must come through --body - (stdin). delete_entity accepts no body.",
55814
+ command: "archctx plan --id <changeset.id> (--op <create_entity|update_entity_fields|delete_entity> --path <file> [--expected-hash sha256:<64-hex>] [--body <yaml> | --body - | --body-file <path>] | --operations-file <operations.json | ->)",
55815
+ operations: "--operations-file takes a repository-relative path to a JSON array of operations in the archcontext_plan_update MCP shape, or - to read that array from stdin, and plans them as one ChangeSet; it cannot be combined with --op, --path, --expected-hash, --body or --body-file.",
55816
+ body: "--body is the complete new YAML document, not a field patch. Give exactly one of --body <yaml>, --body - (stdin) or --body-file <path>. --body-file and --operations-file read only repository-relative files inside the repository (no absolute or .. paths, no symlinks); content from outside the repository must come through stdin (--body - or --operations-file -). delete_entity accepts no body.",
54268
55817
  expectedHash: "digestJson({ body }) of the file's current UTF-8 contents, not sha256sum of the file. Read it with `archctx hash --path <file>`; use `missing` (the create default) for a new file.",
54269
55818
  drafts: "A draft lives in daemon memory. Planning the same --id again replaces it, and a daemon restart between plan and apply drops it: apply then fails with `Unknown ChangeSet`; plan again.",
55819
+ ownershipDelta: "When the plan writes a node file, data.preview.ownershipDelta compares file ownership before and after the planned model over the files the HEAD tree carries, with the parent-chain rule refactor scan uses. becameOwned, becameContested, ownerChanged and becameUnowned each give count, up to 50 files with before and after owners, and omittedCount. status unavailable gives the reason, for example a planned body that does not parse. Read-only.",
54270
55820
  next: "archctx apply --id <changeset.id> --approved --expected-worktree-digest <draft.base.worktreeDigest>"
54271
55821
  };
54272
55822
  var CLI_HASH_HELP = {
@@ -54295,7 +55845,7 @@ function readRepositoryInputFile(cwd, flag, file) {
54295
55845
  const absolute = assertPathHasNoSymlinkSegments2(cwd, file);
54296
55846
  if (!statSync10(absolute).isFile())
54297
55847
  return { problem: `plan ${flag} ${file} is not a regular file` };
54298
- return { content: readFileSync27(absolute, "utf8") };
55848
+ return { content: readFileSync28(absolute, "utf8") };
54299
55849
  } catch (error) {
54300
55850
  return { problem: `plan ${flag} ${file} is not readable: ${error instanceof Error ? error.message : String(error)}` };
54301
55851
  }
@@ -54313,10 +55863,10 @@ function runHashCommand(args, cwd) {
54313
55863
  } catch (error) {
54314
55864
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
54315
55865
  }
54316
- if (!existsSync27(absolute) || !statSync10(absolute).isFile()) {
55866
+ if (!existsSync28(absolute) || !statSync10(absolute).isFile()) {
54317
55867
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", `hash --path is not an existing file: ${path}`);
54318
55868
  }
54319
- return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync27(absolute, "utf8") }) });
55869
+ return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync28(absolute, "utf8") }) });
54320
55870
  }
54321
55871
  async function readCliPlanOperations(args, cwd, deps) {
54322
55872
  if (args.includes("--operations-file")) {
@@ -54326,7 +55876,7 @@ async function readCliPlanOperations(args, cwd, deps) {
54326
55876
  const file = readFlag(args, "--operations-file");
54327
55877
  if (!file)
54328
55878
  return "plan --operations-file requires a path";
54329
- const read = readRepositoryInputFile(cwd, "--operations-file", file);
55879
+ const read = file === "-" ? { content: await (deps.readStdin ? deps.readStdin() : readFileSync28(0, "utf8")) } : readRepositoryInputFile(cwd, "--operations-file", file);
54330
55880
  if ("problem" in read)
54331
55881
  return read.problem;
54332
55882
  let parsed;
@@ -54388,7 +55938,7 @@ async function readCliPlanBody(args, cwd, deps) {
54388
55938
  return { problem: "plan --body requires a value (use - to read stdin)" };
54389
55939
  if (inline !== "-")
54390
55940
  return inline;
54391
- return deps.readStdin ? deps.readStdin() : readFileSync27(0, "utf8");
55941
+ return deps.readStdin ? deps.readStdin() : readFileSync28(0, "utf8");
54392
55942
  }
54393
55943
  function readFlag(args, flag) {
54394
55944
  const index = args.indexOf(flag);
@@ -54399,7 +55949,7 @@ function readFlag(args, flag) {
54399
55949
  function requireFlag(args, flag) {
54400
55950
  const value = readFlag(args, flag);
54401
55951
  if (!value)
54402
- throw new Error(`${flag} is required`);
55952
+ throw new CliUsageError(`${flag} is required`);
54403
55953
  return value;
54404
55954
  }
54405
55955
  function copyOptionalFlag(args, flag) {
@@ -54424,11 +55974,11 @@ function explorerProjectionQueryV2FromCli(args) {
54424
55974
  const expectedObservedFactsDigest = readFlag(args, "--expected-observed-facts-digest");
54425
55975
  const expectedRequired = [expectedHeadSha, expectedWorktreeDigest, expectedGraphDigest];
54426
55976
  if (expectedRequired.some(Boolean) && !expectedRequired.every(Boolean)) {
54427
- throw new Error("--expected-head-sha, --expected-worktree-digest, and --expected-graph-digest must be provided together");
55977
+ throw new CliUsageError("--expected-head-sha, --expected-worktree-digest, and --expected-graph-digest must be provided together");
54428
55978
  }
54429
55979
  const viewId = readFlag(args, "--view") ?? "system-map";
54430
55980
  if (!EXPLORER_VIEW_IDS.includes(viewId))
54431
- throw new Error(`unsupported Explorer view: ${viewId}`);
55981
+ throw new CliUsageError(`unsupported Explorer view: ${viewId}`);
54432
55982
  return {
54433
55983
  schemaVersion: "archcontext.explorer-projection-query/v2",
54434
55984
  viewId,
@@ -54451,6 +56001,44 @@ function explorerProjectionQueryV2FromCli(args) {
54451
56001
  }
54452
56002
  };
54453
56003
  }
56004
+ function cliFailureEnvelope(error) {
56005
+ if (error instanceof CliUsageError)
56006
+ return errorEnvelope("cli", "AC_SCHEMA_INVALID", error.message);
56007
+ return errorEnvelope("cli", "AC_RUNTIME_UNAVAILABLE", error instanceof Error ? error.message : String(error));
56008
+ }
56009
+ if (__require.main == __require.module) {
56010
+ if (command === "capabilities") {
56011
+ const result = await runCli(command, args, process.cwd());
56012
+ if ("ok" in result) {
56013
+ process.stdout.write(`${renderResult(result, "json")}
56014
+ `);
56015
+ if (result.ok === false)
56016
+ process.exitCode = 1;
56017
+ } else {
56018
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
56019
+ `);
56020
+ }
56021
+ } else if (command === "mcp" && args.length === 0) {
56022
+ await runStdioMcpLoop(stdinLines(), (line) => process.stdout.write(`${line}
56023
+ `), (line) => process.stderr.write(`${line}
56024
+ `), { runtimeResolver: (root) => createOrStartRuntimeRpcClient(root) });
56025
+ } else if (command === "daemon" && args[0] === "start" && args.includes("--foreground")) {
56026
+ await runForegroundDaemon(process.cwd(), args).catch((error) => {
56027
+ const message = error instanceof Error ? error.message : String(error);
56028
+ process.stderr.write(`${message}
56029
+ `);
56030
+ process.exitCode = isDaemonOwnedElsewhereError(message) ? DAEMON_OWNED_ELSEWHERE_EXIT_CODE : 1;
56031
+ });
56032
+ } else {
56033
+ const result = await runCli(command, args, process.cwd()).catch(cliFailureEnvelope);
56034
+ process.stdout.write(`${renderResult(result, readFlag(args, "--format") ?? "json")}
56035
+ `);
56036
+ if (result.ok === false)
56037
+ process.exitCode = 1;
56038
+ if (command === "resolve")
56039
+ process.exitCode = resolveCommandExitCode(result);
56040
+ }
56041
+ }
54454
56042
  export {
54455
56043
  resolveCommandExitCode,
54456
56044
  runCapabilitiesCommand,