archctx 0.2.0 → 0.2.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
@@ -702,7 +702,7 @@ function productVersionManifest() {
702
702
  }
703
703
  };
704
704
  }
705
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.2.0", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-06-25.al0-ledger";
705
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.2.2", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.3.10", ARCHCONTEXT_NODE_RANGE = ">=24 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v1", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-06-25.al0-ledger";
706
706
  // packages/contracts/src/index.ts
707
707
  var init_src = __esm(() => {
708
708
  init_control_plane_routes();
@@ -5145,7 +5145,7 @@ var init_src7 = __esm(() => {
5145
5145
 
5146
5146
  // packages/surfaces/cli/src/main.ts
5147
5147
  import { execFileSync as execFileSync8, spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
5148
- import { accessSync, chmodSync as chmodSync5, closeSync as closeSync7, constants, existsSync as existsSync14, mkdirSync as mkdirSync9, openSync as openSync7, readFileSync as readFileSync13, rmSync as rmSync10, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
5148
+ import { accessSync, chmodSync as chmodSync5, closeSync as closeSync7, constants, existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync7, readFileSync as readFileSync13, rmSync as rmSync10, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
5149
5149
  import { dirname as dirname10, join as join10, resolve as resolve17 } from "path";
5150
5150
  import { fileURLToPath as fileURLToPath2 } from "url";
5151
5151
 
@@ -7493,7 +7493,7 @@ function planYamlToArchitectureLedgerImport(input) {
7493
7493
  payloadVersion: "archcontext.architecture-ledger-yaml-import/v1",
7494
7494
  repository: input.repository,
7495
7495
  worktree: input.worktree,
7496
- baseDigest: sourceDigest,
7496
+ baseDigest: architectureLedgerStateDigest(emptyArchitectureLedgerState()),
7497
7497
  resultingDigest: graphDigest,
7498
7498
  headSha: input.worktree.headSha,
7499
7499
  actor: { kind: "migration", id: "archctx-ledger-yaml-import" },
@@ -7677,8 +7677,6 @@ function planYamlToArchitectureLedgerRebuild(input) {
7677
7677
  const plan = planYamlToArchitectureLedgerImport(input);
7678
7678
  const previousGraphDigest = architectureLedgerStateDigest(input.previousState);
7679
7679
  const deleteOperations = architectureLedgerDeletionOperations(input.previousState, plan.state);
7680
- if (deleteOperations.length === 0)
7681
- return plan;
7682
7680
  const payload = architectureLedgerPayload(plan.event);
7683
7681
  const event = normalizeArchitectureLedgerEvent({
7684
7682
  ...plan.event,
@@ -8244,41 +8242,41 @@ function projectArchitectureLedgerStateToYamlFiles(state) {
8244
8242
  const canonical = canonicalArchitectureLedgerState(state);
8245
8243
  const files = [
8246
8244
  ...canonical.entities.map((entity) => {
8245
+ const declared = declaredProjectionRecord(entity.metadata, ["archcontext.node/v1"], `entity ${entity.entityId}`);
8246
+ const summary = entity.summary;
8247
+ if (!summary)
8248
+ throw new Error(`architecture-ledger-projection-invalid: entity ${entity.entityId} requires summary`);
8247
8249
  const body = canonicalArchitectureYaml({
8250
+ ...declared,
8248
8251
  schemaVersion: "archcontext.node/v1",
8249
8252
  id: entity.entityId,
8250
8253
  kind: entity.kind,
8251
8254
  name: entity.canonicalName,
8252
8255
  status: entity.status,
8253
- ...entity.path ? { path: entity.path } : {},
8254
- ...entity.summary ? { summary: entity.summary } : {},
8255
- ...entity.metadata ? { metadata: entity.metadata } : {}
8256
+ summary
8256
8257
  });
8257
8258
  return projectionFile(`.archcontext/model/nodes/${pathSegment(entity.entityId)}.yaml`, body, "entity", entity.entityId);
8258
8259
  }),
8259
8260
  ...canonical.relations.map((relation) => {
8260
- const body = canonicalArchitectureYaml({
8261
+ const declared = declaredProjectionRecord(relation.metadata, ["archcontext.relation/v1", "archcontext.cross-repo-relation/v1"], `relation ${relation.relationId}`);
8262
+ const schemaVersion = requireStringField(declared, "schemaVersion", `relation ${relation.relationId}`);
8263
+ const body = schemaVersion === "archcontext.cross-repo-relation/v1" ? canonicalArchitectureYaml({ ...declared, id: relation.relationId, kind: relation.kind }) : canonicalArchitectureYaml({
8264
+ ...declared,
8261
8265
  schemaVersion: "archcontext.relation/v1",
8262
8266
  id: relation.relationId,
8263
8267
  kind: relation.kind,
8264
8268
  source: relation.sourceEntityId,
8265
- target: relation.targetEntityId,
8266
- status: relation.status,
8267
- ...relation.summary ? { summary: relation.summary } : {},
8268
- ...relation.metadata ? { metadata: relation.metadata } : {}
8269
+ target: relation.targetEntityId
8269
8270
  });
8270
8271
  return projectionFile(`.archcontext/model/relations/${pathSegment(relation.relationId)}.yaml`, body, "relation", relation.relationId);
8271
8272
  }),
8272
8273
  ...canonical.constraints.map((constraint) => {
8274
+ const declared = declaredProjectionRecord(constraint.metadata, ["archcontext.constraint/v1"], `constraint ${constraint.constraintId}`);
8273
8275
  const body = canonicalArchitectureYaml({
8276
+ ...declared,
8274
8277
  schemaVersion: "archcontext.constraint/v1",
8275
8278
  id: constraint.constraintId,
8276
- kind: constraint.kind,
8277
- subject: constraint.subjectId,
8278
- status: constraint.status,
8279
- ...constraint.severity ? { severity: constraint.severity } : {},
8280
- ...constraint.summary ? { summary: constraint.summary } : {},
8281
- ...constraint.metadata ? { metadata: constraint.metadata } : {}
8279
+ ...constraint.severity ? { severity: constraint.severity } : {}
8282
8280
  });
8283
8281
  return projectionFile(`.archcontext/model/constraints/${pathSegment(constraint.constraintId)}.yaml`, body, "constraint", constraint.constraintId);
8284
8282
  })
@@ -8453,12 +8451,11 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8453
8451
  const entityId = requireStringField(value, "id", path);
8454
8452
  const entity = {
8455
8453
  entityId,
8456
- kind: stringField(value, "kind") ?? "component",
8457
- canonicalName: stringField(value, "name") ?? stringField(value, "canonicalName") ?? entityId,
8458
- status: activeStatus(value.status),
8459
- ...stringField(value, "path") ? { path: stringField(value, "path") } : {},
8460
- ...stringField(value, "summary") ? { summary: stringField(value, "summary") } : {},
8461
- ...metadataField(value, ["schemaVersion", "id", "kind", "name", "canonicalName", "status", "path", "summary"])
8454
+ kind: requireStringField(value, "kind", path),
8455
+ canonicalName: requireStringField(value, "name", path),
8456
+ status: declaredStatus(value.status, path),
8457
+ summary: requireStringField(value, "summary", path),
8458
+ metadata: { declared: value }
8462
8459
  };
8463
8460
  return { operation: { op: "upsert_entity", entity }, targetKind: "entity", targetId: entityId };
8464
8461
  }
@@ -8466,12 +8463,12 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8466
8463
  const relationId = requireStringField(value, "id", path);
8467
8464
  const relation = {
8468
8465
  relationId,
8469
- kind: stringField(value, "kind") ?? "depends_on",
8466
+ kind: requireStringField(value, "kind", path),
8470
8467
  sourceEntityId: requireStringField(value, "source", path),
8471
8468
  targetEntityId: requireStringField(value, "target", path),
8472
- status: activeStatus(value.status),
8473
- ...stringField(value, "summary") ?? stringField(value, "intent") ? { summary: stringField(value, "summary") ?? stringField(value, "intent") } : {},
8474
- ...metadataField(value, ["schemaVersion", "id", "kind", "source", "target", "status", "summary", "intent"])
8469
+ status: "active",
8470
+ summary: requireStringField(value, "intent", path),
8471
+ metadata: { declared: value }
8475
8472
  };
8476
8473
  return { operation: { op: "upsert_relation", relation }, targetKind: "relation", targetId: relationId };
8477
8474
  }
@@ -8481,25 +8478,28 @@ function yamlRecordToLedgerOperation(value, path, schemaVersion) {
8481
8478
  const target = repoScopedTarget(value.target, path, "target");
8482
8479
  const relation = {
8483
8480
  relationId,
8484
- kind: stringField(value, "kind") ?? "depends_on",
8481
+ kind: requireStringField(value, "kind", path),
8485
8482
  sourceEntityId: source,
8486
8483
  targetEntityId: target,
8487
- status: activeStatus(value.status),
8488
- ...stringField(value, "intent") ? { summary: stringField(value, "intent") } : {},
8489
- ...metadataField(value, ["schemaVersion", "id", "kind", "source", "target", "status", "intent"])
8484
+ status: "active",
8485
+ summary: requireStringField(value, "intent", path),
8486
+ metadata: { declared: value }
8490
8487
  };
8491
8488
  return { operation: { op: "upsert_relation", relation }, targetKind: "relation", targetId: relationId };
8492
8489
  }
8493
8490
  if (schemaVersion === "archcontext.constraint/v1") {
8494
8491
  const constraintId = requireStringField(value, "id", path);
8492
+ const scope = requiredRecordField(value, "scope", path);
8493
+ const rule = requiredRecordField(value, "rule", path);
8494
+ const subjectId = firstStringArrayValue(scope.nodes) ?? firstStringArrayValue(scope.relations) ?? "repository";
8495
8495
  const constraint = {
8496
8496
  constraintId,
8497
- kind: stringField(value, "kind") ?? "constraint",
8498
- subjectId: stringField(value, "subject") ?? stringField(value, "subjectId") ?? "repository",
8499
- status: activeStatus(value.status),
8500
- ...severityField(value.severity) ? { severity: severityField(value.severity) } : {},
8501
- ...stringField(value, "summary") ? { summary: stringField(value, "summary") } : {},
8502
- ...metadataField(value, ["schemaVersion", "id", "kind", "subject", "subjectId", "status", "severity", "summary"])
8497
+ kind: requireStringField(rule, "type", path),
8498
+ subjectId,
8499
+ status: "active",
8500
+ severity: requiredSeverity(value.severity, path),
8501
+ summary: requireStringField(value, "rationale", path),
8502
+ metadata: { declared: value }
8503
8503
  };
8504
8504
  return { operation: { op: "upsert_constraint", constraint }, targetKind: "constraint", targetId: constraintId };
8505
8505
  }
@@ -8636,19 +8636,30 @@ function isEvidenceOnlySchema(schemaVersion) {
8636
8636
  function isGeneratedProjectionFile(file) {
8637
8637
  return file.schemaVersion === "archcontext.generated/v1" || file.path === ".archcontext/generated" || file.path.startsWith(".archcontext/generated/") || file.body.includes("Generated by ArchContext");
8638
8638
  }
8639
- function metadataField(value, omitted) {
8640
- const metadata = {};
8641
- const explicit = value.metadata;
8642
- if (isRecord(explicit)) {
8643
- for (const key of Object.keys(explicit).sort())
8644
- metadata[key] = explicit[key];
8639
+ function declaredProjectionRecord(metadata, allowedSchemaVersions, label) {
8640
+ const declared = metadata?.declared;
8641
+ if (!isRecord(declared))
8642
+ throw new Error(`architecture-ledger-projection-invalid: ${label} has no declared YAML record`);
8643
+ const schemaVersion = requireStringField(declared, "schemaVersion", label);
8644
+ if (!allowedSchemaVersions.includes(schemaVersion)) {
8645
+ throw new Error(`architecture-ledger-projection-invalid: ${label} has unsupported declared schema ${schemaVersion}`);
8645
8646
  }
8646
- for (const key of Object.keys(value).sort()) {
8647
- if (omitted.includes(key) || key === "metadata")
8648
- continue;
8649
- metadata[key] = value[key];
8650
- }
8651
- return Object.keys(metadata).length === 0 ? {} : { metadata };
8647
+ return declared;
8648
+ }
8649
+ function requiredRecordField(value, key, path) {
8650
+ const field = value[key];
8651
+ if (!isRecord(field))
8652
+ throw new Error(`${path}: ${key} is required`);
8653
+ return field;
8654
+ }
8655
+ function firstStringArrayValue(value) {
8656
+ return Array.isArray(value) ? value.find((item) => typeof item === "string" && item.length > 0) : undefined;
8657
+ }
8658
+ function requiredSeverity(value, path) {
8659
+ const severity = severityField(value);
8660
+ if (!severity)
8661
+ throw new Error(`${path}: severity is required`);
8662
+ return severity;
8652
8663
  }
8653
8664
  function declaredYamlSubject(value, path) {
8654
8665
  const id = stringField(value, "id");
@@ -8681,8 +8692,10 @@ function requireStringField(value, key, path) {
8681
8692
  function stringField(value, key) {
8682
8693
  return typeof value[key] === "string" ? value[key] : undefined;
8683
8694
  }
8684
- function activeStatus(value) {
8685
- return value === "deprecated" || value === "removed" ? value : "active";
8695
+ function declaredStatus(value, path) {
8696
+ if (value === "active" || value === "planned" || value === "deprecated" || value === "removed")
8697
+ return value;
8698
+ throw new Error(`${path}: status is required`);
8686
8699
  }
8687
8700
  function severityField(value) {
8688
8701
  return value === "notice" || value === "warning" || value === "error" || value === "critical" ? value : undefined;
@@ -8768,10 +8781,85 @@ function validateArchitectureLedgerEvent(event) {
8768
8781
  if (event.worktree.workspaceId.length === 0 || event.worktree.storageWorkspaceId.length === 0 || event.worktree.headSha.length === 0) {
8769
8782
  throw new Error(`architecture-ledger-invalid-event: worktree identity required for ${event.eventId}`);
8770
8783
  }
8784
+ assertArchitectureLedgerPersistenceSafe(event.payload, `event.payload for ${event.eventId}`);
8785
+ if (event.extensions)
8786
+ assertArchitectureLedgerPersistenceSafe(event.extensions, `event.extensions for ${event.eventId}`);
8787
+ assertArchitectureLedgerPersistenceSafe(event.provenance, `event.provenance for ${event.eventId}`);
8771
8788
  const payload = architectureLedgerPayload(event);
8772
8789
  for (const operation of payload.operations ?? [])
8773
8790
  validateArchitectureLedgerOperation(operation, event.eventId);
8774
8791
  }
8792
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_JSON_BYTES = 262144;
8793
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_STRING_BYTES = 8192;
8794
+ var ARCHITECTURE_LEDGER_MAX_PERSISTED_DEPTH = 32;
8795
+ var ARCHITECTURE_LEDGER_FORBIDDEN_RAW_KEYS = new Set([
8796
+ "rawsource",
8797
+ "sourcebody",
8798
+ "sourcecode",
8799
+ "rawdiff",
8800
+ "diffbody",
8801
+ "rawpatch",
8802
+ "patchbody",
8803
+ "prompt",
8804
+ "promptbody",
8805
+ "completion",
8806
+ "completionbody",
8807
+ "codegraphoutput",
8808
+ "fullcodegraphoutput",
8809
+ "webhookbody",
8810
+ "rawwebhook",
8811
+ "secret",
8812
+ "secrets",
8813
+ "credential",
8814
+ "credentials",
8815
+ "privatekey",
8816
+ "accesstoken",
8817
+ "refreshtoken"
8818
+ ]);
8819
+ var ARCHITECTURE_LEDGER_SAFE_SENSITIVE_KEY_SUFFIX = /(?:digest|id|ids|count|counts|ref|refs|path|paths|persisted)$/;
8820
+ var ARCHITECTURE_LEDGER_FORBIDDEN_STRING_PATTERNS = [
8821
+ /diff --git /,
8822
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/,
8823
+ /\bgh[pousr]_[A-Za-z0-9]{20,}\b/,
8824
+ /\bsk-[A-Za-z0-9_-]{20,}\b/,
8825
+ /\bAKIA[A-Z0-9]{16}\b/,
8826
+ /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/i,
8827
+ /\b(?:api[_-]?key|secret|token|password|private[_-]?key)\s*[:=]\s*["']?[^\s"',;]{8,}/i
8828
+ ];
8829
+ function assertArchitectureLedgerPersistenceSafe(value, label = "architecture-ledger-value") {
8830
+ const encoded = JSON.stringify(value);
8831
+ if (Buffer2.byteLength(encoded, "utf8") > ARCHITECTURE_LEDGER_MAX_PERSISTED_JSON_BYTES) {
8832
+ throw new Error(`architecture-ledger-privacy-denied: persisted JSON exceeds size limit at ${label}`);
8833
+ }
8834
+ visit(value, label, 0);
8835
+ function visit(current, path, depth) {
8836
+ if (depth > ARCHITECTURE_LEDGER_MAX_PERSISTED_DEPTH) {
8837
+ throw new Error(`architecture-ledger-privacy-denied: persisted JSON exceeds depth limit at ${path}`);
8838
+ }
8839
+ if (typeof current === "string") {
8840
+ if (Buffer2.byteLength(current, "utf8") > ARCHITECTURE_LEDGER_MAX_PERSISTED_STRING_BYTES) {
8841
+ throw new Error(`architecture-ledger-privacy-denied: persisted string exceeds size limit at ${path}`);
8842
+ }
8843
+ if (ARCHITECTURE_LEDGER_FORBIDDEN_STRING_PATTERNS.some((pattern) => pattern.test(current))) {
8844
+ throw new Error(`architecture-ledger-privacy-denied: forbidden raw or secret-shaped content at ${path}`);
8845
+ }
8846
+ return;
8847
+ }
8848
+ if (current === null || typeof current !== "object")
8849
+ return;
8850
+ if (Array.isArray(current)) {
8851
+ current.forEach((item, index) => visit(item, `${path}[${index}]`, depth + 1));
8852
+ return;
8853
+ }
8854
+ for (const [key, child] of Object.entries(current)) {
8855
+ const normalizedKey = key.replace(/[-_]/g, "").toLowerCase();
8856
+ if (ARCHITECTURE_LEDGER_FORBIDDEN_RAW_KEYS.has(normalizedKey) && !ARCHITECTURE_LEDGER_SAFE_SENSITIVE_KEY_SUFFIX.test(normalizedKey)) {
8857
+ throw new Error(`architecture-ledger-privacy-denied: forbidden persisted field at ${path}.${key}`);
8858
+ }
8859
+ visit(child, `${path}.${key}`, depth + 1);
8860
+ }
8861
+ }
8862
+ }
8775
8863
  function validateArchitectureLedgerOperation(operation, eventId) {
8776
8864
  if (!operation || typeof operation !== "object" || !("op" in operation)) {
8777
8865
  throw new Error(`architecture-ledger-invalid-operation: ${eventId}`);
@@ -8894,7 +8982,7 @@ function requireNonEmpty(value, label, eventId) {
8894
8982
  }
8895
8983
  }
8896
8984
  function requireActiveStatus(value, eventId) {
8897
- if (!["active", "deprecated", "removed"].includes(value)) {
8985
+ if (!["active", "planned", "deprecated", "removed"].includes(value)) {
8898
8986
  throw new Error(`architecture-ledger-invalid-operation: invalid status for ${eventId}`);
8899
8987
  }
8900
8988
  }
@@ -8943,6 +9031,7 @@ var REQUIRED_LOCAL_STORE_TABLES = [
8943
9031
  ];
8944
9032
  var SQLITE_PRAGMAS = [
8945
9033
  "PRAGMA journal_mode = WAL",
9034
+ "PRAGMA synchronous = FULL",
8946
9035
  "PRAGMA foreign_keys = ON",
8947
9036
  "PRAGMA busy_timeout = 5000"
8948
9037
  ];
@@ -9464,6 +9553,13 @@ var LOCAL_SQLITE_MIGRATIONS = [
9464
9553
  )`,
9465
9554
  "CREATE INDEX IF NOT EXISTS idx_audit_runs_status ON audit_runs(storage_repository_id, storage_workspace_id, status)"
9466
9555
  ]
9556
+ },
9557
+ {
9558
+ id: "0011_changeset_cleanup_cursor",
9559
+ statements: [
9560
+ "ALTER TABLE changeset_journal ADD COLUMN cleanup_completed_at TEXT",
9561
+ "CREATE INDEX IF NOT EXISTS idx_changeset_journal_cleanup_pending ON changeset_journal(status, cleanup_completed_at, updated_at)"
9562
+ ]
9467
9563
  }
9468
9564
  ];
9469
9565
  var ARCHCONTEXT_STATE_DIR_ENV = "ARCHCONTEXT_STATE_DIR";
@@ -10041,17 +10137,18 @@ function readHeadSha(root) {
10041
10137
  // packages/local-runtime/runtime-daemon/src/index.ts
10042
10138
  import { randomBytes } from "node:crypto";
10043
10139
  import { execFileSync as execFileSync6 } from "node:child_process";
10044
- import { chmodSync as chmodSync3, closeSync as closeSync5, existsSync as existsSync12, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync11, rmSync as rmSync8, statSync as statSync6, writeFileSync as writeFileSync6 } from "node:fs";
10140
+ import { chmodSync as chmodSync3, closeSync as closeSync5, existsSync as existsSync13, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync11, rmSync as rmSync8, statSync as statSync6, writeFileSync as writeFileSync6 } from "node:fs";
10045
10141
  import { createServer } from "node:http";
10046
10142
  import { tmpdir as tmpdir3 } from "node:os";
10047
10143
  import { dirname as dirname8, join as join8, resolve as resolve15 } from "node:path";
10048
10144
 
10049
10145
  // packages/core/changeset-engine/src/index.ts
10050
10146
  init_src();
10051
- import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
10147
+ import { closeSync as closeSync3, existsSync as existsSync5, fsyncSync as fsyncSync2, lstatSync as lstatSync2, mkdirSync as mkdirSync3, openSync as openSync3, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
10052
10148
  import { dirname as dirname3, resolve as resolve7 } from "node:path";
10053
10149
 
10054
10150
  // packages/core/policy-engine/src/index.ts
10151
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
10055
10152
  import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve6, sep as sep3 } from "node:path";
10056
10153
  init_src();
10057
10154
  var INVALID_COMPAT_REASONS = new Set(["just in case", "safer to keep", "many internal callers", "large diff", "old code already exists"]);
@@ -10062,6 +10159,7 @@ var ALLOWLIST = [
10062
10159
  ".archcontext/practices/",
10063
10160
  ".archcontext/waivers/",
10064
10161
  ".archcontext/decisions/",
10162
+ ".archcontext/backups/",
10065
10163
  ".archcontext/generated/",
10066
10164
  "docs/architecture/"
10067
10165
  ];
@@ -10098,6 +10196,21 @@ function assertAllowedArchContextPath(root, relativePath) {
10098
10196
  if (targetFromRoot === "" || targetFromRoot === ".." || targetFromRoot.startsWith(`..${sep3}`) || isAbsolute3(targetFromRoot)) {
10099
10197
  throw new Error(`Path escapes repository: ${relativePath}`);
10100
10198
  }
10199
+ if (!existsSync4(absoluteRoot))
10200
+ return;
10201
+ const canonicalRoot = realpathSync5.native(absoluteRoot);
10202
+ let existingAncestor = absoluteTarget;
10203
+ while (!existsSync4(existingAncestor)) {
10204
+ const parent = resolve6(existingAncestor, "..");
10205
+ if (parent === existingAncestor)
10206
+ throw new Error(`Path has no existing repository ancestor: ${relativePath}`);
10207
+ existingAncestor = parent;
10208
+ }
10209
+ const canonicalAncestor = realpathSync5.native(existingAncestor);
10210
+ const ancestorFromRoot = relative4(canonicalRoot, canonicalAncestor);
10211
+ if (ancestorFromRoot === ".." || ancestorFromRoot.startsWith(`..${sep3}`) || isAbsolute3(ancestorFromRoot)) {
10212
+ throw new Error(`Path escapes repository through symlink: ${relativePath}`);
10213
+ }
10101
10214
  }
10102
10215
  function evaluateChangeSetPaths(root, paths) {
10103
10216
  const findings = [];
@@ -10157,6 +10270,7 @@ class ChangeSetEngine {
10157
10270
  if (!approved)
10158
10271
  throw new Error("ChangeSet must be approved before apply");
10159
10272
  const deps = this.requireDeps();
10273
+ await this.validateModel(root, draft, deps, "before");
10160
10274
  const backups = [];
10161
10275
  const journalId = await deps.journal?.beginChangeSet(root, draft);
10162
10276
  let journalCommitted = false;
@@ -10171,10 +10285,7 @@ class ChangeSetEngine {
10171
10285
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10172
10286
  throw new Error("fault-injection");
10173
10287
  }
10174
- } else {
10175
- this.rebuildGeneratedProjection(root, deps);
10176
- applied += 1;
10177
- }
10288
+ } else {}
10178
10289
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10179
10290
  throw new Error("fault-injection");
10180
10291
  continue;
@@ -10186,15 +10297,24 @@ class ChangeSetEngine {
10186
10297
  if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10187
10298
  throw new Error("fault-injection");
10188
10299
  }
10189
- this.rebuildGeneratedProjection(root, deps);
10190
- await this.validateModel(root, draft, deps);
10191
- await options.afterModelValidatedBeforeCommit?.({ root, draft, journalId });
10192
- if (journalId) {
10300
+ for (const projection of deps.projection.planGeneratedProjection(root)) {
10301
+ await this.applyFileOperation(root, projection.path, projection.expectedHash, projection.body, projection.operation, backups, journalId, applied + 1);
10302
+ applied += 1;
10303
+ if (options.faultAfterOperations && applied >= options.faultAfterOperations)
10304
+ throw new Error("fault-injection");
10305
+ }
10306
+ await this.validateModel(root, draft, deps, "after");
10307
+ const commit = await options.afterModelValidatedBeforeCommit?.({ root, draft, journalId });
10308
+ if (commit?.journalCommitted)
10309
+ journalCommitted = true;
10310
+ if (journalId && !journalCommitted) {
10193
10311
  await deps.journal?.commitChangeSet(journalId);
10194
10312
  journalCommitted = true;
10195
10313
  }
10196
10314
  try {
10197
10315
  cleanupBackups(backups);
10316
+ if (journalId)
10317
+ await deps.journal?.completeChangeSetCleanup(journalId);
10198
10318
  } catch {}
10199
10319
  const appliedDraft = { ...draft, status: "applied" };
10200
10320
  this.states.set(draft.id, appliedDraft);
@@ -10210,11 +10330,11 @@ class ChangeSetEngine {
10210
10330
  throw error;
10211
10331
  }
10212
10332
  }
10213
- rebuildGeneratedProjection(root, deps) {
10214
- deps.projection.rebuildGeneratedProjection(root);
10215
- }
10216
- async validateModel(root, draft, deps) {
10217
- await deps.modelStore.validateModel({ root, repositoryId: draft.reason.taskSessionId, headSha: draft.base.headSha });
10333
+ async validateModel(root, draft, deps, phase) {
10334
+ const result = await deps.modelStore.validateModel({ root, repositoryId: draft.reason.taskSessionId, headSha: draft.base.headSha });
10335
+ if (!result.valid) {
10336
+ throw new Error(`ChangeSet model validation failed ${phase} apply: ${result.errors.join("; ") || "unknown validation error"}`);
10337
+ }
10218
10338
  }
10219
10339
  requireDeps() {
10220
10340
  if (!this.deps)
@@ -10225,19 +10345,16 @@ class ChangeSetEngine {
10225
10345
  assertSafeTarget(root, path);
10226
10346
  const deps = this.requireDeps();
10227
10347
  const absolute = resolve7(root, path);
10228
- const existed = existsSync4(absolute);
10348
+ const existed = existsSync5(absolute);
10229
10349
  const backupPath = `${absolute}.archctx-backup`;
10230
10350
  const tempPath = operation === "delete_entity" ? undefined : `${absolute}.archctx-tmp-${process.pid}-${sequence}`;
10231
- if (existsSync4(backupPath))
10351
+ if (existsSync5(backupPath))
10232
10352
  throw new Error(`Backup path already exists: ${path}`);
10233
- if (existed) {
10353
+ if (existed)
10234
10354
  assertExpectedHash(absolute, expectedHash);
10235
- renameSync2(absolute, backupPath);
10236
- fsyncDirectory2(dirname3(absolute));
10237
- } else if (expectedHash !== "missing") {
10355
+ else if (expectedHash !== "missing")
10238
10356
  throw new Error(`Expected missing file hash for new path: ${path}`);
10239
- }
10240
- backups.push({ path: absolute, backupPath, tempPath, existed });
10357
+ const backup = { path: absolute, backupPath, tempPath, existed };
10241
10358
  if (journalId) {
10242
10359
  await deps.journal?.recordChangeSetFile(journalId, {
10243
10360
  path,
@@ -10247,6 +10364,11 @@ class ChangeSetEngine {
10247
10364
  operation
10248
10365
  });
10249
10366
  }
10367
+ backups.push(backup);
10368
+ if (existed) {
10369
+ renameSync2(absolute, backupPath);
10370
+ fsyncDirectory2(dirname3(absolute));
10371
+ }
10250
10372
  if (operation === "delete_entity") {
10251
10373
  rmSync3(absolute, { force: true });
10252
10374
  } else {
@@ -10257,7 +10379,7 @@ class ChangeSetEngine {
10257
10379
  function assertSafeTarget(root, path) {
10258
10380
  assertAllowedArchContextPath(root, path);
10259
10381
  const absolute = resolve7(root, path);
10260
- if (existsSync4(absolute) && lstatSync2(absolute).isSymbolicLink()) {
10382
+ if (existsSync5(absolute) && lstatSync2(absolute).isSymbolicLink()) {
10261
10383
  throw new Error(`Refusing to write symlink target: ${path}`);
10262
10384
  }
10263
10385
  }
@@ -10332,9 +10454,14 @@ function rollback(backups) {
10332
10454
  for (const backup of backups.reverse()) {
10333
10455
  if (backup.tempPath)
10334
10456
  rmSync3(backup.tempPath, { recursive: true, force: true });
10335
- rmSync3(backup.path, { recursive: true, force: true });
10336
- if (backup.existed && existsSync4(backup.backupPath))
10337
- renameSync2(backup.backupPath, backup.path);
10457
+ if (backup.existed) {
10458
+ if (existsSync5(backup.backupPath)) {
10459
+ rmSync3(backup.path, { recursive: true, force: true });
10460
+ renameSync2(backup.backupPath, backup.path);
10461
+ }
10462
+ } else {
10463
+ rmSync3(backup.path, { recursive: true, force: true });
10464
+ }
10338
10465
  fsyncDirectory2(dirname3(backup.path));
10339
10466
  }
10340
10467
  }
@@ -10540,7 +10667,7 @@ function digestSuffix2(digest) {
10540
10667
  }
10541
10668
 
10542
10669
  // packages/core/application/src/index.ts
10543
- import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
10670
+ import { existsSync as existsSync8, statSync as statSync5 } from "node:fs";
10544
10671
  import { extname, resolve as resolve10 } from "node:path";
10545
10672
 
10546
10673
  // packages/core/context-compiler/src/index.ts
@@ -10548,7 +10675,7 @@ init_src();
10548
10675
 
10549
10676
  // packages/core/practice-catalog/src/index.ts
10550
10677
  init_src();
10551
- import { existsSync as existsSync5, lstatSync as lstatSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync5 } from "node:fs";
10678
+ import { existsSync as existsSync6, lstatSync as lstatSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
10552
10679
  import { dirname as dirname4, relative as relative5, resolve as resolve8, sep as sep4 } from "node:path";
10553
10680
  import { fileURLToPath } from "node:url";
10554
10681
  var PRACTICE_CATALOG_VERSION = "2026.06.0";
@@ -10797,7 +10924,7 @@ function loadProfileFiles(dir, errors) {
10797
10924
  }
10798
10925
  function loadRepoOverlayAssets(root, errors) {
10799
10926
  const overlayRoot = resolve8(root, ".archcontext/practices");
10800
- if (!existsSync5(overlayRoot))
10927
+ if (!existsSync6(overlayRoot))
10801
10928
  return [];
10802
10929
  const out = [];
10803
10930
  for (const path of listDataFiles(overlayRoot, overlayRoot, errors)) {
@@ -11140,7 +11267,7 @@ function validateProfile(value, path, errors) {
11140
11267
  return invalid ? undefined : profile;
11141
11268
  }
11142
11269
  function listDataFiles(dir, allowedRoot, errors) {
11143
- if (!existsSync5(dir))
11270
+ if (!existsSync6(dir))
11144
11271
  return [];
11145
11272
  const out = [];
11146
11273
  const rootReal = safeRealpath(allowedRoot);
@@ -11202,7 +11329,7 @@ function assertRealChild(root, path) {
11202
11329
  }
11203
11330
  function safeRealpath(path) {
11204
11331
  try {
11205
- return realpathSync5.native(path);
11332
+ return realpathSync6.native(path);
11206
11333
  } catch {
11207
11334
  return resolve8(path);
11208
11335
  }
@@ -11773,7 +11900,7 @@ function checkResult(input, result) {
11773
11900
  }
11774
11901
  // packages/core/practice-engine/src/enforcement.ts
11775
11902
  init_src();
11776
- import { existsSync as existsSync6, lstatSync as lstatSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
11903
+ import { existsSync as existsSync7, lstatSync as lstatSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
11777
11904
  import { basename as basename3, join as join4, relative as relative6, resolve as resolve9, sep as sep5 } from "node:path";
11778
11905
  var ENFORCEMENT_RANK = { advisory: 0, checkpoint: 1, complete: 2 };
11779
11906
  var POLICY_MODES = new Set(["advisory", "active", "fail-open", "fail-closed"]);
@@ -11798,7 +11925,7 @@ function shouldEvaluatePracticeEnforcement(policy) {
11798
11925
  }
11799
11926
  function loadPracticeEnforcementPolicy(root) {
11800
11927
  const path = resolve9(root, ".archcontext/policies/practices.yaml");
11801
- if (!existsSync6(path))
11928
+ if (!existsSync7(path))
11802
11929
  return defaultPracticeEnforcementPolicy();
11803
11930
  assertRepoPolicyFile(root, path, ".archcontext/policies/practices.yaml");
11804
11931
  const parsed = parseJsonYamlFile(path);
@@ -11806,7 +11933,7 @@ function loadPracticeEnforcementPolicy(root) {
11806
11933
  }
11807
11934
  function loadPracticeWaivers(root) {
11808
11935
  const dir = resolve9(root, ".archcontext/waivers");
11809
- if (!existsSync6(dir))
11936
+ if (!existsSync7(dir))
11810
11937
  return [];
11811
11938
  const stat = lstatSync4(dir);
11812
11939
  if (stat.isSymbolicLink())
@@ -11829,7 +11956,7 @@ function loadPracticeWaiverOwnerRegistry(root) {
11829
11956
  const modelDir = resolve9(root, ".archcontext/model/nodes");
11830
11957
  const sources = [];
11831
11958
  const subjects = [];
11832
- if (existsSync6(modelDir)) {
11959
+ if (existsSync7(modelDir)) {
11833
11960
  if (lstatSync4(modelDir).isSymbolicLink())
11834
11961
  throw new Error("practice-owner-registry-symlink-denied");
11835
11962
  for (const path of collectFiles(root, ".archcontext/model/nodes")) {
@@ -13642,7 +13769,7 @@ function classifyCheckpointPath(root, path) {
13642
13769
  if (isGeneratedCheckpointPath(path))
13643
13770
  return "generated";
13644
13771
  const absolute = resolve10(root, path);
13645
- if (!existsSync7(absolute))
13772
+ if (!existsSync8(absolute))
13646
13773
  return "deleted";
13647
13774
  try {
13648
13775
  if (!statSync5(absolute).isFile())
@@ -14848,8 +14975,14 @@ function uniqueSorted4(values) {
14848
14975
 
14849
14976
  // packages/core/projection-engine/src/index.ts
14850
14977
  init_src();
14851
- import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "node:fs";
14978
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "node:fs";
14852
14979
  import { basename as basename4, resolve as resolve11 } from "node:path";
14980
+ function nativeNodeSource(node) {
14981
+ const value = node.source;
14982
+ if (!value || typeof value !== "object" || Array.isArray(value))
14983
+ return;
14984
+ return value;
14985
+ }
14853
14986
  var ARCHITECTURE_DOCS_RENDERER_VERSION = "archcontext.docs-renderer/v1";
14854
14987
  var ARCHITECTURE_DOCS_GENERATED_BEGIN_PREFIX = "<!-- BEGIN ARCHCONTEXT:generated";
14855
14988
  var ARCHITECTURE_DOCS_GENERATED_END_PREFIX = "<!-- END ARCHCONTEXT:generated";
@@ -14885,12 +15018,6 @@ function renderArchitectureDocumentationProjection(input) {
14885
15018
  });
14886
15019
  const targets = rendered.map((file) => file.target);
14887
15020
  const expectedByPath = new Map(rendered.map((file) => [file.path, file]));
14888
- const drift = architectureDocumentationProjectionDrift({
14889
- targets,
14890
- expectedFiles: rendered,
14891
- existingFiles: input.existingFiles ?? []
14892
- });
14893
- const rejected = drift.diffs.filter((diff) => diff.reasonCode === "projection-ambiguous-ownership");
14894
15021
  const projectionDigest = digestJson({
14895
15022
  rendererVersion,
14896
15023
  sourceDigest: input.sourceDigest,
@@ -14901,6 +15028,38 @@ function renderArchitectureDocumentationProjection(input) {
14901
15028
  generatedBodyDigest: file.generatedBodyDigest
14902
15029
  })).sort((left, right) => left.path.localeCompare(right.path))
14903
15030
  });
15031
+ const manifestValue = {
15032
+ schemaVersion: "archcontext.architecture-docs-projection-manifest/v1",
15033
+ rendererVersion,
15034
+ sourceDigest: input.sourceDigest,
15035
+ projectionDigest,
15036
+ targetCount: targets.length,
15037
+ fileCount: rendered.length,
15038
+ targets: targets.map((target) => ({
15039
+ targetId: target.targetId,
15040
+ type: target.type,
15041
+ scope: target.scope,
15042
+ path: target.path,
15043
+ ownership: target.ownership,
15044
+ rendererVersion: target.rendererVersion,
15045
+ format: target.format,
15046
+ sourceDigest: target.sourceDigest,
15047
+ outputDigest: target.outputDigest
15048
+ }))
15049
+ };
15050
+ const manifest = {
15051
+ path: "docs/architecture/.projection-manifest.json",
15052
+ body: `${JSON.stringify(manifestValue, null, 2)}
15053
+ `,
15054
+ digest: digestJson(manifestValue)
15055
+ };
15056
+ const drift = architectureDocumentationProjectionDrift({
15057
+ targets,
15058
+ expectedFiles: rendered,
15059
+ expectedManifest: manifest,
15060
+ existingFiles: input.existingFiles ?? []
15061
+ });
15062
+ const rejected = drift.diffs.filter((diff) => diff.reasonCode === "projection-ambiguous-ownership");
14904
15063
  return {
14905
15064
  schemaVersion: "archcontext.architecture-docs-projection-plan/v1",
14906
15065
  rendererVersion,
@@ -14908,6 +15067,7 @@ function renderArchitectureDocumentationProjection(input) {
14908
15067
  projectionDigest,
14909
15068
  targets,
14910
15069
  files: rendered.filter((file) => !rejected.some((diff) => diff.path === file.path && diff.targetId === file.target.targetId)),
15070
+ manifest,
14911
15071
  drift: {
14912
15072
  ...drift,
14913
15073
  diffs: drift.diffs.map((diff) => ({
@@ -14938,7 +15098,7 @@ function architectureDocumentationSourceDigest(input) {
14938
15098
  }
14939
15099
  function loadArchitectureDecisionRecords(root) {
14940
15100
  const dir = resolve11(root, "docs/adr");
14941
- if (!existsSync8(dir))
15101
+ if (!existsSync9(dir))
14942
15102
  return [];
14943
15103
  return readdirSync6(dir).filter((file) => /^ADR-\d{4}-.+\.md$/.test(file)).sort().map((file) => {
14944
15104
  const path = `docs/adr/${file}`;
@@ -14965,12 +15125,12 @@ function loadArchitectureDocumentationFiles(root) {
14965
15125
  "docs/architecture/.projection-manifest.json"
14966
15126
  ]) {
14967
15127
  const absolute = resolve11(root, entry);
14968
- if (existsSync8(absolute))
15128
+ if (existsSync9(absolute))
14969
15129
  files.push({ path: entry, body: readFileSync8(absolute, "utf8") });
14970
15130
  }
14971
15131
  for (const dir of ["docs/architecture/modules", "docs/architecture/relations"]) {
14972
15132
  const absoluteDir = resolve11(root, dir);
14973
- if (!existsSync8(absoluteDir))
15133
+ if (!existsSync9(absoluteDir))
14974
15134
  continue;
14975
15135
  for (const file of readdirSync6(absoluteDir).filter((name) => name.endsWith(".md")).sort()) {
14976
15136
  const path = `${dir}/${file}`;
@@ -15076,25 +15236,12 @@ function mermaidId(id) {
15076
15236
  return stableId(id).replace(/-/g, "_").replace(/\./g, "_");
15077
15237
  }
15078
15238
  function readYamlObjects(dir) {
15079
- if (!existsSync8(dir))
15239
+ if (!existsSync9(dir))
15080
15240
  return [];
15081
- return readdirSync6(dir).filter((file) => /\.ya?ml$/.test(file)).sort().map((file) => parseFlatYaml(readFileSync8(resolve11(dir, file), "utf8")));
15082
- }
15083
- function parseFlatYaml(body) {
15084
- const out = {};
15085
- for (const line of body.split(/\r?\n/)) {
15086
- const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
15087
- if (!match)
15088
- continue;
15089
- const [, key, raw] = match;
15090
- if (raw.startsWith('"') && raw.endsWith('"'))
15091
- out[key] = JSON.parse(raw);
15092
- else if (raw === "true" || raw === "false")
15093
- out[key] = raw === "true";
15094
- else
15095
- out[key] = raw;
15096
- }
15097
- return out;
15241
+ return readdirSync6(dir).filter((file) => /\.ya?ml$/.test(file)).sort().map((file) => {
15242
+ const path = resolve11(dir, file);
15243
+ return parseJsonOrStableYaml(readFileSync8(path, "utf8"), path);
15244
+ });
15098
15245
  }
15099
15246
  function escapeMermaid(value) {
15100
15247
  return value.replace(/"/g, "'");
@@ -15324,6 +15471,34 @@ function architectureDocumentationProjectionDrift(input) {
15324
15471
  const expectedByPath = new Map(input.expectedFiles.map((file) => [file.path, file]));
15325
15472
  const targetIds = new Set(input.targets.map((target) => target.targetId));
15326
15473
  const diffs = [];
15474
+ const existingManifest = existingByPath.get(input.expectedManifest.path);
15475
+ if (!existingManifest) {
15476
+ diffs.push({
15477
+ path: input.expectedManifest.path,
15478
+ reasonCode: "projection-manifest-missing",
15479
+ expectedDigest: input.expectedManifest.digest
15480
+ });
15481
+ } else {
15482
+ try {
15483
+ const parsed = JSON.parse(existingManifest.body);
15484
+ const actualDigest = digestJson(parsed);
15485
+ if (actualDigest !== input.expectedManifest.digest) {
15486
+ diffs.push({
15487
+ path: input.expectedManifest.path,
15488
+ reasonCode: "projection-manifest-stale",
15489
+ expectedDigest: input.expectedManifest.digest,
15490
+ actualDigest
15491
+ });
15492
+ }
15493
+ } catch {
15494
+ diffs.push({
15495
+ path: input.expectedManifest.path,
15496
+ reasonCode: "projection-manifest-invalid",
15497
+ expectedDigest: input.expectedManifest.digest,
15498
+ actualDigest: digestJson({ path: existingManifest.path, body: existingManifest.body })
15499
+ });
15500
+ }
15501
+ }
15327
15502
  for (const expected of input.expectedFiles) {
15328
15503
  const existing = existingByPath.get(expected.path);
15329
15504
  if (!existing) {
@@ -15362,6 +15537,8 @@ function architectureDocumentationProjectionDrift(input) {
15362
15537
  }
15363
15538
  }
15364
15539
  for (const existing of input.existingFiles) {
15540
+ if (existing.path === input.expectedManifest.path)
15541
+ continue;
15365
15542
  if (!isManagedArchitectureDocumentationPath(existing.path) || expectedByPath.has(existing.path))
15366
15543
  continue;
15367
15544
  const region = findAnyGeneratedRegion(existing.body);
@@ -15410,7 +15587,7 @@ function generatedEndMarker(targetId) {
15410
15587
  return `${ARCHITECTURE_DOCS_GENERATED_END_PREFIX} target="${targetId}" -->`;
15411
15588
  }
15412
15589
  function isManagedArchitectureDocumentationPath(path) {
15413
- return /^docs\/architecture\/(modules|relations)\/.+\.md$/.test(path) || /^docs\/architecture\/diagrams\/architecture\.(mmd|likec4|structurizr\.json)$/.test(path) || path === "docs/architecture/index.md" || path === "docs/architecture/changelog.md" || path === "docs/architecture/decisions/index.md";
15590
+ return /^docs\/architecture\/(modules|relations)\/.+\.md$/.test(path) || /^docs\/architecture\/diagrams\/architecture\.(mmd|likec4|structurizr\.json)$/.test(path) || path === "docs/architecture/index.md" || path === "docs/architecture/changelog.md" || path === "docs/architecture/decisions/index.md" || path === "docs/architecture/.projection-manifest.json";
15414
15591
  }
15415
15592
  function pathSegment2(id) {
15416
15593
  return stableId(id).replace(/\./g, "-");
@@ -15441,6 +15618,61 @@ function escapeDsl(value) {
15441
15618
  function escapeRegExp(value) {
15442
15619
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15443
15620
  }
15621
+ function resolveArchitectureOwnerForPath(nodes, path) {
15622
+ const candidates = [];
15623
+ for (const node of nodes) {
15624
+ const source = nativeNodeSource(node);
15625
+ const include = source?.include ?? [];
15626
+ if (include.length === 0)
15627
+ continue;
15628
+ const excluded = (source?.exclude ?? []).some((pattern) => matchesGlob(path, pattern));
15629
+ if (excluded)
15630
+ continue;
15631
+ const specificity = include.filter((pattern) => matchesGlob(path, pattern)).reduce((max, pattern) => Math.max(max, globLiteralPrefixLength(pattern)), -1);
15632
+ if (specificity >= 0)
15633
+ candidates.push({ node, specificity });
15634
+ }
15635
+ if (candidates.length === 0)
15636
+ return { status: "no-match" };
15637
+ const maxSpecificity = candidates.reduce((max, candidate) => Math.max(max, candidate.specificity), -1);
15638
+ const winners = candidates.filter((candidate) => candidate.specificity === maxSpecificity).map((candidate) => candidate.node);
15639
+ if (winners.length > 1)
15640
+ return { status: "ambiguous", candidates: winners };
15641
+ return { status: "matched", node: winners[0] };
15642
+ }
15643
+ function matchesGlob(path, pattern) {
15644
+ return globToRegExp(pattern).test(path);
15645
+ }
15646
+ function globLiteralPrefixLength(pattern) {
15647
+ const index = pattern.search(/[*?]/);
15648
+ return index === -1 ? pattern.length : index;
15649
+ }
15650
+ function globToRegExp(pattern) {
15651
+ let out = "";
15652
+ let index = 0;
15653
+ while (index < pattern.length) {
15654
+ if (pattern.startsWith("**/", index)) {
15655
+ out += "(?:.*/)?";
15656
+ index += 3;
15657
+ continue;
15658
+ }
15659
+ if (pattern.startsWith("**", index)) {
15660
+ out += ".*";
15661
+ index += 2;
15662
+ continue;
15663
+ }
15664
+ const char = pattern[index];
15665
+ if (char === "*") {
15666
+ out += "[^/]*";
15667
+ } else if (char === "?") {
15668
+ out += "[^/]";
15669
+ } else {
15670
+ out += escapeRegExp(char);
15671
+ }
15672
+ index += 1;
15673
+ }
15674
+ return new RegExp(`^${out}$`);
15675
+ }
15444
15676
 
15445
15677
  // packages/local-runtime/explorer-html/src/index.ts
15446
15678
  var STATUS_STYLE = {
@@ -16565,7 +16797,7 @@ init_src();
16565
16797
 
16566
16798
  // packages/local-runtime/git-adapter/src/index.ts
16567
16799
  import { execFileSync as execFileSync4, spawnSync as spawnSync2 } from "node:child_process";
16568
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
16800
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
16569
16801
  import { dirname as dirname5, join as join5, resolve as resolve12 } from "node:path";
16570
16802
  import { tmpdir } from "node:os";
16571
16803
  init_src();
@@ -16579,7 +16811,7 @@ function findRepositoryRoot2(start) {
16579
16811
  } catch {
16580
16812
  let cursor = resolve12(start);
16581
16813
  while (true) {
16582
- if (existsSync9(resolve12(cursor, ".git")))
16814
+ if (existsSync10(resolve12(cursor, ".git")))
16583
16815
  return cursor;
16584
16816
  const parent = dirname5(cursor);
16585
16817
  if (parent === cursor)
@@ -16850,7 +17082,7 @@ function isGitWorktreeError(error) {
16850
17082
  // packages/local-runtime/local-store-sqlite/src/index.ts
16851
17083
  import { execFileSync as execFileSync5 } from "node:child_process";
16852
17084
  import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
16853
- import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync10, fsyncSync as fsyncSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync6, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "node:fs";
17085
+ import { chmodSync as chmodSync2, closeSync as closeSync4, existsSync as existsSync11, fsyncSync as fsyncSync3, lstatSync as lstatSync5, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync3 } from "node:fs";
16854
17086
  import { readdir, readFile } from "node:fs/promises";
16855
17087
  import { createRequire as createRequire3 } from "node:module";
16856
17088
  import { homedir as homedir2 } from "node:os";
@@ -16893,6 +17125,7 @@ var REQUIRED_LOCAL_STORE_TABLES2 = [
16893
17125
  ];
16894
17126
  var SQLITE_PRAGMAS2 = [
16895
17127
  "PRAGMA journal_mode = WAL",
17128
+ "PRAGMA synchronous = FULL",
16896
17129
  "PRAGMA foreign_keys = ON",
16897
17130
  "PRAGMA busy_timeout = 5000"
16898
17131
  ];
@@ -17414,8 +17647,16 @@ var LOCAL_SQLITE_MIGRATIONS2 = [
17414
17647
  )`,
17415
17648
  "CREATE INDEX IF NOT EXISTS idx_audit_runs_status ON audit_runs(storage_repository_id, storage_workspace_id, status)"
17416
17649
  ]
17650
+ },
17651
+ {
17652
+ id: "0011_changeset_cleanup_cursor",
17653
+ statements: [
17654
+ "ALTER TABLE changeset_journal ADD COLUMN cleanup_completed_at TEXT",
17655
+ "CREATE INDEX IF NOT EXISTS idx_changeset_journal_cleanup_pending ON changeset_journal(status, cleanup_completed_at, updated_at)"
17656
+ ]
17417
17657
  }
17418
17658
  ];
17659
+ var CHANGESET_STARTUP_CLEANUP_LIMIT = 100;
17419
17660
  var ARCHCONTEXT_STATE_DIR_ENV2 = "ARCHCONTEXT_STATE_DIR";
17420
17661
  var ARCHCONTEXT_LOCAL_STORE_PATH_ENV2 = "ARCHCONTEXT_LOCAL_STORE_PATH";
17421
17662
  function defaultArchContextStateRoot2(env = process.env, platform = process.platform, home = homedir2()) {
@@ -17474,10 +17715,10 @@ function migrateLegacyLocalStoreIfNeeded2(root = process.cwd(), env = process.en
17474
17715
  });
17475
17716
  }
17476
17717
  ensurePrivateDir2(dirname6(paths.localStorePath));
17477
- const legacyExists = existsSync10(paths.legacyLocalStorePath);
17718
+ const legacyExists = existsSync11(paths.legacyLocalStorePath);
17478
17719
  const integrityCheck = {};
17479
17720
  const quarantinedFiles = [];
17480
- const targetExists = existsSync10(paths.localStorePath);
17721
+ const targetExists = existsSync11(paths.localStorePath);
17481
17722
  if (targetExists) {
17482
17723
  try {
17483
17724
  integrityCheck.target = assertCurrentLocalStore2(paths.localStorePath);
@@ -17941,6 +18182,11 @@ class SqliteLocalStore {
17941
18182
  throw new Error(`ChangeSet journal not found: ${journalId}`);
17942
18183
  db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", nowIso2(), nowIso2(), journalId);
17943
18184
  }
18185
+ async completeChangeSetCleanup(journalId) {
18186
+ const db = await this.database();
18187
+ const completedAt = nowIso2();
18188
+ db.prepare("UPDATE changeset_journal SET cleanup_completed_at = ?, updated_at = ? WHERE journal_id = ? AND status = ?").run(completedAt, completedAt, journalId, "committed");
18189
+ }
17944
18190
  async abortChangeSet(journalId, reason) {
17945
18191
  const db = await this.database();
17946
18192
  const row = db.prepare("SELECT metadata_json FROM changeset_journal WHERE journal_id = ?").get(journalId);
@@ -17951,37 +18197,61 @@ class SqliteLocalStore {
17951
18197
  }
17952
18198
  recoverPendingChangeSets() {
17953
18199
  const db = this.requireOpenDatabase();
17954
- const committed = db.prepare("SELECT files_json FROM changeset_journal WHERE status = ?").all("committed");
18200
+ const committed = db.prepare(`SELECT journal_id, files_json FROM changeset_journal
18201
+ WHERE status = ? AND cleanup_completed_at IS NULL
18202
+ ORDER BY updated_at, journal_id
18203
+ LIMIT ?`).all("committed", CHANGESET_STARTUP_CLEANUP_LIMIT);
17955
18204
  for (const row of committed) {
17956
- cleanupCommittedJournalFiles(JSON.parse(String(row.files_json)));
18205
+ try {
18206
+ cleanupCommittedJournalFiles(JSON.parse(String(row.files_json)));
18207
+ const completedAt = nowIso2();
18208
+ db.prepare("UPDATE changeset_journal SET cleanup_completed_at = ?, updated_at = ? WHERE journal_id = ?").run(completedAt, completedAt, String(row.journal_id));
18209
+ } catch {}
17957
18210
  }
17958
18211
  const rows = db.prepare("SELECT journal_id, root, files_json, metadata_json FROM changeset_journal WHERE status = ?").all("pending");
18212
+ let recovered = 0;
17959
18213
  for (const row of rows) {
17960
- const files = JSON.parse(String(row.files_json));
17961
- const metadata = JSON.parse(String(row.metadata_json));
17962
- const plannedLedgerEvent = changeSetJournalPlannedLedgerEvent(metadata);
17963
- const existingLedgerEvent = plannedLedgerEvent ? architectureEventByIdempotency(db, plannedLedgerEvent) : undefined;
17964
- if (plannedLedgerEvent && existingLedgerEvent) {
17965
- const expectedEventHash = normalizeArchitectureLedgerEvent(plannedLedgerEvent, existingLedgerEvent.previousEventHash).eventHash;
17966
- if (expectedEventHash !== existingLedgerEvent.event.eventHash) {
17967
- throw new Error(`changeset-ledger-recovery-idempotency-conflict: ${plannedLedgerEvent.idempotencyKey}`);
18214
+ let metadata = {};
18215
+ try {
18216
+ const files = JSON.parse(String(row.files_json));
18217
+ metadata = JSON.parse(String(row.metadata_json));
18218
+ const plannedLedgerEvent = changeSetJournalPlannedLedgerEvent(metadata);
18219
+ const existingLedgerEvent = plannedLedgerEvent ? architectureEventByIdempotency(db, plannedLedgerEvent) : undefined;
18220
+ if (plannedLedgerEvent && existingLedgerEvent) {
18221
+ const expectedEventHash = normalizeArchitectureLedgerEvent(plannedLedgerEvent, existingLedgerEvent.previousEventHash).eventHash;
18222
+ if (expectedEventHash !== existingLedgerEvent.event.eventHash) {
18223
+ throw new Error(`changeset-ledger-recovery-idempotency-conflict: ${plannedLedgerEvent.idempotencyKey}`);
18224
+ }
18225
+ cleanupCommittedJournalFiles(files);
18226
+ const completedAt2 = nowIso2();
18227
+ db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ?, cleanup_completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
18228
+ recovery: {
18229
+ schemaVersion: "archcontext.changeset-ledger-recovery/v1",
18230
+ status: "ledger-append-detected",
18231
+ eventId: existingLedgerEvent.event.eventId,
18232
+ eventHash: existingLedgerEvent.event.eventHash ?? "",
18233
+ recoveredAt: completedAt2
18234
+ }
18235
+ })), completedAt2, completedAt2, completedAt2, String(row.journal_id));
18236
+ recovered += 1;
18237
+ continue;
17968
18238
  }
17969
- cleanupCommittedJournalFiles(files);
17970
- db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
17971
- recovery: {
17972
- schemaVersion: "archcontext.changeset-ledger-recovery/v1",
17973
- status: "ledger-append-detected",
17974
- eventId: existingLedgerEvent.event.eventId,
17975
- eventHash: existingLedgerEvent.event.eventHash ?? "",
17976
- recoveredAt: nowIso2()
18239
+ recoverJournalFiles(String(row.root), files);
18240
+ const completedAt = nowIso2();
18241
+ db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ?, cleanup_completed_at = ? WHERE journal_id = ?").run("recovered", completedAt, completedAt, completedAt, String(row.journal_id));
18242
+ recovered += 1;
18243
+ } catch (error) {
18244
+ db.prepare("UPDATE changeset_journal SET metadata_json = ?, updated_at = ? WHERE journal_id = ?").run(stableJson({
18245
+ ...metadata,
18246
+ recoveryError: {
18247
+ schemaVersion: "archcontext.changeset-recovery-error/v1",
18248
+ message: error instanceof Error ? error.message : String(error),
18249
+ failedAt: nowIso2()
17977
18250
  }
17978
- })), nowIso2(), nowIso2(), String(row.journal_id));
17979
- continue;
18251
+ }), nowIso2(), String(row.journal_id));
17980
18252
  }
17981
- recoverJournalFiles(String(row.root), files);
17982
- db.prepare("UPDATE changeset_journal SET status = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("recovered", nowIso2(), nowIso2(), String(row.journal_id));
17983
18253
  }
17984
- return rows.length;
18254
+ return recovered;
17985
18255
  }
17986
18256
  async saveTaskState(taskSessionId, state) {
17987
18257
  const db = await this.database();
@@ -18079,52 +18349,38 @@ class SqliteLocalStore {
18079
18349
  for (const event of input.events)
18080
18350
  validateArchitectureLedgerEvent(event);
18081
18351
  const db = await this.database();
18082
- const startedAt = Date.now();
18083
- const appendedEvents = [];
18084
- const duplicateEvents = [];
18085
18352
  db.exec("BEGIN IMMEDIATE");
18086
18353
  try {
18087
- let processed = 0;
18088
- for (const event of input.events) {
18089
- const duplicate = architectureEventByIdempotency(db, event);
18090
- if (duplicate) {
18091
- const expectedDuplicateHash = normalizeArchitectureLedgerEvent(event, duplicate.previousEventHash).eventHash;
18092
- if (expectedDuplicateHash !== duplicate.event.eventHash) {
18093
- throw new Error(`architecture-ledger-idempotency-conflict: ${event.idempotencyKey}`);
18094
- }
18095
- duplicateEvents.push(duplicate.event);
18096
- continue;
18097
- }
18098
- const previousEventHash = latestArchitectureEventHash(db, event.repository.storageRepositoryId, event.worktree.storageWorkspaceId);
18099
- const normalized = normalizeArchitectureLedgerEvent(event, previousEventHash);
18100
- insertArchitectureEvent(db, normalized);
18101
- persistArchitectureLedgerArtifacts(db, normalized);
18102
- materializeArchitectureLedgerEvent(db, normalized);
18103
- appendedEvents.push(normalized);
18104
- processed += 1;
18105
- if (input.faultAfterEvents !== undefined && processed >= input.faultAfterEvents)
18106
- throw new Error("architecture-ledger-fault-injection");
18107
- }
18108
- const scope = architectureScopeFromEvent(input.events[0] ?? duplicateEvents[0]);
18109
- const state = scope ? readArchitectureLedgerStateFromDb(db, scope) : emptyArchitectureLedgerState();
18110
- if (scope) {
18111
- recordArchitectureLedgerOperation(db, {
18112
- scope,
18113
- operationKind: "append_events",
18114
- durationMs: Date.now() - startedAt,
18115
- rowCount: appendedEvents.length,
18116
- rebuildReason: null
18117
- });
18118
- }
18354
+ const result = appendArchitectureEventsInOpenTransaction(db, input);
18119
18355
  db.exec("COMMIT");
18120
- return {
18121
- appendedEvents,
18122
- duplicateEvents,
18123
- graphDigest: architectureLedgerStateDigest(state),
18124
- entityCount: state.entities.length,
18125
- relationCount: state.relations.length,
18126
- constraintCount: state.constraints.length
18127
- };
18356
+ return result;
18357
+ } catch (error) {
18358
+ db.exec("ROLLBACK");
18359
+ throw error;
18360
+ }
18361
+ }
18362
+ async appendArchitectureEventsAndCommitChangeSet(journalId, input) {
18363
+ if (input.writer !== "runtime-daemon")
18364
+ throw new Error("architecture-ledger-writer-must-be-runtime-daemon");
18365
+ for (const event of input.events)
18366
+ validateArchitectureLedgerEvent(event);
18367
+ const db = await this.database();
18368
+ db.exec("BEGIN IMMEDIATE");
18369
+ try {
18370
+ const row = db.prepare("SELECT status, metadata_json FROM changeset_journal WHERE journal_id = ?").get(journalId);
18371
+ if (!row)
18372
+ throw new Error(`ChangeSet journal not found: ${journalId}`);
18373
+ if (String(row.status) !== "pending")
18374
+ throw new Error(`ChangeSet journal is not pending: ${journalId}`);
18375
+ const metadata = JSON.parse(String(row.metadata_json));
18376
+ const result = appendArchitectureEventsInOpenTransaction(db, input);
18377
+ const completedAt = nowIso2();
18378
+ db.prepare("UPDATE changeset_journal SET status = ?, metadata_json = ?, updated_at = ?, completed_at = ? WHERE journal_id = ?").run("committed", stableJson(withChangeSetJournalArchitectureLedger(metadata, {
18379
+ append: changeSetLedgerAppendSummary(result),
18380
+ appendedAt: completedAt
18381
+ })), completedAt, completedAt, journalId);
18382
+ db.exec("COMMIT");
18383
+ return result;
18128
18384
  } catch (error) {
18129
18385
  db.exec("ROLLBACK");
18130
18386
  throw error;
@@ -18138,24 +18394,50 @@ class SqliteLocalStore {
18138
18394
  WHERE storage_repository_id = ?
18139
18395
  AND storage_workspace_id = ?
18140
18396
  ${statusClause}
18141
- ORDER BY created_at DESC, run_id ASC`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, ...statuses).map((row) => JSON.parse(String(row.run_json)));
18397
+ ORDER BY created_at DESC, run_id ASC`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), ...statuses).map((row) => JSON.parse(String(row.run_json)));
18398
+ }
18399
+ async resolveArchitectureLedgerScope(input) {
18400
+ const db = await this.database();
18401
+ const rows = db.prepare(`SELECT event_json FROM architecture_events
18402
+ WHERE storage_repository_id = ? AND workspace_id = ? AND branch = ?
18403
+ ORDER BY event_sequence DESC`).all(input.repository.storageRepositoryId, input.worktree.workspaceId, input.worktree.branch);
18404
+ for (const row of rows) {
18405
+ const event = JSON.parse(String(row.event_json));
18406
+ if (event.worktree.storageWorkspaceId === input.worktree.storageWorkspaceId) {
18407
+ return architectureScopeFromEvent(event);
18408
+ }
18409
+ }
18410
+ return input;
18411
+ }
18412
+ async resolveLatestArchitectureLedgerScope(input) {
18413
+ const db = await this.database();
18414
+ const rows = db.prepare(`SELECT event_json FROM architecture_events
18415
+ WHERE storage_repository_id = ? AND workspace_id = ?
18416
+ ORDER BY event_sequence DESC`).all(input.repository.storageRepositoryId, input.worktree.workspaceId);
18417
+ for (const row of rows) {
18418
+ const event = JSON.parse(String(row.event_json));
18419
+ if (event.worktree.storageWorkspaceId === input.worktree.storageWorkspaceId) {
18420
+ return architectureScopeFromEvent(event);
18421
+ }
18422
+ }
18423
+ return input;
18142
18424
  }
18143
18425
  async getAuditRun(input) {
18144
18426
  const db = await this.database();
18145
18427
  const row = db.prepare(`SELECT run_json FROM audit_runs
18146
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND run_id = ?`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, input.runId);
18428
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND run_id = ?`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), architectureLedgerStorageId(input.worktree, input.runId));
18147
18429
  return row ? JSON.parse(String(row.run_json)) : undefined;
18148
18430
  }
18149
18431
  async readArchitectureLedgerSourceCursor(input) {
18150
18432
  const db = await this.database();
18151
18433
  const row = db.prepare(`SELECT cursor_json FROM source_cursors
18152
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND cursor_id = ?`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, input.cursorId);
18434
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND cursor_id = ?`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), architectureLedgerStorageId(input.worktree, input.cursorId));
18153
18435
  return row ? JSON.parse(String(row.cursor_json)) : undefined;
18154
18436
  }
18155
18437
  async createArchitectureLedgerSnapshot(input) {
18156
18438
  const db = await this.database();
18157
18439
  const startedAt = Date.now();
18158
- const latest = latestArchitectureEvent(db, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
18440
+ const latest = latestArchitectureEvent(db, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18159
18441
  if (!latest)
18160
18442
  throw new Error("architecture-ledger-snapshot-requires-event");
18161
18443
  const state = readArchitectureLedgerStateFromDb(db, input);
@@ -18169,7 +18451,7 @@ class SqliteLocalStore {
18169
18451
  (snapshot_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, branch, head_sha, worktree_digest,
18170
18452
  source_mode, last_event_id, last_event_hash, graph_digest, projection_digest, entity_count, relation_count,
18171
18453
  constraint_count, input_digests_json, snapshot_json, created_at)
18172
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(snapshot.snapshotId, snapshot.repository.repositoryId, snapshot.repository.storageRepositoryId, snapshot.worktree.workspaceId, snapshot.worktree.storageWorkspaceId, snapshot.worktree.branch, snapshot.worktree.headSha, snapshot.worktree.worktreeDigest, snapshot.sourceMode, snapshot.eventCursor.lastEventId, snapshot.eventCursor.lastEventHash, snapshot.graphDigest, snapshot.projectionDigest, snapshot.entityCount, snapshot.relationCount, snapshot.constraintCount, stableJson(snapshot.inputDigests), stableJson(snapshot), snapshot.createdAt);
18454
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(snapshot.snapshotId, snapshot.repository.repositoryId, snapshot.repository.storageRepositoryId, snapshot.worktree.workspaceId, architectureLedgerWorkspaceKey(snapshot.worktree), snapshot.worktree.branch, snapshot.worktree.headSha, snapshot.worktree.worktreeDigest, snapshot.sourceMode, architectureLedgerStorageId(snapshot.worktree, snapshot.eventCursor.lastEventId), snapshot.eventCursor.lastEventHash, snapshot.graphDigest, snapshot.projectionDigest, snapshot.entityCount, snapshot.relationCount, snapshot.constraintCount, stableJson(snapshot.inputDigests), stableJson(snapshot), snapshot.createdAt);
18173
18455
  recordArchitectureLedgerOperation(db, {
18174
18456
  scope: input,
18175
18457
  operationKind: "create_snapshot",
@@ -18238,16 +18520,16 @@ class SqliteLocalStore {
18238
18520
  const db = await this.database();
18239
18521
  const startedAt = Date.now();
18240
18522
  const snapshot = db.prepare(`SELECT last_event_id FROM architecture_snapshots
18241
- WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.beforeSnapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
18523
+ WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.beforeSnapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18242
18524
  if (!snapshot)
18243
18525
  throw new Error(`architecture-ledger-snapshot-not-found: ${input.beforeSnapshotId}`);
18244
18526
  const cursor = db.prepare("SELECT event_sequence FROM architecture_events WHERE event_id = ?").get(String(snapshot.last_event_id));
18245
18527
  if (!cursor)
18246
18528
  throw new Error(`architecture-ledger-snapshot-cursor-not-found: ${String(snapshot.last_event_id)}`);
18247
18529
  const before = Number(db.prepare(`SELECT COUNT(*) AS count FROM architecture_events
18248
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, Number(cursor.event_sequence))?.count ?? 0);
18530
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), Number(cursor.event_sequence))?.count ?? 0);
18249
18531
  db.prepare(`UPDATE architecture_events SET compacted_by_snapshot_id = ?
18250
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).run(input.beforeSnapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, Number(cursor.event_sequence));
18532
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND event_sequence <= ? AND compacted_by_snapshot_id IS NULL`).run(input.beforeSnapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), Number(cursor.event_sequence));
18251
18533
  recordArchitectureLedgerOperation(db, {
18252
18534
  scope: input,
18253
18535
  operationKind: "compact_events",
@@ -18267,7 +18549,7 @@ class SqliteLocalStore {
18267
18549
  const replay = await this.verifyArchitectureLedgerReplay(input);
18268
18550
  if (!replay.ok)
18269
18551
  failures.push(...replay.mismatches);
18270
- const snapshotCount = Number(db.prepare("SELECT COUNT(*) AS count FROM architecture_snapshots WHERE storage_repository_id = ? AND storage_workspace_id = ?").get(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId)?.count ?? 0);
18552
+ const snapshotCount = Number(db.prepare("SELECT COUNT(*) AS count FROM architecture_snapshots WHERE storage_repository_id = ? AND storage_workspace_id = ?").get(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree))?.count ?? 0);
18271
18553
  const result = {
18272
18554
  ok: failures.length === 0,
18273
18555
  graphDigest: replay.materializedDigest,
@@ -18287,7 +18569,7 @@ class SqliteLocalStore {
18287
18569
  async backupArchitectureLedger(input) {
18288
18570
  const db = await this.database();
18289
18571
  ensurePrivateDir2(dirname6(input.backupPath));
18290
- if (existsSync10(input.backupPath))
18572
+ if (existsSync11(input.backupPath))
18291
18573
  rmSync5(input.backupPath, { force: true });
18292
18574
  db.exec(`VACUUM INTO ${sqliteStringLiteral2(input.backupPath)}`);
18293
18575
  return { backupPath: input.backupPath, integrity: assertSqliteIntegrity2(input.backupPath) };
@@ -18318,9 +18600,80 @@ class SqliteLocalStore {
18318
18600
  function architectureScopeFromEvent(event) {
18319
18601
  return event ? { repository: event.repository, worktree: event.worktree } : undefined;
18320
18602
  }
18603
+ function architectureLedgerWorkspaceKey(worktree) {
18604
+ return `ledger-scope:${digestJson({
18605
+ storageWorkspaceId: worktree.storageWorkspaceId,
18606
+ branch: worktree.branch,
18607
+ headSha: worktree.headSha,
18608
+ worktreeDigest: worktree.worktreeDigest
18609
+ }).slice("sha256:".length)}`;
18610
+ }
18611
+ function architectureLedgerStorageId(worktree, logicalId) {
18612
+ return `${architectureLedgerWorkspaceKey(worktree)}:${logicalId}`;
18613
+ }
18614
+ function appendArchitectureEventsInOpenTransaction(db, input) {
18615
+ const startedAt = Date.now();
18616
+ const appendedEvents = [];
18617
+ const duplicateEvents = [];
18618
+ let processed = 0;
18619
+ for (const event of input.events) {
18620
+ const duplicate = architectureEventByIdempotency(db, event);
18621
+ if (duplicate) {
18622
+ const expectedDuplicateHash = normalizeArchitectureLedgerEvent(event, duplicate.previousEventHash).eventHash;
18623
+ if (expectedDuplicateHash !== duplicate.event.eventHash) {
18624
+ throw new Error(`architecture-ledger-idempotency-conflict: ${event.idempotencyKey}`);
18625
+ }
18626
+ duplicateEvents.push(duplicate.event);
18627
+ continue;
18628
+ }
18629
+ const previousEventHash = latestArchitectureEventHash(db, event.repository.storageRepositoryId, architectureLedgerWorkspaceKey(event.worktree));
18630
+ const normalized = normalizeArchitectureLedgerEvent(event, previousEventHash);
18631
+ const operations = architectureLedgerPayload(normalized).operations ?? [];
18632
+ const scope2 = architectureScopeFromEvent(normalized);
18633
+ if (operations.length > 0) {
18634
+ const currentDigest = architectureLedgerStateDigest(readArchitectureLedgerStateFromDb(db, scope2));
18635
+ if (normalized.baseDigest !== currentDigest) {
18636
+ throw new Error(`architecture-ledger-base-digest-conflict: expected ${currentDigest}, received ${normalized.baseDigest}`);
18637
+ }
18638
+ }
18639
+ insertArchitectureEvent(db, normalized);
18640
+ persistArchitectureLedgerArtifacts(db, normalized);
18641
+ materializeArchitectureLedgerEvent(db, normalized);
18642
+ if (operations.length > 0) {
18643
+ const resultingDigest = architectureLedgerStateDigest(readArchitectureLedgerStateFromDb(db, scope2));
18644
+ if (normalized.resultingDigest !== resultingDigest) {
18645
+ throw new Error(`architecture-ledger-resulting-digest-conflict: expected ${resultingDigest}, received ${normalized.resultingDigest}`);
18646
+ }
18647
+ }
18648
+ appendedEvents.push(normalized);
18649
+ processed += 1;
18650
+ if (input.faultAfterEvents !== undefined && processed >= input.faultAfterEvents) {
18651
+ throw new Error("architecture-ledger-fault-injection");
18652
+ }
18653
+ }
18654
+ const scope = architectureScopeFromEvent(input.events[0] ?? duplicateEvents[0]);
18655
+ const state = scope ? readArchitectureLedgerStateFromDb(db, scope) : emptyArchitectureLedgerState();
18656
+ if (scope) {
18657
+ recordArchitectureLedgerOperation(db, {
18658
+ scope,
18659
+ operationKind: "append_events",
18660
+ durationMs: Date.now() - startedAt,
18661
+ rowCount: appendedEvents.length,
18662
+ rebuildReason: null
18663
+ });
18664
+ }
18665
+ return {
18666
+ appendedEvents,
18667
+ duplicateEvents,
18668
+ graphDigest: architectureLedgerStateDigest(state),
18669
+ entityCount: state.entities.length,
18670
+ relationCount: state.relations.length,
18671
+ constraintCount: state.constraints.length
18672
+ };
18673
+ }
18321
18674
  function architectureEventByIdempotency(db, event) {
18322
18675
  const row = db.prepare(`SELECT event_json, previous_event_hash FROM architecture_events
18323
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND idempotency_key = ?`).get(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, event.idempotencyKey);
18676
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND idempotency_key = ?`).get(event.repository.storageRepositoryId, architectureLedgerWorkspaceKey(event.worktree), event.idempotencyKey);
18324
18677
  return row ? {
18325
18678
  event: JSON.parse(String(row.event_json)),
18326
18679
  previousEventHash: row.previous_event_hash === null || row.previous_event_hash === undefined ? null : String(row.previous_event_hash)
@@ -18336,11 +18689,12 @@ function latestArchitectureEvent(db, storageRepositoryId, storageWorkspaceId) {
18336
18689
  return row ? { event: JSON.parse(String(row.event_json)), eventHash: String(row.event_hash) } : undefined;
18337
18690
  }
18338
18691
  function insertArchitectureEvent(db, event) {
18692
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18339
18693
  db.prepare(`INSERT INTO architecture_events
18340
18694
  (event_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, branch, head_sha, worktree_digest,
18341
18695
  event_type, payload_version, source, actor_kind, actor_id, base_digest, resulting_digest, previous_event_hash,
18342
18696
  event_hash, idempotency_key, payload_json, provenance_json, event_json, created_at)
18343
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.eventId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, event.eventType, event.payloadVersion, event.source, event.actor.kind, event.actor.id, event.baseDigest, event.resultingDigest, event.previousEventHash ?? null, event.eventHash, event.idempotencyKey, stableJson(event.payload), stableJson(event.provenance), stableJson(event), event.timestamp);
18697
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, event.eventId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, event.eventType, event.payloadVersion, event.source, event.actor.kind, event.actor.id, event.baseDigest, event.resultingDigest, event.previousEventHash ?? null, event.eventHash, event.idempotencyKey, stableJson(event.payload), stableJson(event.provenance), stableJson(event), event.timestamp);
18344
18698
  insertArchitectureLedgerFts(db, "event", architectureLedgerPayload(event).summary ?? "", architectureLedgerPayload(event).rationale ?? "", architectureLedgerPayload(event).title ?? "", "");
18345
18699
  const payload = architectureLedgerPayload(event);
18346
18700
  insertArchitectureLedgerSearchDoc(db, event, {
@@ -18354,11 +18708,13 @@ function insertArchitectureEvent(db, event) {
18354
18708
  }
18355
18709
  function persistArchitectureLedgerArtifacts(db, event) {
18356
18710
  const payload = architectureLedgerPayload(event);
18711
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18712
+ const storageEventId = architectureLedgerStorageId(event.worktree, event.eventId);
18357
18713
  for (const evidence of payload.evidenceItems ?? []) {
18358
18714
  db.prepare(`INSERT OR REPLACE INTO evidence_items
18359
18715
  (evidence_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, kind, strength,
18360
18716
  polarity, origin, subject, selector_json, summary, coverage_json, supports_json, provenance_json, evidence_json, digest, created_at)
18361
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(evidence.evidenceId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, evidence.kind, evidence.strength, evidence.polarity, evidence.origin, evidence.subject, stableJson(evidence.selector), evidence.summary, stableJson(evidence.coverage), stableJson(evidence.supports), stableJson(evidence.provenance), stableJson(evidence), evidence.digest, evidence.createdAt);
18717
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, evidence.evidenceId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, storageEventId, evidence.kind, evidence.strength, evidence.polarity, evidence.origin, evidence.subject, stableJson(evidence.selector), evidence.summary, stableJson(evidence.coverage), stableJson(evidence.supports), stableJson(evidence.provenance), stableJson(evidence), evidence.digest, evidence.createdAt);
18362
18718
  insertArchitectureLedgerFts(db, "evidence", evidence.summary, "", "", evidence.summary);
18363
18719
  insertArchitectureLedgerSearchDoc(db, event, {
18364
18720
  docId: `evidence:${evidence.evidenceId}`,
@@ -18373,7 +18729,7 @@ function persistArchitectureLedgerArtifacts(db, event) {
18373
18729
  db.prepare(`INSERT OR REPLACE INTO evidence_bindings
18374
18730
  (binding_id, evidence_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id,
18375
18731
  target_kind, target_id, binding_reason, authority_effect, provenance_json, binding_json, created_at)
18376
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(binding.bindingId, binding.evidenceId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, binding.target.kind, binding.target.id, binding.bindingReason, binding.authorityEffect, stableJson(binding.provenance), stableJson(binding), binding.createdAt);
18732
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, binding.bindingId), architectureLedgerStorageId(event.worktree, binding.evidenceId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, storageEventId, binding.target.kind, binding.target.id, binding.bindingReason, binding.authorityEffect, stableJson(binding.provenance), stableJson(binding), binding.createdAt);
18377
18733
  }
18378
18734
  for (const run of payload.recommendationRuns ?? [])
18379
18735
  persistRecommendationRun(db, event, run);
@@ -18393,17 +18749,19 @@ function persistArchitectureLedgerArtifacts(db, event) {
18393
18749
  persistProjectionState(db, event, payload.projectionState);
18394
18750
  }
18395
18751
  function persistRecommendationRun(db, event, run) {
18752
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18396
18753
  db.prepare(`INSERT OR REPLACE INTO recommendation_runs
18397
18754
  (run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, status, catalog_digest,
18398
18755
  input_digest, output_digest, metrics_json, run_json, started_at, completed_at)
18399
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, run.status, run.catalogDigest, run.inputDigest, run.outputDigest, stableJson(run.metrics), stableJson(run), run.startedAt, run.completedAt ?? null);
18756
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, run.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), run.status, run.catalogDigest, run.inputDigest, run.outputDigest, stableJson(run.metrics), stableJson(run), run.startedAt, run.completedAt ?? null);
18400
18757
  }
18401
18758
  function persistRecommendation(db, event, recommendation) {
18759
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18402
18760
  db.prepare(`INSERT OR REPLACE INTO recommendations
18403
18761
  (recommendation_id, run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, fingerprint,
18404
18762
  subject, practice_id, status, confidence, enforcement, risk, uncertainty, evidence_binding_ids_json, explanation_json,
18405
18763
  recommendation_json, created_at, updated_at)
18406
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(recommendation.recommendationId, recommendation.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, recommendation.fingerprint, recommendation.subject, recommendation.practiceId ?? null, recommendation.status, recommendation.confidence, recommendation.enforcement, recommendation.risk, recommendation.uncertainty, stableJson(recommendation.evidenceBindingIds), stableJson(recommendation.explanation), stableJson(recommendation), recommendation.createdAt, recommendation.updatedAt);
18764
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, recommendation.recommendationId), architectureLedgerStorageId(event.worktree, recommendation.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), recommendation.fingerprint, recommendation.subject, recommendation.practiceId ?? null, recommendation.status, recommendation.confidence, recommendation.enforcement, recommendation.risk, recommendation.uncertainty, stableJson(recommendation.evidenceBindingIds), stableJson(recommendation.explanation), stableJson(recommendation), recommendation.createdAt, recommendation.updatedAt);
18407
18765
  insertArchitectureLedgerFts(db, "recommendation", recommendation.explanation.join(`
18408
18766
  `), "", recommendation.subject, recommendation.explanation.join(`
18409
18767
  `));
@@ -18420,44 +18778,51 @@ function persistRecommendation(db, event, recommendation) {
18420
18778
  });
18421
18779
  }
18422
18780
  function persistAgentJob(db, event, job) {
18781
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18423
18782
  db.prepare(`INSERT OR REPLACE INTO agent_jobs
18424
18783
  (job_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, status, runner_port,
18425
18784
  fingerprint, input_digest, output_digest, stale_policy, job_json, queued_at, updated_at)
18426
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(job.jobId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, job.status, job.runnerPort, job.fingerprint, job.inputDigest, job.outputDigest ?? null, job.stalePolicy, stableJson(job), job.queuedAt, job.updatedAt);
18785
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, job.jobId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), job.status, job.runnerPort, job.fingerprint, job.inputDigest, job.outputDigest ?? null, job.stalePolicy, stableJson(job), job.queuedAt, job.updatedAt);
18427
18786
  }
18428
18787
  function persistAuditRun(db, event, run) {
18788
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18429
18789
  db.prepare(`INSERT OR REPLACE INTO audit_runs
18430
18790
  (run_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, job_id, report_id, status,
18431
18791
  repo_name_with_owner, repo_visibility, base_sha, input_digest, output_digest, run_json, created_at)
18432
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.runId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, run.jobId, run.reportId, run.status, run.repoNameWithOwner, run.repoVisibility, run.baseSha, run.inputDigest, run.outputDigest, stableJson(run), run.createdAt);
18792
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, run.runId), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), run.jobId, run.reportId, run.status, run.repoNameWithOwner, run.repoVisibility, run.baseSha, run.inputDigest, run.outputDigest, stableJson(run), run.createdAt);
18433
18793
  }
18434
18794
  function persistProjectionState(db, event, state) {
18435
18795
  const path = String(state.path ?? "projection");
18436
18796
  const projectionDigest = typeof state.projectionDigest === "string" ? state.projectionDigest : digestJson(state);
18797
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18437
18798
  db.prepare(`INSERT OR REPLACE INTO projection_state
18438
18799
  (projection_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, path, projection_digest, state_json, updated_at)
18439
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(String(state.projectionId ?? stableLedgerId("projection", event.eventId, path)), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, path, projectionDigest, stableJson(state), event.timestamp);
18800
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, String(state.projectionId ?? stableLedgerId("projection", event.eventId, path))), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), path, projectionDigest, stableJson(state), event.timestamp);
18440
18801
  }
18441
18802
  function persistSourceCursor(db, event, cursor) {
18442
18803
  const source = String(cursor.source ?? event.source);
18804
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18443
18805
  db.prepare(`INSERT OR REPLACE INTO source_cursors
18444
18806
  (cursor_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, source, cursor_json, updated_at)
18445
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(String(cursor.cursorId ?? stableLedgerId("cursor", event.eventId, source)), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, source, stableJson(cursor), event.timestamp);
18807
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(architectureLedgerStorageId(event.worktree, String(cursor.cursorId ?? stableLedgerId("cursor", event.eventId, source))), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, source, stableJson(cursor), event.timestamp);
18446
18808
  }
18447
18809
  function persistGenericLedgerJson(db, event, table, idColumn, jsonColumn, value, prefix) {
18448
18810
  const id = String(value[idColumn] ?? value.id ?? stableLedgerId(prefix, event.eventId, stableJson(value)));
18811
+ const storageId = architectureLedgerStorageId(event.worktree, id);
18812
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18449
18813
  if (table === "recommendation_feedback") {
18450
18814
  db.prepare(`INSERT OR REPLACE INTO recommendation_feedback
18451
18815
  (feedback_id, recommendation_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, feedback_json, created_at)
18452
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, String(value.recommendationId ?? value.recommendation_id ?? "unknown"), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, stableJson(value), String(value.createdAt ?? event.timestamp));
18816
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(storageId, architectureLedgerStorageId(event.worktree, String(value.recommendationId ?? value.recommendation_id ?? "unknown")), event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), stableJson(value), String(value.createdAt ?? event.timestamp));
18453
18817
  return;
18454
18818
  }
18455
18819
  db.prepare(`INSERT OR REPLACE INTO waivers
18456
18820
  (waiver_id, repository_id, storage_repository_id, workspace_id, storage_workspace_id, event_id, target_kind, target_id, waiver_json, created_at, expires_at)
18457
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, event.worktree.storageWorkspaceId, event.eventId, String(value.targetKind ?? value.target_kind ?? "unknown"), String(value.targetId ?? value.target_id ?? "unknown"), stableJson(value), String(value.createdAt ?? event.timestamp), value.expiresAt ? String(value.expiresAt) : null);
18821
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(storageId, event.repository.repositoryId, event.repository.storageRepositoryId, event.worktree.workspaceId, workspaceKey, architectureLedgerStorageId(event.worktree, event.eventId), String(value.targetKind ?? value.target_kind ?? "unknown"), String(value.targetId ?? value.target_id ?? "unknown"), stableJson(value), String(value.createdAt ?? event.timestamp), value.expiresAt ? String(value.expiresAt) : null);
18458
18822
  }
18459
18823
  function materializeArchitectureLedgerEvent(db, event) {
18460
18824
  const payload = architectureLedgerPayload(event);
18825
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18461
18826
  for (const operation of payload.operations ?? []) {
18462
18827
  switch (operation.op) {
18463
18828
  case "upsert_entity":
@@ -18465,31 +18830,32 @@ function materializeArchitectureLedgerEvent(db, event) {
18465
18830
  break;
18466
18831
  case "delete_entity":
18467
18832
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.entityId]);
18468
- db.prepare("DELETE FROM architecture_entities_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.entityId);
18469
- db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (source_entity_id = ? OR target_entity_id = ?)").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.entityId, operation.entityId);
18833
+ db.prepare("DELETE FROM architecture_entities_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.entityId);
18834
+ db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (source_entity_id = ? OR target_entity_id = ?)").run(event.repository.storageRepositoryId, workspaceKey, operation.entityId, operation.entityId);
18470
18835
  break;
18471
18836
  case "upsert_relation":
18472
18837
  upsertArchitectureRelation(db, event, operation.relation);
18473
18838
  break;
18474
18839
  case "delete_relation":
18475
18840
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.relationId]);
18476
- db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND relation_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.relationId);
18841
+ db.prepare("DELETE FROM architecture_relations_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND relation_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.relationId);
18477
18842
  break;
18478
18843
  case "upsert_constraint":
18479
18844
  upsertArchitectureConstraint(db, event, operation.constraint);
18480
18845
  break;
18481
18846
  case "delete_constraint":
18482
18847
  deleteArchitectureLedgerSearchDocs(db, { repository: event.repository, worktree: event.worktree }, [operation.constraintId]);
18483
- db.prepare("DELETE FROM architecture_constraints_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND constraint_id = ?").run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, operation.constraintId);
18848
+ db.prepare("DELETE FROM architecture_constraints_current WHERE storage_repository_id = ? AND storage_workspace_id = ? AND constraint_id = ?").run(event.repository.storageRepositoryId, workspaceKey, operation.constraintId);
18484
18849
  break;
18485
18850
  }
18486
18851
  }
18487
18852
  }
18488
18853
  function upsertArchitectureEntity(db, event, entity) {
18854
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18489
18855
  db.prepare(`INSERT OR REPLACE INTO architecture_entities_current
18490
18856
  (storage_repository_id, storage_workspace_id, entity_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18491
18857
  kind, canonical_name, status, path, summary, metadata_json, last_event_id, updated_at)
18492
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, entity.entityId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, entity.kind, entity.canonicalName, entity.status, entity.path ?? null, entity.summary ?? null, stableJson(entity.metadata ?? {}), event.eventId, event.timestamp);
18858
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, entity.entityId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, entity.kind, entity.canonicalName, entity.status, entity.path ?? null, entity.summary ?? null, stableJson(entity.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18493
18859
  insertArchitectureLedgerFts(db, "entity", entity.summary ?? "", "", entity.canonicalName, "");
18494
18860
  insertArchitectureLedgerSearchDoc(db, event, {
18495
18861
  docId: `entity:${entity.entityId}`,
@@ -18502,10 +18868,11 @@ function upsertArchitectureEntity(db, event, entity) {
18502
18868
  });
18503
18869
  }
18504
18870
  function upsertArchitectureRelation(db, event, relation) {
18871
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18505
18872
  db.prepare(`INSERT OR REPLACE INTO architecture_relations_current
18506
18873
  (storage_repository_id, storage_workspace_id, relation_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18507
18874
  kind, source_entity_id, target_entity_id, status, summary, metadata_json, last_event_id, updated_at)
18508
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, relation.relationId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, relation.kind, relation.sourceEntityId, relation.targetEntityId, relation.status, relation.summary ?? null, stableJson(relation.metadata ?? {}), event.eventId, event.timestamp);
18875
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, relation.relationId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, relation.kind, relation.sourceEntityId, relation.targetEntityId, relation.status, relation.summary ?? null, stableJson(relation.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18509
18876
  insertArchitectureLedgerFts(db, "relation", relation.summary ?? "", "", relation.relationId, "");
18510
18877
  insertArchitectureLedgerSearchDoc(db, event, {
18511
18878
  docId: `relation:${relation.relationId}`,
@@ -18518,10 +18885,11 @@ function upsertArchitectureRelation(db, event, relation) {
18518
18885
  });
18519
18886
  }
18520
18887
  function upsertArchitectureConstraint(db, event, constraint) {
18888
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18521
18889
  db.prepare(`INSERT OR REPLACE INTO architecture_constraints_current
18522
18890
  (storage_repository_id, storage_workspace_id, constraint_id, repository_id, workspace_id, branch, head_sha, worktree_digest,
18523
18891
  kind, subject_id, status, severity, summary, metadata_json, last_event_id, updated_at)
18524
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, constraint.constraintId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, constraint.kind, constraint.subjectId, constraint.status, constraint.severity ?? null, constraint.summary ?? null, stableJson(constraint.metadata ?? {}), event.eventId, event.timestamp);
18892
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(event.repository.storageRepositoryId, workspaceKey, constraint.constraintId, event.repository.repositoryId, event.worktree.workspaceId, event.worktree.branch, event.worktree.headSha, event.worktree.worktreeDigest, constraint.kind, constraint.subjectId, constraint.status, constraint.severity ?? null, constraint.summary ?? null, stableJson(constraint.metadata ?? {}), architectureLedgerStorageId(event.worktree, event.eventId), event.timestamp);
18525
18893
  insertArchitectureLedgerFts(db, "constraint", constraint.summary ?? "", "", constraint.constraintId, "");
18526
18894
  insertArchitectureLedgerSearchDoc(db, event, {
18527
18895
  docId: `constraint:${constraint.constraintId}`,
@@ -18534,7 +18902,7 @@ function upsertArchitectureConstraint(db, event, constraint) {
18534
18902
  });
18535
18903
  }
18536
18904
  function readArchitectureLedgerStateFromDb(db, scope) {
18537
- const scopeParams = [scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId];
18905
+ const scopeParams = [scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree)];
18538
18906
  const entities = db.prepare(`SELECT entity_id, kind, canonical_name, status, path, summary, metadata_json
18539
18907
  FROM architecture_entities_current
18540
18908
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
@@ -18575,7 +18943,7 @@ function readArchitectureLedgerStateFromDb(db, scope) {
18575
18943
  }
18576
18944
  function readArchitectureLedgerNeighborhoodFromDb(db, input) {
18577
18945
  const depth = Math.max(0, Math.floor(input.depth));
18578
- const scopeParams = [input.repository.storageRepositoryId, input.worktree.storageWorkspaceId];
18946
+ const scopeParams = [input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree)];
18579
18947
  const entityIds = [...new Set(db.prepare(`WITH RECURSIVE seed(entity_id) AS (
18580
18948
  SELECT entity_id FROM architecture_entities_current
18581
18949
  WHERE storage_repository_id = ? AND storage_workspace_id = ? AND entity_id = ?
@@ -18656,42 +19024,43 @@ function architectureEventsForReplay(db, input) {
18656
19024
  let untilEventId = input.untilEventId;
18657
19025
  if (input.snapshotId) {
18658
19026
  const snapshot = db.prepare(`SELECT last_event_id FROM architecture_snapshots
18659
- WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.snapshotId, input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
19027
+ WHERE snapshot_id = ? AND storage_repository_id = ? AND storage_workspace_id = ?`).get(input.snapshotId, input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18660
19028
  if (!snapshot)
18661
19029
  throw new Error(`architecture-ledger-snapshot-not-found: ${input.snapshotId}`);
18662
19030
  untilEventId = String(snapshot.last_event_id);
18663
19031
  }
18664
19032
  const rows = db.prepare(`SELECT event_id, event_json FROM architecture_events
18665
19033
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
18666
- ORDER BY event_sequence`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId);
19034
+ ORDER BY event_sequence`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree));
18667
19035
  const events = [];
18668
19036
  for (const row of rows) {
18669
19037
  const event = JSON.parse(String(row.event_json));
18670
19038
  events.push(event);
18671
- if (untilEventId && String(row.event_id) === untilEventId)
19039
+ if (untilEventId && (String(row.event_id) === untilEventId || event.eventId === untilEventId))
18672
19040
  break;
18673
19041
  }
18674
19042
  return events;
18675
19043
  }
18676
19044
  function deleteArchitectureCurrentState(db, scope) {
18677
19045
  for (const table of ["architecture_entities_current", "architecture_relations_current", "architecture_constraints_current"]) {
18678
- db.prepare(`DELETE FROM ${table} WHERE storage_repository_id = ? AND storage_workspace_id = ?`).run(scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId);
19046
+ db.prepare(`DELETE FROM ${table} WHERE storage_repository_id = ? AND storage_workspace_id = ?`).run(scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree));
18679
19047
  }
18680
19048
  }
18681
19049
  function insertArchitectureLedgerFts(db, kind, summary, rationale, title, evidenceSummary) {
18682
19050
  db.prepare("INSERT INTO architecture_ledger_fts(kind, summary, rationale, title, evidence_summary) VALUES (?, ?, ?, ?, ?)").run(kind, summary, rationale, title, evidenceSummary);
18683
19051
  }
18684
19052
  function insertArchitectureLedgerSearchDoc(db, event, doc) {
19053
+ const workspaceKey = architectureLedgerWorkspaceKey(event.worktree);
18685
19054
  db.prepare(`DELETE FROM architecture_ledger_search_fts
18686
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND doc_id = ?`).run(event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, doc.docId);
19055
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND doc_id = ?`).run(event.repository.storageRepositoryId, workspaceKey, doc.docId);
18687
19056
  db.prepare(`INSERT INTO architecture_ledger_search_fts
18688
19057
  (doc_id, storage_repository_id, storage_workspace_id, target_kind, target_id, subject_id, title, summary, rationale, evidence_summary)
18689
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(doc.docId, event.repository.storageRepositoryId, event.worktree.storageWorkspaceId, doc.targetKind, doc.targetId, doc.subjectId ?? null, doc.title ?? "", doc.summary ?? "", doc.rationale ?? "", doc.evidenceSummary ?? "");
19058
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(doc.docId, event.repository.storageRepositoryId, workspaceKey, doc.targetKind, doc.targetId, doc.subjectId ?? null, doc.title ?? "", doc.summary ?? "", doc.rationale ?? "", doc.evidenceSummary ?? "");
18690
19059
  }
18691
19060
  function deleteArchitectureLedgerSearchDocs(db, scope, ids) {
18692
19061
  for (const id of ids) {
18693
19062
  db.prepare(`DELETE FROM architecture_ledger_search_fts
18694
- WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (target_id = ? OR subject_id = ?)`).run(scope.repository.storageRepositoryId, scope.worktree.storageWorkspaceId, id, id);
19063
+ WHERE storage_repository_id = ? AND storage_workspace_id = ? AND (target_id = ? OR subject_id = ?)`).run(scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(scope.worktree), id, id);
18695
19064
  }
18696
19065
  }
18697
19066
  function queryArchitectureLedgerSearchFts(db, input) {
@@ -18705,7 +19074,7 @@ function queryArchitectureLedgerSearchFts(db, input) {
18705
19074
  WHERE storage_repository_id = ? AND storage_workspace_id = ?
18706
19075
  AND architecture_ledger_search_fts MATCH ?
18707
19076
  ORDER BY rank, target_kind, target_id
18708
- LIMIT ?`).all(input.repository.storageRepositoryId, input.worktree.storageWorkspaceId, matchQuery, limit);
19077
+ LIMIT ?`).all(input.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.worktree), matchQuery, limit);
18709
19078
  return rows.map((row) => {
18710
19079
  const title = String(row.title ?? "");
18711
19080
  const summary = String(row.summary ?? "");
@@ -18749,7 +19118,7 @@ function ftsMatchKind(query, doc) {
18749
19118
  function recordArchitectureLedgerOperation(db, input) {
18750
19119
  db.prepare(`INSERT INTO architecture_ledger_operations
18751
19120
  (operation_id, storage_repository_id, storage_workspace_id, operation_kind, duration_ms, row_count, rebuild_reason, created_at)
18752
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(`ledger_operation_${randomUUID2()}`, input.scope.repository.storageRepositoryId, input.scope.worktree.storageWorkspaceId, input.operationKind, Math.max(0, Math.trunc(input.durationMs)), input.rowCount, input.rebuildReason, nowIso2());
19121
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(`ledger_operation_${randomUUID2()}`, input.scope.repository.storageRepositoryId, architectureLedgerWorkspaceKey(input.scope.worktree), input.operationKind, Math.max(0, Math.trunc(input.durationMs)), input.rowCount, input.rebuildReason, nowIso2());
18753
19122
  }
18754
19123
  function stableLedgerId(prefix, ...parts) {
18755
19124
  return `${prefix}.${createHash6("sha256").update(parts.join("\x00")).digest("hex").slice(0, 24)}`;
@@ -18932,7 +19301,7 @@ function assertTrustedLegacyLocalStoreSource2(paths) {
18932
19301
  if (!stat.isFile()) {
18933
19302
  throw new Error(`Legacy SQLite source must be a regular file: ${paths.legacyLocalStorePath}`);
18934
19303
  }
18935
- const sourceRealPath = realpathSync6.native(paths.legacyLocalStorePath);
19304
+ const sourceRealPath = realpathSync7.native(paths.legacyLocalStorePath);
18936
19305
  if (!isPathInsideOrSame2(sourceRealPath, paths.repositoryRoot)) {
18937
19306
  throw new Error(`Legacy SQLite source must stay inside the repository root: ${paths.legacyLocalStorePath}`);
18938
19307
  }
@@ -18986,7 +19355,7 @@ function sqliteStringLiteral2(value) {
18986
19355
  function publishStagedLocalStore2(stagingPath, targetPath) {
18987
19356
  for (const suffix of SQLITE_SIDECAR_SUFFIXES2) {
18988
19357
  const path = `${targetPath}${suffix}`;
18989
- if (existsSync10(path))
19358
+ if (existsSync11(path))
18990
19359
  throw new Error(`Cannot publish migrated SQLite over existing target file: ${path}`);
18991
19360
  }
18992
19361
  renameSync3(stagingPath, targetPath);
@@ -19058,14 +19427,14 @@ function quarantineExistingLocalStore2(paths) {
19058
19427
  const quarantinedFiles = [];
19059
19428
  for (const suffix of SQLITE_SIDECAR_SUFFIXES2) {
19060
19429
  const source = `${paths.localStorePath}${suffix}`;
19061
- if (!existsSync10(source))
19430
+ if (!existsSync11(source))
19062
19431
  continue;
19063
19432
  const target = join6(quarantineDir, `runtime.sqlite${suffix}`);
19064
19433
  renameSync3(source, target);
19065
19434
  quarantinedFiles.push(target);
19066
19435
  }
19067
19436
  const markerPath = legacyMigrationMarkerPath2(paths);
19068
- if (existsSync10(markerPath)) {
19437
+ if (existsSync11(markerPath)) {
19069
19438
  const target = join6(quarantineDir, LEGACY_MIGRATION_MARKER_FILE2);
19070
19439
  renameSync3(markerPath, target);
19071
19440
  quarantinedFiles.push(target);
@@ -19125,7 +19494,7 @@ function resolveMaybeRelative2(base, path) {
19125
19494
  function canonicalPath2(path) {
19126
19495
  const resolved = resolve13(path);
19127
19496
  try {
19128
- return realpathSync6.native(resolved);
19497
+ return realpathSync7.native(resolved);
19129
19498
  } catch {
19130
19499
  return resolved;
19131
19500
  }
@@ -19315,18 +19684,22 @@ function changeSetLedgerAppendSummary(result) {
19315
19684
  }
19316
19685
  function changeSetJournalPlannedLedgerEvent(metadata) {
19317
19686
  const architectureLedger = metadata.architectureLedger;
19318
- if (!isJsonRecord(architectureLedger))
19687
+ if (architectureLedger === undefined)
19319
19688
  return;
19689
+ if (!isJsonRecord(architectureLedger))
19690
+ throw new Error("changeset-ledger-recovery-metadata-malformed");
19320
19691
  const plannedEvent = architectureLedger.plannedEvent;
19321
- if (!isJsonRecord(plannedEvent))
19692
+ if (plannedEvent === undefined)
19322
19693
  return;
19694
+ if (!isJsonRecord(plannedEvent))
19695
+ throw new Error("changeset-ledger-recovery-planned-event-malformed");
19696
+ const event = plannedEvent;
19323
19697
  try {
19324
- const event = plannedEvent;
19325
19698
  validateArchitectureLedgerEvent(event);
19326
- return event;
19327
- } catch {
19328
- return;
19699
+ } catch (error) {
19700
+ throw new Error(`changeset-ledger-recovery-planned-event-invalid: ${error instanceof Error ? error.message : String(error)}`);
19329
19701
  }
19702
+ return event;
19330
19703
  }
19331
19704
  function isJsonRecord(value) {
19332
19705
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -19336,9 +19709,13 @@ function recoverJournalFiles(root, files) {
19336
19709
  const absolute = resolve13(root, file.path);
19337
19710
  if (file.tempPath)
19338
19711
  rmSync5(file.tempPath, { recursive: true, force: true });
19339
- rmSync5(absolute, { recursive: true, force: true });
19340
- if (file.existed && file.backupPath && existsSync10(file.backupPath)) {
19341
- renameSync3(file.backupPath, absolute);
19712
+ if (file.existed) {
19713
+ if (file.backupPath && existsSync11(file.backupPath)) {
19714
+ rmSync5(absolute, { recursive: true, force: true });
19715
+ renameSync3(file.backupPath, absolute);
19716
+ }
19717
+ } else {
19718
+ rmSync5(absolute, { recursive: true, force: true });
19342
19719
  }
19343
19720
  fsyncDirectory3(dirname6(absolute));
19344
19721
  }
@@ -19395,8 +19772,18 @@ async function readRelationFiles(root, relationsDir) {
19395
19772
 
19396
19773
  // packages/local-runtime/model-store-yaml/src/index.ts
19397
19774
  init_src();
19398
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync10, rmSync as rmSync6, writeFileSync as writeFileSync4 } from "node:fs";
19775
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync10, rmSync as rmSync6, writeFileSync as writeFileSync4 } from "node:fs";
19399
19776
  import { dirname as dirname7, resolve as resolve14 } from "node:path";
19777
+ var GENERATED_ARCHITECTURE_PATH = ".archcontext/generated/ARCHITECTURE.md";
19778
+ var GENERATED_ARCHITECTURE_BODY = [
19779
+ "<!-- Generated by ArchContext. Do not edit by hand. -->",
19780
+ "",
19781
+ "# Architecture",
19782
+ "",
19783
+ "Model projection is generated from `.archcontext/model`.",
19784
+ ""
19785
+ ].join(`
19786
+ `);
19400
19787
  function createDefaultManifest(productId, productName) {
19401
19788
  return {
19402
19789
  schemaVersion: "archcontext.manifest/v1",
@@ -19494,6 +19881,22 @@ function createDefaultProjectionTargetManifest() {
19494
19881
  pathTemplate: "docs/architecture/diagrams/architecture.likec4",
19495
19882
  ownership: "generated",
19496
19883
  format: "likec4"
19884
+ },
19885
+ {
19886
+ id: "projection_rule.agent-context.claude",
19887
+ targetType: "agent-context",
19888
+ scope: { kind: "entity", entityKind: "capability" },
19889
+ pathTemplate: "{primarySourceDir}/CLAUDE.md",
19890
+ ownership: "mixed",
19891
+ format: "markdown"
19892
+ },
19893
+ {
19894
+ id: "projection_rule.agent-context.agents",
19895
+ targetType: "agent-context",
19896
+ scope: { kind: "entity", entityKind: "capability" },
19897
+ pathTemplate: "{primarySourceDir}/AGENTS.md",
19898
+ ownership: "mixed",
19899
+ format: "markdown"
19497
19900
  }
19498
19901
  ]
19499
19902
  };
@@ -19530,16 +19933,50 @@ function initializeArchContextModel(root, productName = "ArchContext Project") {
19530
19933
  rebuildGeneratedProjection(root);
19531
19934
  }
19532
19935
  function rebuildGeneratedProjection(root) {
19533
- rmSync6(resolve14(root, ".archcontext/generated"), { recursive: true, force: true });
19534
- writeFile(root, ".archcontext/generated/ARCHITECTURE.md", [
19535
- "<!-- Generated by ArchContext. Do not edit by hand. -->",
19536
- "",
19537
- "# Architecture",
19538
- "",
19539
- "Model projection is generated from `.archcontext/model`.",
19540
- ""
19541
- ].join(`
19542
- `));
19936
+ for (const operation of planGeneratedProjection(root)) {
19937
+ if (operation.operation === "delete_entity") {
19938
+ rmSync6(resolve14(root, operation.path), { force: true });
19939
+ } else {
19940
+ writeFile(root, operation.path, operation.body);
19941
+ }
19942
+ }
19943
+ }
19944
+ function planGeneratedProjection(root) {
19945
+ const desiredBody = GENERATED_ARCHITECTURE_BODY;
19946
+ const desiredAbsolute = resolve14(root, GENERATED_ARCHITECTURE_PATH);
19947
+ const operations = [];
19948
+ if (!existsSync12(desiredAbsolute) || readFileSync10(desiredAbsolute, "utf8") !== desiredBody) {
19949
+ operations.push({
19950
+ path: GENERATED_ARCHITECTURE_PATH,
19951
+ expectedHash: existsSync12(desiredAbsolute) ? digestJson({ body: readFileSync10(desiredAbsolute, "utf8") }) : "missing",
19952
+ body: desiredBody,
19953
+ operation: "render_projection"
19954
+ });
19955
+ }
19956
+ for (const path of collectGeneratedProjectionFiles(root)) {
19957
+ if (path === GENERATED_ARCHITECTURE_PATH)
19958
+ continue;
19959
+ const body = readFileSync10(resolve14(root, path), "utf8");
19960
+ operations.push({
19961
+ path,
19962
+ expectedHash: digestJson({ body }),
19963
+ body: "",
19964
+ operation: "delete_entity"
19965
+ });
19966
+ }
19967
+ return operations.sort((left, right) => left.path.localeCompare(right.path));
19968
+ }
19969
+ function collectGeneratedProjectionFiles(root) {
19970
+ const generatedRoot = ".archcontext/generated";
19971
+ if (!existsSync12(resolve14(root, generatedRoot)))
19972
+ return [];
19973
+ const walk = (relativeDir) => readdirSync7(resolve14(root, relativeDir), { withFileTypes: true }).flatMap((entry) => {
19974
+ const child = `${relativeDir}/${entry.name}`;
19975
+ if (entry.isDirectory())
19976
+ return walk(child);
19977
+ return entry.isFile() ? [child] : [];
19978
+ });
19979
+ return walk(generatedRoot).sort();
19543
19980
  }
19544
19981
 
19545
19982
  class YamlModelStore {
@@ -19596,7 +20033,7 @@ function collectArchContextFiles(root, entries) {
19596
20033
  const files = [];
19597
20034
  for (const entry of entries) {
19598
20035
  const absolute = resolve14(root, entry);
19599
- if (!existsSync11(absolute))
20036
+ if (!existsSync12(absolute))
19600
20037
  continue;
19601
20038
  const stat = readdirOrFile(absolute);
19602
20039
  if (stat === "file") {
@@ -20011,7 +20448,8 @@ class ArchitectureLedgerReadModelStore {
20011
20448
  return this.fallback.writeChangeSetPreview(changeSet);
20012
20449
  }
20013
20450
  async loadLedgerModel(workspace) {
20014
- const state = await this.localStore.readArchitectureLedgerState(architectureLedgerScopeForWorkspace(workspace));
20451
+ const scope = await this.localStore.resolveArchitectureLedgerScope(architectureLedgerScopeForWorkspace(workspace));
20452
+ const state = await this.localStore.readArchitectureLedgerState(scope);
20015
20453
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state).map((file) => ({
20016
20454
  path: file.path,
20017
20455
  body: file.body,
@@ -20095,7 +20533,7 @@ class ArchctxDaemon {
20095
20533
  this.readModelStore = new ArchitectureLedgerReadModelStore(this.modelStore, this.localStore, this.architectureLedger);
20096
20534
  this.changeSetEngine = deps.changeSetEngine ?? new ChangeSetEngine({
20097
20535
  modelStore: this.modelStore,
20098
- projection: { rebuildGeneratedProjection },
20536
+ projection: { planGeneratedProjection },
20099
20537
  journal: this.localStore
20100
20538
  });
20101
20539
  this.devicePrivateKeySigner = deps.devicePrivateKeySigner;
@@ -21047,7 +21485,7 @@ class ArchctxDaemon {
21047
21485
  const absolute = resolve15(session.workspace.root, path);
21048
21486
  const body = `${JSON.stringify(waiver, null, 2)}
21049
21487
  `;
21050
- const expectedHash = existsSync12(absolute) ? digestJson({ body: readFileSync11(absolute, "utf8") }) : "missing";
21488
+ const expectedHash = existsSync13(absolute) ? digestJson({ body: readFileSync11(absolute, "utf8") }) : "missing";
21051
21489
  const draft = this.changeSetEngine.plan({
21052
21490
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
21053
21491
  base: {
@@ -21386,6 +21824,16 @@ class ArchctxDaemon {
21386
21824
  const draft = this.changesets.get(input.id);
21387
21825
  if (!draft)
21388
21826
  throw new Error(`Unknown ChangeSet: ${input.id}`);
21827
+ if (draft.base.headSha !== session.workspace.headSha)
21828
+ throw new Error("ChangeSet HEAD changed before apply");
21829
+ if (draft.base.worktreeDigest !== current)
21830
+ throw new Error("ChangeSet worktree digest changed before apply");
21831
+ const currentModel = await this.readModelStore.validateModel(session.workspace);
21832
+ if (!currentModel.valid) {
21833
+ throw new Error(`ChangeSet base model is invalid: ${currentModel.errors.join("; ") || "unknown validation error"}`);
21834
+ }
21835
+ if (draft.base.modelDigest !== currentModel.modelDigest)
21836
+ throw new Error("ChangeSet model digest changed before apply");
21389
21837
  const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
21390
21838
  let ledgerAppend;
21391
21839
  const writesLedger = architectureLedgerWriteAppendsEvents(this.architectureLedger.writeMode);
@@ -21402,6 +21850,7 @@ class ArchctxDaemon {
21402
21850
  relationCount: appended.relationCount,
21403
21851
  constraintCount: appended.constraintCount
21404
21852
  };
21853
+ return { journalCommitted: Boolean(journalId) };
21405
21854
  } : undefined
21406
21855
  });
21407
21856
  return okEnvelope("apply_update", {
@@ -21435,14 +21884,72 @@ class ArchctxDaemon {
21435
21884
  });
21436
21885
  if (journalId)
21437
21886
  await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
21438
- const result = await this.localStore.appendArchitectureEvents({
21439
- writer: "runtime-daemon",
21440
- events: [plan.event]
21441
- });
21442
- if (journalId)
21443
- await this.localStore.recordChangeSetLedgerAppend(journalId, { result });
21887
+ const appendInput = { writer: "runtime-daemon", events: [plan.event] };
21888
+ const result = journalId ? await this.localStore.appendArchitectureEventsAndCommitChangeSet(journalId, appendInput) : await this.localStore.appendArchitectureEvents(appendInput);
21444
21889
  return result;
21445
21890
  }
21891
+ async applyArchitectureProjectionChangeSet(root, input) {
21892
+ const session = await this.openSession(root);
21893
+ const model = await this.modelStore.validateModel(session.workspace);
21894
+ if (!model.valid)
21895
+ throw new Error(`Architecture projection ChangeSet base model is invalid: ${model.errors.join("; ")}`);
21896
+ const projectionFiles = input.files.map((file) => ({
21897
+ path: file.path,
21898
+ body: file.body.endsWith(`
21899
+ `) ? file.body : `${file.body}
21900
+ `,
21901
+ expectedHash: expectedFileHash(root, file.path)
21902
+ }));
21903
+ const operations = [
21904
+ ...projectionFiles.length > 0 ? [{ op: "render_projection", expectedHash: "missing", projectionFiles }] : [],
21905
+ ...input.removedPaths.map((path) => ({
21906
+ op: "delete_entity",
21907
+ path,
21908
+ expectedHash: expectedFileHash(root, path)
21909
+ }))
21910
+ ];
21911
+ const draft = this.changeSetEngine.approve(this.changeSetEngine.plan({
21912
+ id: input.id,
21913
+ base: {
21914
+ headSha: session.workspace.headSha,
21915
+ worktreeDigest: session.snapshot.worktreeDigest,
21916
+ modelDigest: model.modelDigest
21917
+ },
21918
+ reason: { taskSessionId: input.id },
21919
+ operations
21920
+ }));
21921
+ await this.changeSetEngine.apply(root, draft, { approved: true });
21922
+ }
21923
+ async applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentFiles, createdAt) {
21924
+ const targetPaths = new Set(projectedFiles.map((file) => file.path));
21925
+ const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment(createdAt)}`;
21926
+ const backupRelativePath = uniqueBackupPath(root, backupBase);
21927
+ const manifestPath = `${backupRelativePath}/manifest.json`;
21928
+ const { backup, manifest } = architectureProjectionRollbackBackup(currentFiles, {
21929
+ createdAt,
21930
+ path: backupRelativePath,
21931
+ manifestPath
21932
+ });
21933
+ const removedPaths = currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
21934
+ await this.applyArchitectureProjectionChangeSet(root, {
21935
+ id: `changeset.ledger-rollback-${shortDigest3(digestJson({ createdAt, projectionDigest: architectureLedgerProjectionDigest(projectedFiles) }))}`,
21936
+ files: [
21937
+ ...currentFiles.map((file) => ({
21938
+ path: join8(backupRelativePath, archContextRelativePath(file.path)),
21939
+ body: file.body
21940
+ })),
21941
+ { path: manifestPath, body: `${JSON.stringify(manifest, null, 2)}
21942
+ ` },
21943
+ ...projectedFiles.map(({ path, body }) => ({ path, body }))
21944
+ ],
21945
+ removedPaths
21946
+ });
21947
+ return {
21948
+ backup,
21949
+ writtenPaths: projectedFiles.map((file) => file.path),
21950
+ removedPaths
21951
+ };
21952
+ }
21446
21953
  async ledgerState(root) {
21447
21954
  this.assertRunning();
21448
21955
  return okEnvelope("ledger.state", await this.architectureLedgerReadback(root));
@@ -21796,8 +22303,13 @@ class ArchctxDaemon {
21796
22303
  const scope = await this.architectureLedgerScope(root);
21797
22304
  const state = await this.localStore.readArchitectureLedgerState(scope);
21798
22305
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
21799
- if (writes)
21800
- writeArchitectureProjectionFiles(root, projectedFiles);
22306
+ if (writes) {
22307
+ await this.applyArchitectureProjectionChangeSet(root, {
22308
+ id: `changeset.ledger-project-${shortDigest3(architectureLedgerProjectionDigest(projectedFiles))}`,
22309
+ files: projectedFiles.map(({ path, body }) => ({ path, body })),
22310
+ removedPaths: []
22311
+ });
22312
+ }
21801
22313
  const drift = compareArchitectureLedgerStateToYaml({
21802
22314
  state,
21803
22315
  files: listModelFiles(root),
@@ -21947,7 +22459,7 @@ class ArchctxDaemon {
21947
22459
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
21948
22460
  const currentManagedFiles = listModelFiles(root).filter((file) => isArchitectureLedgerManagedModelPath(file.path));
21949
22461
  const backupPlan = architectureProjectionRollbackBackup(currentManagedFiles);
21950
- const writeResult = writes ? replaceArchitectureProjectionFilesForYamlRollback(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
22462
+ const writeResult = writes ? await this.applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
21951
22463
  const drift = compareArchitectureLedgerStateToYaml({
21952
22464
  state,
21953
22465
  files: listModelFiles(root),
@@ -21986,9 +22498,12 @@ class ArchctxDaemon {
21986
22498
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild currently requires --from-git");
21987
22499
  return this.withWriter(async () => {
21988
22500
  this.assertFreshWorktree(root, input.expectedWorktreeDigest, "ledger rebuild --from-git");
21989
- const scope = await this.architectureLedgerScope(root);
22501
+ const scope = await this.architectureLedgerGitScope(root);
21990
22502
  const files = listModelFiles(root);
21991
- const previousState = await this.localStore.readArchitectureLedgerState(scope);
22503
+ const exactReplay = await this.localStore.replayArchitectureLedger(scope);
22504
+ const exactScopeHasEvents = exactReplay.events.length > 0;
22505
+ const authorityScope = exactScopeHasEvents ? scope : await this.localStore.resolveLatestArchitectureLedgerScope(scope);
22506
+ const previousState = exactScopeHasEvents ? exactReplay.state : await this.localStore.readArchitectureLedgerState(authorityScope);
21992
22507
  const previousGraphDigest = architectureLedgerStateDigest(previousState);
21993
22508
  const rebuildCommand = input.acceptExternalProjection ? "archctx ledger rebuild --from-git --accept-external-projection" : "archctx ledger rebuild --from-git";
21994
22509
  const plan = planYamlToArchitectureLedgerRebuild({
@@ -21998,12 +22513,18 @@ class ArchctxDaemon {
21998
22513
  command: rebuildCommand,
21999
22514
  previousState
22000
22515
  });
22516
+ const importPlan = planYamlToArchitectureLedgerImport({
22517
+ ...scope,
22518
+ files,
22519
+ createdAt: this.clock(),
22520
+ command: rebuildCommand
22521
+ });
22001
22522
  if (plan.unsupportedFiles.length > 0) {
22002
22523
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild requires supported YAML model files");
22003
22524
  }
22004
22525
  const cursor = architectureLedgerGitCursorFromPlan({ ...scope, plan });
22005
22526
  const previousCursor = await this.localStore.readArchitectureLedgerSourceCursor({
22006
- ...scope,
22527
+ ...authorityScope,
22007
22528
  cursorId: ARCHITECTURE_LEDGER_GIT_CURSOR_ID
22008
22529
  });
22009
22530
  const cursorChanged = previousCursor?.cursorDigest !== cursor.cursorDigest;
@@ -22019,7 +22540,13 @@ class ArchctxDaemon {
22019
22540
  let rebuildStatus = "unchanged";
22020
22541
  let proposedExternalProjectionChange;
22021
22542
  if (previousGraphDigest === plan.graphDigest) {
22022
- if (cursorChanged) {
22543
+ if (!exactScopeHasEvents) {
22544
+ append = await this.localStore.appendArchitectureEvents({
22545
+ writer: "runtime-daemon",
22546
+ events: [importPlan.event]
22547
+ });
22548
+ rebuildStatus = isEmptyArchitectureLedgerState(previousState) ? "rebuilt" : "cursor-refreshed";
22549
+ } else if (cursorChanged) {
22023
22550
  const cursorPlan = planGitCursorRefreshToArchitectureLedgerEvent({
22024
22551
  ...scope,
22025
22552
  cursor,
@@ -22035,7 +22562,7 @@ class ArchctxDaemon {
22035
22562
  }
22036
22563
  } else if (!previousStateEmpty && !input.acceptExternalProjection) {
22037
22564
  const proposal = planExternalProjectionChangeToArchitectureLedgerEvent({
22038
- ...scope,
22565
+ ...authorityScope,
22039
22566
  files,
22040
22567
  createdAt: this.clock(),
22041
22568
  command: rebuildCommand,
@@ -22058,11 +22585,12 @@ class ArchctxDaemon {
22058
22585
  } else {
22059
22586
  append = await this.localStore.appendArchitectureEvents({
22060
22587
  writer: "runtime-daemon",
22061
- events: [plan.event]
22588
+ events: [exactScopeHasEvents ? plan.event : importPlan.event]
22062
22589
  });
22063
22590
  rebuildStatus = previousStateEmpty ? "rebuilt" : "external-projection-accepted";
22064
22591
  }
22065
- const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(scope);
22592
+ const replayScope = rebuildStatus === "external-projection-proposed" ? authorityScope : scope;
22593
+ const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(replayScope);
22066
22594
  const drift = compareArchitectureLedgerStateToYaml({
22067
22595
  state: replay.state,
22068
22596
  files: listModelFiles(root),
@@ -22208,6 +22736,9 @@ class ArchctxDaemon {
22208
22736
  return this.running ? this.architectureLedgerContextPort(root) : undefined;
22209
22737
  }
22210
22738
  async architectureLedgerScope(root) {
22739
+ return this.localStore.resolveArchitectureLedgerScope(await this.architectureLedgerGitScope(root));
22740
+ }
22741
+ async architectureLedgerGitScope(root) {
22211
22742
  const session = await this.openSession(root);
22212
22743
  const paths = runtimeStatePaths2(root);
22213
22744
  return {
@@ -22270,7 +22801,7 @@ class ArchctxDaemon {
22270
22801
  cleanup: "remove-run-root"
22271
22802
  }
22272
22803
  };
22273
- if (existsSync12(paths.lockPath) || existsSync12(paths.manifestPath)) {
22804
+ if (existsSync13(paths.lockPath) || existsSync13(paths.manifestPath)) {
22274
22805
  removePathWithRetry(paths.runRoot);
22275
22806
  throw new Error(`developer-review-run-already-active: ${input.challenge.challengeId}`);
22276
22807
  }
@@ -22327,7 +22858,7 @@ class ArchctxDaemon {
22327
22858
  const errors = [];
22328
22859
  if (run.worktree) {
22329
22860
  try {
22330
- const hadWorktree = existsSync12(run.worktree.worktreeRoot);
22861
+ const hadWorktree = existsSync13(run.worktree.worktreeRoot);
22331
22862
  removeDetachedReviewWorktree(run.worktree);
22332
22863
  if (hadWorktree)
22333
22864
  removed.push("worktree");
@@ -22341,7 +22872,7 @@ class ArchctxDaemon {
22341
22872
  ["lock", run.lockPath]
22342
22873
  ]) {
22343
22874
  try {
22344
- const existed = existsSync12(path);
22875
+ const existed = existsSync13(path);
22345
22876
  removePathWithRetry(path);
22346
22877
  if (existed)
22347
22878
  removed.push(kind);
@@ -22370,7 +22901,7 @@ class ArchctxDaemon {
22370
22901
  removedLocks: [],
22371
22902
  skippedActive: []
22372
22903
  };
22373
- if (!existsSync12(stateDir))
22904
+ if (!existsSync13(stateDir))
22374
22905
  return recovery;
22375
22906
  for (const entry of readdirSync8(stateDir).sort()) {
22376
22907
  if (!entry.endsWith(".json"))
@@ -22738,7 +23269,7 @@ class ArchctxDaemon {
22738
23269
  }
22739
23270
  async restoreRepositorySessions() {
22740
23271
  for (const record of await this.localStore.listRepositorySessions()) {
22741
- if (!record.root || !existsSync12(record.root))
23272
+ if (!record.root || !existsSync13(record.root))
22742
23273
  continue;
22743
23274
  if (repositoryFingerprint2(record.root) !== record.repositoryId)
22744
23275
  continue;
@@ -23531,7 +24062,7 @@ function recoverStaleDaemonControlFiles(root = process.cwd(), options = {}) {
23531
24062
  rmSync8(connectionPath, { force: true });
23532
24063
  removed.push(connectionReason);
23533
24064
  }
23534
- if (existsSync12(lockPath) && isStaleLock(lockPath)) {
24065
+ if (existsSync13(lockPath) && isStaleLock(lockPath)) {
23535
24066
  rmSync8(lockPath, { force: true });
23536
24067
  removed.push("stale-lock-file");
23537
24068
  }
@@ -23979,14 +24510,14 @@ function expandWorkspacePackageJson(root, pattern) {
23979
24510
  return [];
23980
24511
  if (!pattern.includes("*")) {
23981
24512
  const path = resolve15(root, pattern, "package.json");
23982
- return existsSync12(path) ? [path] : [];
24513
+ return existsSync13(path) ? [path] : [];
23983
24514
  }
23984
24515
  if (!pattern.endsWith("/*"))
23985
24516
  return [];
23986
24517
  const base = resolve15(root, pattern.slice(0, -2));
23987
- if (!existsSync12(base) || !statSync6(base).isDirectory())
24518
+ if (!existsSync13(base) || !statSync6(base).isDirectory())
23988
24519
  return [];
23989
- return readdirSync8(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve15(base, entry.name, "package.json")).filter((path) => existsSync12(path));
24520
+ return readdirSync8(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve15(base, entry.name, "package.json")).filter((path) => existsSync13(path));
23990
24521
  }
23991
24522
  function exactVersionFromManifest(manifest, packageName) {
23992
24523
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
@@ -24002,7 +24533,7 @@ function exactVersionFromManifest(manifest, packageName) {
24002
24533
  return;
24003
24534
  }
24004
24535
  function readJsonFile(path) {
24005
- if (!existsSync12(path))
24536
+ if (!existsSync13(path))
24006
24537
  return;
24007
24538
  try {
24008
24539
  return JSON.parse(readFileSync11(path, "utf8"));
@@ -24015,7 +24546,7 @@ function isExactPackageVersion(value) {
24015
24546
  }
24016
24547
  function readContext7Lockfile(root) {
24017
24548
  const path = resolve15(root, CONTEXT7_LOCKFILE);
24018
- if (!existsSync12(path)) {
24549
+ if (!existsSync13(path)) {
24019
24550
  return {
24020
24551
  schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
24021
24552
  provider: "context7",
@@ -24264,48 +24795,9 @@ function auditApproveResultPayload(runId, status, totalCount, issuedIssues) {
24264
24795
  issuedIssues
24265
24796
  };
24266
24797
  }
24267
- function writeArchitectureProjectionFiles(root, files) {
24268
- for (const file of files) {
24269
- const absolute = resolve15(root, file.path);
24270
- mkdirSync7(dirname8(absolute), { recursive: true });
24271
- writeFileSync6(absolute, file.body.endsWith(`
24272
- `) ? file.body : `${file.body}
24273
- `, "utf8");
24274
- }
24275
- }
24276
- function replaceArchitectureProjectionFilesForYamlRollback(root, projectedFiles, currentFiles, createdAt) {
24277
- const targetPaths = new Set(projectedFiles.map((file) => file.path));
24278
- const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment(createdAt)}`;
24279
- const backupRelativePath = uniqueBackupPath(root, backupBase);
24280
- const manifestPath = `${backupRelativePath}/manifest.json`;
24281
- const { backup, manifest } = architectureProjectionRollbackBackup(currentFiles, {
24282
- createdAt,
24283
- path: backupRelativePath,
24284
- manifestPath
24285
- });
24286
- for (const file of currentFiles) {
24287
- const backupPath = join8(backupRelativePath, archContextRelativePath(file.path));
24288
- const absolute = resolve15(root, backupPath);
24289
- mkdirSync7(dirname8(absolute), { recursive: true });
24290
- writeFileSync6(absolute, file.body, "utf8");
24291
- }
24292
- const manifestAbsolute = resolve15(root, manifestPath);
24293
- mkdirSync7(dirname8(manifestAbsolute), { recursive: true });
24294
- writeFileSync6(manifestAbsolute, `${JSON.stringify(manifest, null, 2)}
24295
- `, "utf8");
24296
- const removedPaths = [];
24297
- for (const file of currentFiles) {
24298
- if (targetPaths.has(file.path))
24299
- continue;
24300
- rmSync8(resolve15(root, file.path), { force: true });
24301
- removedPaths.push(file.path);
24302
- }
24303
- writeArchitectureProjectionFiles(root, projectedFiles);
24304
- return {
24305
- backup,
24306
- writtenPaths: projectedFiles.map((file) => file.path),
24307
- removedPaths
24308
- };
24798
+ function expectedFileHash(root, path) {
24799
+ const absolute = resolve15(root, path);
24800
+ return existsSync13(absolute) ? digestJson({ body: readFileSync11(absolute, "utf8") }) : "missing";
24309
24801
  }
24310
24802
  function architectureProjectionRollbackBackup(files, options = {}) {
24311
24803
  const manifest = {
@@ -24332,7 +24824,7 @@ function architectureProjectionRollbackBackup(files, options = {}) {
24332
24824
  function uniqueBackupPath(root, backupBase) {
24333
24825
  let candidate = backupBase;
24334
24826
  let suffix = 2;
24335
- while (existsSync12(resolve15(root, candidate))) {
24827
+ while (existsSync13(resolve15(root, candidate))) {
24336
24828
  candidate = `${backupBase}-${suffix}`;
24337
24829
  suffix += 1;
24338
24830
  }
@@ -24341,7 +24833,7 @@ function uniqueBackupPath(root, backupBase) {
24341
24833
  function uniqueRuntimeBackupPath(backupBase) {
24342
24834
  let candidate = backupBase;
24343
24835
  let suffix = 2;
24344
- while (existsSync12(candidate)) {
24836
+ while (existsSync13(candidate)) {
24345
24837
  candidate = backupBase.replace(/\.sqlite$/, `-${suffix}.sqlite`);
24346
24838
  suffix += 1;
24347
24839
  }
@@ -24386,7 +24878,7 @@ function isValidRuntimeRpcConnection(value) {
24386
24878
  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";
24387
24879
  }
24388
24880
  function staleConnectionFileReason(path, removeUnhealthyConnection) {
24389
- if (!existsSync12(path))
24881
+ if (!existsSync13(path))
24390
24882
  return;
24391
24883
  try {
24392
24884
  if (!isPrivateControlFile(path))
@@ -24448,7 +24940,7 @@ function parsePracticeCheckpointBaselineState(state, repositoryId, taskSessionId
24448
24940
  return record;
24449
24941
  }
24450
24942
  function completeTaskProjectionDrift(root) {
24451
- if (!existsSync12(resolve15(root, "docs/architecture/.projection-manifest.json")))
24943
+ if (!existsSync13(resolve15(root, "docs/architecture/.projection-manifest.json")))
24452
24944
  return;
24453
24945
  const loaded = loadArchitectureDocumentationInputs(root);
24454
24946
  const sourceDigest = architectureDocumentationSourceDigest({
@@ -24724,7 +25216,7 @@ init_src();
24724
25216
  // packages/local-runtime/runtime-daemon/src/index.ts
24725
25217
  import { randomBytes as randomBytes2 } from "node:crypto";
24726
25218
  import { execFileSync as execFileSync7 } from "node:child_process";
24727
- import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync13, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync7, writeFileSync as writeFileSync7 } from "node:fs";
25219
+ import { chmodSync as chmodSync4, closeSync as closeSync6, existsSync as existsSync14, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync7, writeFileSync as writeFileSync7 } from "node:fs";
24728
25220
  import { createServer as createServer2 } from "node:http";
24729
25221
  import { tmpdir as tmpdir4 } from "node:os";
24730
25222
  import { dirname as dirname9, join as join9, resolve as resolve16 } from "node:path";
@@ -24837,7 +25329,8 @@ class ArchitectureLedgerReadModelStore2 {
24837
25329
  return this.fallback.writeChangeSetPreview(changeSet);
24838
25330
  }
24839
25331
  async loadLedgerModel(workspace) {
24840
- const state = await this.localStore.readArchitectureLedgerState(architectureLedgerScopeForWorkspace2(workspace));
25332
+ const scope = await this.localStore.resolveArchitectureLedgerScope(architectureLedgerScopeForWorkspace2(workspace));
25333
+ const state = await this.localStore.readArchitectureLedgerState(scope);
24841
25334
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state).map((file) => ({
24842
25335
  path: file.path,
24843
25336
  body: file.body,
@@ -24921,7 +25414,7 @@ class ArchctxDaemon2 {
24921
25414
  this.readModelStore = new ArchitectureLedgerReadModelStore2(this.modelStore, this.localStore, this.architectureLedger);
24922
25415
  this.changeSetEngine = deps.changeSetEngine ?? new ChangeSetEngine({
24923
25416
  modelStore: this.modelStore,
24924
- projection: { rebuildGeneratedProjection },
25417
+ projection: { planGeneratedProjection },
24925
25418
  journal: this.localStore
24926
25419
  });
24927
25420
  this.devicePrivateKeySigner = deps.devicePrivateKeySigner;
@@ -25873,7 +26366,7 @@ class ArchctxDaemon2 {
25873
26366
  const absolute = resolve16(session.workspace.root, path);
25874
26367
  const body = `${JSON.stringify(waiver, null, 2)}
25875
26368
  `;
25876
- const expectedHash = existsSync13(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
26369
+ const expectedHash = existsSync14(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
25877
26370
  const draft = this.changeSetEngine.plan({
25878
26371
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
25879
26372
  base: {
@@ -26212,6 +26705,16 @@ class ArchctxDaemon2 {
26212
26705
  const draft = this.changesets.get(input.id);
26213
26706
  if (!draft)
26214
26707
  throw new Error(`Unknown ChangeSet: ${input.id}`);
26708
+ if (draft.base.headSha !== session.workspace.headSha)
26709
+ throw new Error("ChangeSet HEAD changed before apply");
26710
+ if (draft.base.worktreeDigest !== current)
26711
+ throw new Error("ChangeSet worktree digest changed before apply");
26712
+ const currentModel = await this.readModelStore.validateModel(session.workspace);
26713
+ if (!currentModel.valid) {
26714
+ throw new Error(`ChangeSet base model is invalid: ${currentModel.errors.join("; ") || "unknown validation error"}`);
26715
+ }
26716
+ if (draft.base.modelDigest !== currentModel.modelDigest)
26717
+ throw new Error("ChangeSet model digest changed before apply");
26215
26718
  const approved = input.approved ? this.changeSetEngine.approve(draft) : draft;
26216
26719
  let ledgerAppend;
26217
26720
  const writesLedger = architectureLedgerWriteAppendsEvents2(this.architectureLedger.writeMode);
@@ -26228,6 +26731,7 @@ class ArchctxDaemon2 {
26228
26731
  relationCount: appended.relationCount,
26229
26732
  constraintCount: appended.constraintCount
26230
26733
  };
26734
+ return { journalCommitted: Boolean(journalId) };
26231
26735
  } : undefined
26232
26736
  });
26233
26737
  return okEnvelope("apply_update", {
@@ -26261,14 +26765,72 @@ class ArchctxDaemon2 {
26261
26765
  });
26262
26766
  if (journalId)
26263
26767
  await this.localStore.recordChangeSetLedgerPlan(journalId, { event: plan.event });
26264
- const result = await this.localStore.appendArchitectureEvents({
26265
- writer: "runtime-daemon",
26266
- events: [plan.event]
26267
- });
26268
- if (journalId)
26269
- await this.localStore.recordChangeSetLedgerAppend(journalId, { result });
26768
+ const appendInput = { writer: "runtime-daemon", events: [plan.event] };
26769
+ const result = journalId ? await this.localStore.appendArchitectureEventsAndCommitChangeSet(journalId, appendInput) : await this.localStore.appendArchitectureEvents(appendInput);
26270
26770
  return result;
26271
26771
  }
26772
+ async applyArchitectureProjectionChangeSet(root, input) {
26773
+ const session = await this.openSession(root);
26774
+ const model = await this.modelStore.validateModel(session.workspace);
26775
+ if (!model.valid)
26776
+ throw new Error(`Architecture projection ChangeSet base model is invalid: ${model.errors.join("; ")}`);
26777
+ const projectionFiles = input.files.map((file) => ({
26778
+ path: file.path,
26779
+ body: file.body.endsWith(`
26780
+ `) ? file.body : `${file.body}
26781
+ `,
26782
+ expectedHash: expectedFileHash2(root, file.path)
26783
+ }));
26784
+ const operations = [
26785
+ ...projectionFiles.length > 0 ? [{ op: "render_projection", expectedHash: "missing", projectionFiles }] : [],
26786
+ ...input.removedPaths.map((path) => ({
26787
+ op: "delete_entity",
26788
+ path,
26789
+ expectedHash: expectedFileHash2(root, path)
26790
+ }))
26791
+ ];
26792
+ const draft = this.changeSetEngine.approve(this.changeSetEngine.plan({
26793
+ id: input.id,
26794
+ base: {
26795
+ headSha: session.workspace.headSha,
26796
+ worktreeDigest: session.snapshot.worktreeDigest,
26797
+ modelDigest: model.modelDigest
26798
+ },
26799
+ reason: { taskSessionId: input.id },
26800
+ operations
26801
+ }));
26802
+ await this.changeSetEngine.apply(root, draft, { approved: true });
26803
+ }
26804
+ async applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentFiles, createdAt) {
26805
+ const targetPaths = new Set(projectedFiles.map((file) => file.path));
26806
+ const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment2(createdAt)}`;
26807
+ const backupRelativePath = uniqueBackupPath2(root, backupBase);
26808
+ const manifestPath = `${backupRelativePath}/manifest.json`;
26809
+ const { backup, manifest } = architectureProjectionRollbackBackup2(currentFiles, {
26810
+ createdAt,
26811
+ path: backupRelativePath,
26812
+ manifestPath
26813
+ });
26814
+ const removedPaths = currentFiles.filter((file) => !targetPaths.has(file.path)).map((file) => file.path);
26815
+ await this.applyArchitectureProjectionChangeSet(root, {
26816
+ id: `changeset.ledger-rollback-${shortDigest4(digestJson({ createdAt, projectionDigest: architectureLedgerProjectionDigest(projectedFiles) }))}`,
26817
+ files: [
26818
+ ...currentFiles.map((file) => ({
26819
+ path: join9(backupRelativePath, archContextRelativePath2(file.path)),
26820
+ body: file.body
26821
+ })),
26822
+ { path: manifestPath, body: `${JSON.stringify(manifest, null, 2)}
26823
+ ` },
26824
+ ...projectedFiles.map(({ path, body }) => ({ path, body }))
26825
+ ],
26826
+ removedPaths
26827
+ });
26828
+ return {
26829
+ backup,
26830
+ writtenPaths: projectedFiles.map((file) => file.path),
26831
+ removedPaths
26832
+ };
26833
+ }
26272
26834
  async ledgerState(root) {
26273
26835
  this.assertRunning();
26274
26836
  return okEnvelope("ledger.state", await this.architectureLedgerReadback(root));
@@ -26622,8 +27184,13 @@ class ArchctxDaemon2 {
26622
27184
  const scope = await this.architectureLedgerScope(root);
26623
27185
  const state = await this.localStore.readArchitectureLedgerState(scope);
26624
27186
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
26625
- if (writes)
26626
- writeArchitectureProjectionFiles2(root, projectedFiles);
27187
+ if (writes) {
27188
+ await this.applyArchitectureProjectionChangeSet(root, {
27189
+ id: `changeset.ledger-project-${shortDigest4(architectureLedgerProjectionDigest(projectedFiles))}`,
27190
+ files: projectedFiles.map(({ path, body }) => ({ path, body })),
27191
+ removedPaths: []
27192
+ });
27193
+ }
26627
27194
  const drift = compareArchitectureLedgerStateToYaml({
26628
27195
  state,
26629
27196
  files: listModelFiles(root),
@@ -26773,7 +27340,7 @@ class ArchctxDaemon2 {
26773
27340
  const projectedFiles = projectArchitectureLedgerStateToYamlFiles(state);
26774
27341
  const currentManagedFiles = listModelFiles(root).filter((file) => isArchitectureLedgerManagedModelPath2(file.path));
26775
27342
  const backupPlan = architectureProjectionRollbackBackup2(currentManagedFiles);
26776
- const writeResult = writes ? replaceArchitectureProjectionFilesForYamlRollback2(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
27343
+ const writeResult = writes ? await this.applyArchitectureProjectionRollbackChangeSet(root, projectedFiles, currentManagedFiles, this.clock()) : { backup: backupPlan.backup, writtenPaths: [], removedPaths: [] };
26777
27344
  const drift = compareArchitectureLedgerStateToYaml({
26778
27345
  state,
26779
27346
  files: listModelFiles(root),
@@ -26812,9 +27379,12 @@ class ArchctxDaemon2 {
26812
27379
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild currently requires --from-git");
26813
27380
  return this.withWriter(async () => {
26814
27381
  this.assertFreshWorktree(root, input.expectedWorktreeDigest, "ledger rebuild --from-git");
26815
- const scope = await this.architectureLedgerScope(root);
27382
+ const scope = await this.architectureLedgerGitScope(root);
26816
27383
  const files = listModelFiles(root);
26817
- const previousState = await this.localStore.readArchitectureLedgerState(scope);
27384
+ const exactReplay = await this.localStore.replayArchitectureLedger(scope);
27385
+ const exactScopeHasEvents = exactReplay.events.length > 0;
27386
+ const authorityScope = exactScopeHasEvents ? scope : await this.localStore.resolveLatestArchitectureLedgerScope(scope);
27387
+ const previousState = exactScopeHasEvents ? exactReplay.state : await this.localStore.readArchitectureLedgerState(authorityScope);
26818
27388
  const previousGraphDigest = architectureLedgerStateDigest(previousState);
26819
27389
  const rebuildCommand = input.acceptExternalProjection ? "archctx ledger rebuild --from-git --accept-external-projection" : "archctx ledger rebuild --from-git";
26820
27390
  const plan = planYamlToArchitectureLedgerRebuild({
@@ -26824,12 +27394,18 @@ class ArchctxDaemon2 {
26824
27394
  command: rebuildCommand,
26825
27395
  previousState
26826
27396
  });
27397
+ const importPlan = planYamlToArchitectureLedgerImport({
27398
+ ...scope,
27399
+ files,
27400
+ createdAt: this.clock(),
27401
+ command: rebuildCommand
27402
+ });
26827
27403
  if (plan.unsupportedFiles.length > 0) {
26828
27404
  return errorEnvelope("ledger.rebuild", "AC_SCHEMA_INVALID", "ledger rebuild requires supported YAML model files");
26829
27405
  }
26830
27406
  const cursor = architectureLedgerGitCursorFromPlan({ ...scope, plan });
26831
27407
  const previousCursor = await this.localStore.readArchitectureLedgerSourceCursor({
26832
- ...scope,
27408
+ ...authorityScope,
26833
27409
  cursorId: ARCHITECTURE_LEDGER_GIT_CURSOR_ID
26834
27410
  });
26835
27411
  const cursorChanged = previousCursor?.cursorDigest !== cursor.cursorDigest;
@@ -26845,7 +27421,13 @@ class ArchctxDaemon2 {
26845
27421
  let rebuildStatus = "unchanged";
26846
27422
  let proposedExternalProjectionChange;
26847
27423
  if (previousGraphDigest === plan.graphDigest) {
26848
- if (cursorChanged) {
27424
+ if (!exactScopeHasEvents) {
27425
+ append = await this.localStore.appendArchitectureEvents({
27426
+ writer: "runtime-daemon",
27427
+ events: [importPlan.event]
27428
+ });
27429
+ rebuildStatus = isEmptyArchitectureLedgerState2(previousState) ? "rebuilt" : "cursor-refreshed";
27430
+ } else if (cursorChanged) {
26849
27431
  const cursorPlan = planGitCursorRefreshToArchitectureLedgerEvent({
26850
27432
  ...scope,
26851
27433
  cursor,
@@ -26861,7 +27443,7 @@ class ArchctxDaemon2 {
26861
27443
  }
26862
27444
  } else if (!previousStateEmpty && !input.acceptExternalProjection) {
26863
27445
  const proposal = planExternalProjectionChangeToArchitectureLedgerEvent({
26864
- ...scope,
27446
+ ...authorityScope,
26865
27447
  files,
26866
27448
  createdAt: this.clock(),
26867
27449
  command: rebuildCommand,
@@ -26884,11 +27466,12 @@ class ArchctxDaemon2 {
26884
27466
  } else {
26885
27467
  append = await this.localStore.appendArchitectureEvents({
26886
27468
  writer: "runtime-daemon",
26887
- events: [plan.event]
27469
+ events: [exactScopeHasEvents ? plan.event : importPlan.event]
26888
27470
  });
26889
27471
  rebuildStatus = previousStateEmpty ? "rebuilt" : "external-projection-accepted";
26890
27472
  }
26891
- const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(scope);
27473
+ const replayScope = rebuildStatus === "external-projection-proposed" ? authorityScope : scope;
27474
+ const replay = await this.localStore.rebuildArchitectureLedgerCurrentState(replayScope);
26892
27475
  const drift = compareArchitectureLedgerStateToYaml({
26893
27476
  state: replay.state,
26894
27477
  files: listModelFiles(root),
@@ -27034,6 +27617,9 @@ class ArchctxDaemon2 {
27034
27617
  return this.running ? this.architectureLedgerContextPort(root) : undefined;
27035
27618
  }
27036
27619
  async architectureLedgerScope(root) {
27620
+ return this.localStore.resolveArchitectureLedgerScope(await this.architectureLedgerGitScope(root));
27621
+ }
27622
+ async architectureLedgerGitScope(root) {
27037
27623
  const session = await this.openSession(root);
27038
27624
  const paths = runtimeStatePaths2(root);
27039
27625
  return {
@@ -27096,7 +27682,7 @@ class ArchctxDaemon2 {
27096
27682
  cleanup: "remove-run-root"
27097
27683
  }
27098
27684
  };
27099
- if (existsSync13(paths.lockPath) || existsSync13(paths.manifestPath)) {
27685
+ if (existsSync14(paths.lockPath) || existsSync14(paths.manifestPath)) {
27100
27686
  removePathWithRetry(paths.runRoot);
27101
27687
  throw new Error(`developer-review-run-already-active: ${input.challenge.challengeId}`);
27102
27688
  }
@@ -27153,7 +27739,7 @@ class ArchctxDaemon2 {
27153
27739
  const errors = [];
27154
27740
  if (run.worktree) {
27155
27741
  try {
27156
- const hadWorktree = existsSync13(run.worktree.worktreeRoot);
27742
+ const hadWorktree = existsSync14(run.worktree.worktreeRoot);
27157
27743
  removeDetachedReviewWorktree(run.worktree);
27158
27744
  if (hadWorktree)
27159
27745
  removed.push("worktree");
@@ -27167,7 +27753,7 @@ class ArchctxDaemon2 {
27167
27753
  ["lock", run.lockPath]
27168
27754
  ]) {
27169
27755
  try {
27170
- const existed = existsSync13(path);
27756
+ const existed = existsSync14(path);
27171
27757
  removePathWithRetry(path);
27172
27758
  if (existed)
27173
27759
  removed.push(kind);
@@ -27196,7 +27782,7 @@ class ArchctxDaemon2 {
27196
27782
  removedLocks: [],
27197
27783
  skippedActive: []
27198
27784
  };
27199
- if (!existsSync13(stateDir))
27785
+ if (!existsSync14(stateDir))
27200
27786
  return recovery;
27201
27787
  for (const entry of readdirSync9(stateDir).sort()) {
27202
27788
  if (!entry.endsWith(".json"))
@@ -27564,7 +28150,7 @@ class ArchctxDaemon2 {
27564
28150
  }
27565
28151
  async restoreRepositorySessions() {
27566
28152
  for (const record of await this.localStore.listRepositorySessions()) {
27567
- if (!record.root || !existsSync13(record.root))
28153
+ if (!record.root || !existsSync14(record.root))
27568
28154
  continue;
27569
28155
  if (repositoryFingerprint2(record.root) !== record.repositoryId)
27570
28156
  continue;
@@ -28470,14 +29056,14 @@ function expandWorkspacePackageJson2(root, pattern) {
28470
29056
  return [];
28471
29057
  if (!pattern.includes("*")) {
28472
29058
  const path = resolve16(root, pattern, "package.json");
28473
- return existsSync13(path) ? [path] : [];
29059
+ return existsSync14(path) ? [path] : [];
28474
29060
  }
28475
29061
  if (!pattern.endsWith("/*"))
28476
29062
  return [];
28477
29063
  const base = resolve16(root, pattern.slice(0, -2));
28478
- if (!existsSync13(base) || !statSync7(base).isDirectory())
29064
+ if (!existsSync14(base) || !statSync7(base).isDirectory())
28479
29065
  return [];
28480
- return readdirSync9(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve16(base, entry.name, "package.json")).filter((path) => existsSync13(path));
29066
+ return readdirSync9(base, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => resolve16(base, entry.name, "package.json")).filter((path) => existsSync14(path));
28481
29067
  }
28482
29068
  function exactVersionFromManifest2(manifest, packageName) {
28483
29069
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
@@ -28493,7 +29079,7 @@ function exactVersionFromManifest2(manifest, packageName) {
28493
29079
  return;
28494
29080
  }
28495
29081
  function readJsonFile2(path) {
28496
- if (!existsSync13(path))
29082
+ if (!existsSync14(path))
28497
29083
  return;
28498
29084
  try {
28499
29085
  return JSON.parse(readFileSync12(path, "utf8"));
@@ -28506,7 +29092,7 @@ function isExactPackageVersion2(value) {
28506
29092
  }
28507
29093
  function readContext7Lockfile2(root) {
28508
29094
  const path = resolve16(root, CONTEXT7_LOCKFILE2);
28509
- if (!existsSync13(path)) {
29095
+ if (!existsSync14(path)) {
28510
29096
  return {
28511
29097
  schemaVersion: CONTEXT7_LOCKFILE_SCHEMA_VERSION,
28512
29098
  provider: "context7",
@@ -28735,48 +29321,9 @@ function auditApproveResultPayload2(runId, status, totalCount, issuedIssues) {
28735
29321
  issuedIssues
28736
29322
  };
28737
29323
  }
28738
- function writeArchitectureProjectionFiles2(root, files) {
28739
- for (const file of files) {
28740
- const absolute = resolve16(root, file.path);
28741
- mkdirSync8(dirname9(absolute), { recursive: true });
28742
- writeFileSync7(absolute, file.body.endsWith(`
28743
- `) ? file.body : `${file.body}
28744
- `, "utf8");
28745
- }
28746
- }
28747
- function replaceArchitectureProjectionFilesForYamlRollback2(root, projectedFiles, currentFiles, createdAt) {
28748
- const targetPaths = new Set(projectedFiles.map((file) => file.path));
28749
- const backupBase = `.archcontext/backups/ledger-rollback/${safePathSegment2(createdAt)}`;
28750
- const backupRelativePath = uniqueBackupPath2(root, backupBase);
28751
- const manifestPath = `${backupRelativePath}/manifest.json`;
28752
- const { backup, manifest } = architectureProjectionRollbackBackup2(currentFiles, {
28753
- createdAt,
28754
- path: backupRelativePath,
28755
- manifestPath
28756
- });
28757
- for (const file of currentFiles) {
28758
- const backupPath = join9(backupRelativePath, archContextRelativePath2(file.path));
28759
- const absolute = resolve16(root, backupPath);
28760
- mkdirSync8(dirname9(absolute), { recursive: true });
28761
- writeFileSync7(absolute, file.body, "utf8");
28762
- }
28763
- const manifestAbsolute = resolve16(root, manifestPath);
28764
- mkdirSync8(dirname9(manifestAbsolute), { recursive: true });
28765
- writeFileSync7(manifestAbsolute, `${JSON.stringify(manifest, null, 2)}
28766
- `, "utf8");
28767
- const removedPaths = [];
28768
- for (const file of currentFiles) {
28769
- if (targetPaths.has(file.path))
28770
- continue;
28771
- rmSync9(resolve16(root, file.path), { force: true });
28772
- removedPaths.push(file.path);
28773
- }
28774
- writeArchitectureProjectionFiles2(root, projectedFiles);
28775
- return {
28776
- backup,
28777
- writtenPaths: projectedFiles.map((file) => file.path),
28778
- removedPaths
28779
- };
29324
+ function expectedFileHash2(root, path) {
29325
+ const absolute = resolve16(root, path);
29326
+ return existsSync14(absolute) ? digestJson({ body: readFileSync12(absolute, "utf8") }) : "missing";
28780
29327
  }
28781
29328
  function architectureProjectionRollbackBackup2(files, options = {}) {
28782
29329
  const manifest = {
@@ -28803,7 +29350,7 @@ function architectureProjectionRollbackBackup2(files, options = {}) {
28803
29350
  function uniqueBackupPath2(root, backupBase) {
28804
29351
  let candidate = backupBase;
28805
29352
  let suffix = 2;
28806
- while (existsSync13(resolve16(root, candidate))) {
29353
+ while (existsSync14(resolve16(root, candidate))) {
28807
29354
  candidate = `${backupBase}-${suffix}`;
28808
29355
  suffix += 1;
28809
29356
  }
@@ -28812,7 +29359,7 @@ function uniqueBackupPath2(root, backupBase) {
28812
29359
  function uniqueRuntimeBackupPath2(backupBase) {
28813
29360
  let candidate = backupBase;
28814
29361
  let suffix = 2;
28815
- while (existsSync13(candidate)) {
29362
+ while (existsSync14(candidate)) {
28816
29363
  candidate = backupBase.replace(/\.sqlite$/, `-${suffix}.sqlite`);
28817
29364
  suffix += 1;
28818
29365
  }
@@ -28874,7 +29421,7 @@ function parsePracticeCheckpointBaselineState2(state, repositoryId, taskSessionI
28874
29421
  return record;
28875
29422
  }
28876
29423
  function completeTaskProjectionDrift2(root) {
28877
- if (!existsSync13(resolve16(root, "docs/architecture/.projection-manifest.json")))
29424
+ if (!existsSync14(resolve16(root, "docs/architecture/.projection-manifest.json")))
28878
29425
  return;
28879
29426
  const loaded = loadArchitectureDocumentationInputs(root);
28880
29427
  const sourceDigest = architectureDocumentationSourceDigest({
@@ -28991,6 +29538,7 @@ function writeHtml2(response, statusCode, body) {
28991
29538
  }
28992
29539
 
28993
29540
  // packages/surfaces/mcp-local/src/index.ts
29541
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
28994
29542
  var LOCAL_MCP_TOOLS = [
28995
29543
  {
28996
29544
  name: "archcontext_prepare_task",
@@ -29302,9 +29850,17 @@ async function runStdioMcpLoop(input, output, log = (line) => process.stderr.wri
29302
29850
  log("[archctx-mcp] started");
29303
29851
  for await (const line of input) {
29304
29852
  const message = JSON.parse(line);
29853
+ if (message.id === undefined)
29854
+ continue;
29305
29855
  let result;
29306
- if (message.method === "tools/list") {
29856
+ if (message.method === "initialize") {
29857
+ result = mcpInitializeResult(message.params?.protocolVersion);
29858
+ } else if (message.method === "ping") {
29859
+ result = {};
29860
+ } else if (message.method === "tools/list") {
29307
29861
  result = { tools: server.listTools() };
29862
+ } else if (message.method === "tools/call") {
29863
+ result = await server.callTool(message.params?.name, message.params?.arguments ?? {});
29308
29864
  } else if (message.method === "resources/list") {
29309
29865
  result = { resources: await server.listResources(message.params?.root) };
29310
29866
  } else if (message.method === "resources/read") {
@@ -29314,11 +29870,28 @@ async function runStdioMcpLoop(input, output, log = (line) => process.stderr.wri
29314
29870
  contents: content === undefined ? [] : [{ uri, mimeType: "application/json", text: JSON.stringify(content) }]
29315
29871
  };
29316
29872
  } else {
29317
- result = await server.callTool(message.params?.name, message.params?.arguments ?? {});
29873
+ result = {
29874
+ content: errorEnvelope("mcp", "AC_SCHEMA_INVALID", `Unknown MCP method: ${message.method}`),
29875
+ dataClassification: "local-metadata"
29876
+ };
29318
29877
  }
29319
29878
  output(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }));
29320
29879
  }
29321
29880
  }
29881
+ function mcpInitializeResult(clientProtocolVersion) {
29882
+ const product = productVersionManifest().product;
29883
+ return {
29884
+ protocolVersion: typeof clientProtocolVersion === "string" ? clientProtocolVersion : MCP_PROTOCOL_VERSION,
29885
+ capabilities: {
29886
+ tools: { listChanged: false },
29887
+ resources: { subscribe: false, listChanged: false }
29888
+ },
29889
+ serverInfo: {
29890
+ name: product.name,
29891
+ version: product.version
29892
+ }
29893
+ };
29894
+ }
29322
29895
  function isExternalDocumentationResourceUri(uri) {
29323
29896
  return /^archcontext:\/\/external-docs\/context7\/sha256:[0-9a-f]{64}$/.test(uri);
29324
29897
  }
@@ -29365,6 +29938,8 @@ if (__require.main == __require.module) {
29365
29938
  `);
29366
29939
  if (result.ok === false)
29367
29940
  process.exitCode = 1;
29941
+ if (command === "resolve")
29942
+ process.exitCode = resolveCommandExitCode(result);
29368
29943
  }
29369
29944
  }
29370
29945
  async function* stdinLines() {
@@ -29621,6 +30196,37 @@ async function runCliUnchecked(command2 = "help", args2 = [], cwd, deps = {}) {
29621
30196
  revocation: "archctx tunnel --revoke"
29622
30197
  }
29623
30198
  };
30199
+ case "resolve": {
30200
+ const path = readFlag(args2, "--path");
30201
+ if (!path)
30202
+ return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve requires --path");
30203
+ if (!isRepoRelativePosixPath(path)) {
30204
+ return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve --path must be a repository-relative POSIX path");
30205
+ }
30206
+ const model = loadNativeModelFromArchContext(cwd);
30207
+ const outcome = resolveArchitectureOwnerForPath(model.nodes, path);
30208
+ if (outcome.status === "matched") {
30209
+ const node = outcome.node;
30210
+ return okEnvelope("resolve", {
30211
+ matched: true,
30212
+ ambiguous: false,
30213
+ stableId: node.id,
30214
+ kind: node.kind,
30215
+ name: node.name,
30216
+ ...node.source ? { source: node.source } : {},
30217
+ ...node.extensions ? { extensions: node.extensions } : {}
30218
+ });
30219
+ }
30220
+ if (outcome.status === "ambiguous") {
30221
+ return okEnvelope("resolve", {
30222
+ matched: false,
30223
+ ambiguous: true,
30224
+ path,
30225
+ candidates: outcome.candidates.map((node) => node.id)
30226
+ });
30227
+ }
30228
+ return okEnvelope("resolve", { matched: false, ambiguous: false, path });
30229
+ }
29624
30230
  case "help":
29625
30231
  default:
29626
30232
  return {
@@ -29628,8 +30234,8 @@ async function runCliUnchecked(command2 = "help", args2 = [], cwd, deps = {}) {
29628
30234
  ok: true,
29629
30235
  requestId: "help",
29630
30236
  data: {
29631
- commands: ["init", "sync", "validate", "context", "status", "daemon", "repo", "landscape", "ledger", "book", "recommendations", "explore", "prepare", "practices", "checkpoint", "hook", "hooks", "investigate", "agents", "jobs", "audit", "plan", "apply", "review", "complete", "github", "config", "mcp", "install", "uninstall", "doctor", "update", "paths", "privacy-audit", "export", "import", "tunnel"],
29632
- examples: ["archctx init --name MyApp", "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 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 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:<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 tunnel"]
30237
+ commands: ["init", "sync", "validate", "context", "status", "daemon", "repo", "landscape", "ledger", "book", "recommendations", "explore", "prepare", "practices", "checkpoint", "hook", "hooks", "investigate", "agents", "jobs", "audit", "plan", "apply", "review", "complete", "github", "config", "mcp", "install", "uninstall", "doctor", "update", "paths", "privacy-audit", "export", "import", "resolve", "tunnel"],
30238
+ examples: ["archctx init --name MyApp", "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 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 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:<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"]
29633
30239
  }
29634
30240
  };
29635
30241
  }
@@ -30074,37 +30680,10 @@ function buildArchitectureDocsProjection(root, generatedAt) {
30074
30680
  sourceDigest,
30075
30681
  generatedAt
30076
30682
  });
30077
- const manifestBody = `${JSON.stringify({
30078
- schemaVersion: "archcontext.architecture-docs-projection-manifest/v1",
30079
- rendererVersion: plan.rendererVersion,
30080
- sourceDigest: plan.sourceDigest,
30081
- projectionDigest: plan.projectionDigest,
30082
- targetCount: plan.targets.length,
30083
- fileCount: plan.files.length,
30084
- targets: plan.targets.map((target) => ({
30085
- targetId: target.targetId,
30086
- type: target.type,
30087
- scope: target.scope,
30088
- path: target.path,
30089
- ownership: target.ownership,
30090
- rendererVersion: target.rendererVersion,
30091
- format: target.format,
30092
- sourceDigest: target.sourceDigest,
30093
- outputDigest: target.outputDigest
30094
- }))
30095
- }, null, 2)}
30096
- `;
30097
- const manifest = {
30098
- path: "docs/architecture/.projection-manifest.json",
30099
- body: manifestBody,
30100
- digest: digestJson({ path: "docs/architecture/.projection-manifest.json", body: manifestBody }),
30101
- target: plan.targets[0],
30102
- generatedBodyDigest: digestJson({ body: manifestBody })
30103
- };
30104
30683
  return {
30105
30684
  plan,
30106
- manifest,
30107
- files: [...plan.files, manifest]
30685
+ manifest: plan.manifest,
30686
+ files: [...plan.files, plan.manifest]
30108
30687
  };
30109
30688
  }
30110
30689
  function architectureDocsRenderProjectionOperation(root, files) {
@@ -30120,7 +30699,7 @@ function architectureDocsRenderProjectionOperation(root, files) {
30120
30699
  }
30121
30700
  function currentBodyHash(root, path) {
30122
30701
  const absolute = resolve17(root, path);
30123
- return existsSync14(absolute) ? digestJson({ body: readFileSync13(absolute, "utf8") }) : "missing";
30702
+ return existsSync15(absolute) ? digestJson({ body: readFileSync13(absolute, "utf8") }) : "missing";
30124
30703
  }
30125
30704
  async function runPracticesCommand(args2, cwd, daemon) {
30126
30705
  const subcommand = args2[0] ?? "list";
@@ -30468,7 +31047,7 @@ function auditManifestGateRoot(cwd) {
30468
31047
  }
30469
31048
  function auditGithubIssuesEnabled(cwd) {
30470
31049
  const manifestPath = resolve17(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
30471
- if (!existsSync14(manifestPath))
31050
+ if (!existsSync15(manifestPath))
30472
31051
  return false;
30473
31052
  let raw;
30474
31053
  try {
@@ -31081,7 +31660,7 @@ async function writeGithubDeveloperReviewState(cwd, state) {
31081
31660
  return { state, path };
31082
31661
  }
31083
31662
  function readGithubDeveloperReviewState(path) {
31084
- if (!existsSync14(path))
31663
+ if (!existsSync15(path))
31085
31664
  return;
31086
31665
  try {
31087
31666
  const parsed = JSON.parse(readFileSync13(path, "utf8"));
@@ -31133,7 +31712,7 @@ function defaultGithubConnectionPath(cwd) {
31133
31712
  return join10(dirname10(defaultDaemonConnectionPath(cwd)), "github-connection.json");
31134
31713
  }
31135
31714
  function readGithubConnection(path) {
31136
- if (!existsSync14(path))
31715
+ if (!existsSync15(path))
31137
31716
  return;
31138
31717
  try {
31139
31718
  const parsed = JSON.parse(readFileSync13(path, "utf8"));
@@ -31647,10 +32226,10 @@ function doctorSqlite(cwd) {
31647
32226
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
31648
32227
  return {
31649
32228
  path,
31650
- exists: existsSync14(path),
32229
+ exists: existsSync15(path),
31651
32230
  migrations: productVersionManifest().runtime.sqliteMigrations,
31652
32231
  legacyPath: paths.legacyLocalStorePath,
31653
- legacyExists: existsSync14(paths.legacyLocalStorePath),
32232
+ legacyExists: existsSync15(paths.legacyLocalStorePath),
31654
32233
  legacyLocalStore
31655
32234
  };
31656
32235
  }
@@ -31681,7 +32260,7 @@ function runtimePathsReport(cwd) {
31681
32260
  };
31682
32261
  }
31683
32262
  function pathAccess(path) {
31684
- const exists = existsSync14(path);
32263
+ const exists = existsSync15(path);
31685
32264
  return {
31686
32265
  path,
31687
32266
  exists,
@@ -32164,6 +32743,16 @@ function renderResult(result, format) {
32164
32743
  return `OK ${result.requestId}
32165
32744
  ${JSON.stringify(result.data, null, 2)}`;
32166
32745
  }
32746
+ function resolveCommandExitCode(result) {
32747
+ if (!result || result.ok !== true)
32748
+ return 1;
32749
+ const data = result.data;
32750
+ if (data?.matched === true)
32751
+ return 0;
32752
+ if (data?.ambiguous === true)
32753
+ return 2;
32754
+ return 1;
32755
+ }
32167
32756
  function readFlag(args2, flag) {
32168
32757
  const index = args2.indexOf(flag);
32169
32758
  if (index === -1)
@@ -32193,5 +32782,6 @@ function readRepeatedFlag(args2, flag) {
32193
32782
  }
32194
32783
  export {
32195
32784
  runForegroundDaemon,
32196
- runCli
32785
+ runCli,
32786
+ resolveCommandExitCode
32197
32787
  };