archctx 0.8.1 → 0.9.1

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
@@ -53,6 +53,9 @@ function stableYaml(value, indent = 0) {
53
53
  return `[]
54
54
  `;
55
55
  return value.map((item) => {
56
+ if (Array.isArray(item))
57
+ return `${pad}- ${canonicalize(item)}
58
+ `;
56
59
  if (item !== null && typeof item === "object") {
57
60
  return `${pad}- ${stableYaml(item, indent + 2).trimStart()}`;
58
61
  }
@@ -1753,7 +1756,9 @@ var init_projection = __esm(() => {
1753
1756
  "projection-preview-v1",
1754
1757
  "projection-prior-committed-applies-v1",
1755
1758
  "projection-protocol-v2",
1759
+ "projection-publish-v1",
1756
1760
  "projection-snapshot-v1",
1761
+ "projection-verify-pr-v1",
1757
1762
  "recommendation-scan-decision-v1",
1758
1763
  "recommendation-v3",
1759
1764
  "refactor-assessment-v1",
@@ -3363,6 +3368,10 @@ var init_ports = __esm(() => {
3363
3368
  // packages/contracts/src/practices.ts
3364
3369
  var PRACTICE_SCHEMA_VERSION = "archcontext.practice/v1", PRACTICE_PROFILE_SCHEMA_VERSION = "archcontext.practice-profile/v1", PRACTICE_SOURCE_SCHEMA_VERSION = "archcontext.practice-source/v1", PRACTICE_CATALOG_MANIFEST_SCHEMA_VERSION = "archcontext.practice-catalog-manifest/v1", PRACTICE_ENFORCEMENT_POLICY_SCHEMA_VERSION = "archcontext.practice-enforcement-policy/v1", PRACTICE_WAIVER_SCHEMA_VERSION = "archcontext.practice-waiver/v1";
3365
3370
 
3371
+ // packages/contracts/src/projection-writer.ts
3372
+ var PROJECTION_PUBLISH_RUN_SCHEMA_VERSION = "archcontext.projection-publish-run/v1", PROJECTION_PUBLISH_STATUS_SCHEMA_VERSION = "archcontext.projection-publish-status/v1", PROJECTION_PR_VERIFICATION_SCHEMA_VERSION = "archcontext.projection-pr-verification/v1", PROJECTION_WRITER_BRANCH_PREFIX = "archctx/", PROJECTION_WRITER_DEFAULT_BRANCH = "archctx/projection";
3373
+ var init_projection_writer = () => {};
3374
+
3366
3375
  // packages/contracts/src/projection-manifest.ts
3367
3376
  function architectureDocsProjectionManifestIssues(value) {
3368
3377
  const issues = [];
@@ -3867,7 +3876,7 @@ function compareVersionTriples(left, right) {
3867
3876
  }
3868
3877
  return 0;
3869
3878
  }
3870
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.8.1", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v2", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
3879
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.1", ARCHCONTEXT_PACKAGE_MANAGER = "bun@1.4.3", ARCHCONTEXT_NODE_RANGE = ">=22.22 <26", LOCAL_RUNTIME_RPC_SCHEMA_VERSION = "archcontext.runtime-rpc/v2", ARCHCONTEXT_SCHEMA_SET_VERSION = "2026-07-11.explorer-view-v2";
3871
3880
 
3872
3881
  // packages/contracts/src/refactor.ts
3873
3882
  function refactorNamespaceIssues(value, label = "product.id") {
@@ -4969,6 +4978,7 @@ var init_src = __esm(() => {
4969
4978
  init_ports();
4970
4979
  init_projection();
4971
4980
  init_projection_recovery();
4981
+ init_projection_writer();
4972
4982
  init_projection_manifest();
4973
4983
  init_refactor();
4974
4984
  init_schema();
@@ -15377,7 +15387,7 @@ var init_src6 = __esm(() => {
15377
15387
 
15378
15388
  // packages/local-runtime/egress-admission/src/index.ts
15379
15389
  var LOCAL_EGRESS_MODE_ENV = "ARCHCONTEXT_EGRESS_MODE";
15380
- var LOCAL_EGRESS_CHANNELS = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "codegraph-telemetry"];
15390
+ var LOCAL_EGRESS_CHANNELS = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "projection-publishing", "codegraph-telemetry"];
15381
15391
 
15382
15392
  class LocalEgressPolicyError extends Error {
15383
15393
  }
@@ -21015,6 +21025,16 @@ ${wrapped}`;
21015
21025
  }
21016
21026
  return `${existing.slice(0, region.start)}${wrapped}${existing.slice(region.end)}`;
21017
21027
  }
21028
+ function agentContextTextOutsideRegions(body, nodeIds) {
21029
+ let text = body;
21030
+ for (const nodeId of [...new Set(nodeIds)].sort()) {
21031
+ const region = findAgentContextRegion(text, nodeId);
21032
+ if (!region)
21033
+ throw new Error(`agent-context-marker-missing: ${nodeId}`);
21034
+ text = `${text.slice(0, region.start)}\x00archcontext-agent-context-region:${nodeId}\x00${text.slice(region.end)}`;
21035
+ }
21036
+ return text;
21037
+ }
21018
21038
  function findAgentContextRegion(body, nodeId) {
21019
21039
  const startPattern = new RegExp(`<!-- BEGIN ARCHCONTEXT AGENT CONTEXT id="${escapeRegExp(nodeId)}"[^>]*-->`);
21020
21040
  const startMatch = startPattern.exec(body);
@@ -22998,7 +23018,7 @@ function isGitWorktreeError(error) {
22998
23018
 
22999
23019
  // packages/local-runtime/egress-admission/src/index.ts
23000
23020
  var LOCAL_EGRESS_MODE_ENV2 = "ARCHCONTEXT_EGRESS_MODE";
23001
- var LOCAL_EGRESS_CHANNELS2 = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "codegraph-telemetry"];
23021
+ var LOCAL_EGRESS_CHANNELS2 = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "projection-publishing", "codegraph-telemetry"];
23002
23022
 
23003
23023
  class LocalEgressPolicyError2 extends Error {
23004
23024
  }
@@ -27294,7 +27314,7 @@ function listModelFiles(root) {
27294
27314
  return {
27295
27315
  path,
27296
27316
  body,
27297
- schemaVersion: extractSchemaVersion(body),
27317
+ schemaVersion: extractSchemaVersion(body, path),
27298
27318
  digest: digestJson({ path, body })
27299
27319
  };
27300
27320
  });
@@ -27340,7 +27360,15 @@ function readdirOrFile(path) {
27340
27360
  return "file";
27341
27361
  }
27342
27362
  }
27343
- function extractSchemaVersion(body) {
27363
+ function extractSchemaVersion(body, path) {
27364
+ if (path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(path)) {
27365
+ try {
27366
+ const parsed = parseJsonOrStableYaml(body, path);
27367
+ return typeof parsed?.schemaVersion === "string" ? parsed.schemaVersion : "";
27368
+ } catch {
27369
+ return "";
27370
+ }
27371
+ }
27344
27372
  const match = body.match(/schemaVersion:\s*"?([^"\n]+)"?/);
27345
27373
  if (match)
27346
27374
  return match[1].trim();
@@ -33880,6 +33908,19 @@ async function runAgentContextProjectionCommand(input, cwd, daemon) {
33880
33908
  throw publicOperationFailure(error);
33881
33909
  }
33882
33910
  }
33911
+ function agentContextProjectionDifferingPaths(root, paths) {
33912
+ const wanted = new Set(paths);
33913
+ if (wanted.size === 0)
33914
+ return [];
33915
+ const projection = buildAgentContextProjection(root);
33916
+ const onDisk = new Map(projection.existingFiles.map((file) => [file.path, file.onDisk]));
33917
+ const written = projectionFilesBoundToRenderInput(projection.existingFiles, projection.plan.files);
33918
+ const rendered = new Set(written.map((file) => file.path));
33919
+ return [...new Set([
33920
+ ...written.filter((file) => wanted.has(file.path) && onDisk.get(file.path) !== file.body).map((file) => file.path),
33921
+ ...[...wanted].filter((path) => !rendered.has(path))
33922
+ ])].sort();
33923
+ }
33883
33924
  function buildAgentContextProjection(root) {
33884
33925
  const model = loadNativeModelFromArchContext(root);
33885
33926
  const sourceDigest = digestJson({ model });
@@ -34566,6 +34607,8 @@ function effectiveEgressChannels(env, live) {
34566
34607
  }
34567
34608
  if (live.updateCheckRequested)
34568
34609
  channels.push({ channel: "npm-update-check", destination: "configured npm registry", trigger: "explicit update --check or doctor --check-updates", data: "archctx package version lookup", status: "enabled" });
34610
+ if (live.projectionPublishRequested)
34611
+ channels.push({ channel: "projection-publishing", destination: "configured git remote, plus GitHub via gh or the operator's publisher command", trigger: "explicit archctx projection publish", data: "one commit of generated projection outputs and a pull request title/body listing their paths", status: "enabled" });
34569
34612
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
34570
34613
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
34571
34614
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -34600,9 +34643,9 @@ function localEgressStatus(env = process.env, live = {}) {
34600
34643
  }
34601
34644
 
34602
34645
  // packages/surfaces/cli/src/main.ts
34603
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
34604
- import { accessSync as accessSync3, closeSync as closeSync12, constants as constants3, existsSync as existsSync34, mkdirSync as mkdirSync13, openSync as openSync12, readFileSync as readFileSync31, rmSync as rmSync15, statSync as statSync11 } from "fs";
34605
- import { dirname as dirname16, join as join26, parse, resolve as resolve34 } from "path";
34646
+ import { spawn as spawn2, spawnSync as spawnSync6 } from "child_process";
34647
+ import { accessSync as accessSync3, closeSync as closeSync13, constants as constants3, existsSync as existsSync35, mkdirSync as mkdirSync14, openSync as openSync13, readFileSync as readFileSync32, rmSync as rmSync16, statSync as statSync11 } from "fs";
34648
+ import { dirname as dirname16, join as join27, parse, resolve as resolve35 } from "path";
34606
34649
  import { fileURLToPath as fileURLToPath2 } from "url";
34607
34650
 
34608
34651
  // packages/contracts/src/index.ts
@@ -34617,6 +34660,7 @@ init_ownership_dependencies();
34617
34660
  init_ports();
34618
34661
  init_projection();
34619
34662
  init_projection_recovery();
34663
+ init_projection_writer();
34620
34664
  init_projection_manifest();
34621
34665
  init_refactor();
34622
34666
  init_schema();
@@ -39615,6 +39659,7 @@ class AgentJobService {
39615
39659
  supersededByJobId: input.supersededByJobId,
39616
39660
  now: input.now ?? this.context.clock()
39617
39661
  });
39662
+ this.context.abortOwnedAudit(job.job.jobId);
39618
39663
  return okEnvelope("jobs.cancel", { job });
39619
39664
  }
39620
39665
  async runtimeAgentJobInScope(scope, jobId) {
@@ -54326,6 +54371,973 @@ function runtimeProjectionOwnedOutputDigest(projection) {
54326
54371
  files: projection.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
54327
54372
  });
54328
54373
  }
54374
+ // packages/local-runtime/runtime-daemon/src/projection-writer.ts
54375
+ import { execFile as execFile2, spawnSync as spawnSync5 } from "node:child_process";
54376
+ import { createHash as createHash13, randomUUID as randomUUID3 } from "node:crypto";
54377
+ import { closeSync as closeSync9, existsSync as existsSync29, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync5, openSync as openSync10, readFileSync as readFileSync27, readSync as readSync5, realpathSync as realpathSync12, renameSync as renameSync7, rmSync as rmSync15, writeFileSync as writeFileSync11 } from "node:fs";
54378
+ import { tmpdir as tmpdir4 } from "node:os";
54379
+ import { extname as extname2, join as join23, resolve as resolve30 } from "node:path";
54380
+ init_src();
54381
+ var PROJECTION_PUBLISH_REQUEST_ID = "projection.publish";
54382
+ var PROJECTION_PUBLISH_STATUS_REQUEST_ID = "projection.publish-status";
54383
+ var PROJECTION_VERIFY_PR_REQUEST_ID = "projection.verify-pr";
54384
+ var PROJECTION_WRITER_DEFAULT_BASE = "main";
54385
+ var PROJECTION_WRITER_DEFAULT_REMOTE = "origin";
54386
+ var PROJECTION_PR_BODY_MARKER = "<!-- archctx-projection-writer -->";
54387
+ var PROJECTION_WRITER_STATE_SCHEMA_VERSION = "archcontext.projection-writer-state/v1";
54388
+ var PROJECTION_WRITER_MAX_ITERATIONS = 3;
54389
+ var PROJECTION_WRITER_TARGETS = ["agent-context", "architecture-docs"];
54390
+ var PUBLISHER_TIMEOUT_MS = 120000;
54391
+ var PR_BODY_MAX_LISTED_PATHS = 200;
54392
+
54393
+ class ProjectionWriterFailure extends Error {
54394
+ code;
54395
+ reasonCode;
54396
+ constructor(code, reasonCode, message) {
54397
+ super(message);
54398
+ this.code = code;
54399
+ this.reasonCode = reasonCode;
54400
+ this.name = "ProjectionWriterFailure";
54401
+ }
54402
+ }
54403
+ function projectionWriterPaths(repositoryRoot, remote, base, env = process.env) {
54404
+ const key = createHash13("sha256").update(`${remote}\x00${base}`, "utf8").digest("hex").slice(0, 16);
54405
+ const dir = join23(runtimeStatePaths(repositoryRoot, env).repositoryStateDir, "projection-writer", key);
54406
+ return { dir, baseRef: `refs/archctx/projection-writer/${key}/base`, worktree: join23(dir, "worktree"), state: join23(dir, "state.json"), lock: join23(dir, "lock") };
54407
+ }
54408
+ async function runProjectionPublishCommand(options) {
54409
+ const env = options.env ?? process.env;
54410
+ const now = options.now ?? (() => new Date);
54411
+ const remote = options.remote ?? PROJECTION_WRITER_DEFAULT_REMOTE;
54412
+ const base = options.base ?? PROJECTION_WRITER_DEFAULT_BASE;
54413
+ const branch = options.branch ?? PROJECTION_WRITER_DEFAULT_BRANCH;
54414
+ let repositoryRoot;
54415
+ try {
54416
+ repositoryRoot = findRepositoryRoot(options.cwd);
54417
+ } catch {
54418
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_REPO_NOT_FOUND", `projection publish requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54419
+ }
54420
+ const invalid = projectionWriterTargetIssue(repositoryRoot, { remote, base, branch });
54421
+ if (invalid)
54422
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_SCHEMA_INVALID", invalid, "projection-writer-input-invalid");
54423
+ try {
54424
+ assertLocalEgressAllowed2("projection-publishing", env);
54425
+ } catch (error) {
54426
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_POLICY_VIOLATION", errorMessage2(error), "egress-policy-denied");
54427
+ }
54428
+ const startedAt = now().toISOString();
54429
+ const paths = projectionWriterPaths(repositoryRoot, remote, base, env);
54430
+ const blank = () => ({
54431
+ schemaVersion: PROJECTION_PUBLISH_RUN_SCHEMA_VERSION,
54432
+ result: "failed",
54433
+ remote,
54434
+ base,
54435
+ branch,
54436
+ baseSha: null,
54437
+ publishedCommit: null,
54438
+ pushed: false,
54439
+ pullRequest: null,
54440
+ publisher: options.publisher.kind,
54441
+ files: [],
54442
+ projectionStatus: null,
54443
+ humanActions: [],
54444
+ error: null,
54445
+ startedAt,
54446
+ finishedAt: startedAt,
54447
+ iterations: 0
54448
+ });
54449
+ let lock;
54450
+ try {
54451
+ mkdirSync12(paths.dir, { recursive: true, mode: 448 });
54452
+ lock = acquireProjectionWriterLock(paths.lock, startedAt);
54453
+ } catch (error) {
54454
+ const failure = asWriterFailure(error);
54455
+ return projectionPublishEnvelope({ ...blank(), finishedAt: now().toISOString(), error: writerError(failure) });
54456
+ }
54457
+ if (lock.kind === "busy") {
54458
+ return projectionPublishEnvelope({ ...blank(), result: "busy", holderPid: lock.pid, finishedAt: now().toISOString() });
54459
+ }
54460
+ try {
54461
+ let state;
54462
+ try {
54463
+ state = readProjectionWriterState(paths.state, remote, base);
54464
+ } catch (error) {
54465
+ return projectionPublishEnvelope({ ...blank(), finishedAt: now().toISOString(), error: writerError(asWriterFailure(error)) });
54466
+ }
54467
+ const outcome = await runLockedProjectionPublish({ options, env, now, repositoryRoot, paths, remote, base, branch, state, assertOwner: () => assertProjectionWriterOwner(paths.lock, lock.record), blank: blank() });
54468
+ const next = nextProjectionWriterState(state, outcome);
54469
+ try {
54470
+ writeProjectionWriterState(paths.state, next);
54471
+ } catch (error) {
54472
+ return projectionPublishEnvelope({
54473
+ ...outcome.run,
54474
+ result: "failed",
54475
+ error: writerError(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-unwritable", errorMessage2(error)))
54476
+ });
54477
+ }
54478
+ return projectionPublishEnvelope(outcome.run);
54479
+ } finally {
54480
+ lock.release();
54481
+ }
54482
+ }
54483
+ async function runLockedProjectionPublish(input) {
54484
+ const { options, env, now, repositoryRoot, paths, remote, base, branch, state } = input;
54485
+ const runSequence = state.runSequence + 1;
54486
+ const publisherIdentity = options.publisher.identity ?? options.publisher.kind;
54487
+ let iterations = 0;
54488
+ let baseSha = null;
54489
+ const finish = (result, fields = {}, extra = {}) => ({
54490
+ run: { ...input.blank, ...fields, result, baseSha, iterations, finishedAt: now().toISOString() },
54491
+ runSequence,
54492
+ publisherIdentity,
54493
+ ...extra
54494
+ });
54495
+ const failed = (failure, fields = {}, extra = {}) => finish("failed", { ...fields, error: writerError(failure) }, extra);
54496
+ try {
54497
+ const pushRemote = projectionWriterPushUrl(repositoryRoot, remote);
54498
+ const fetchUrls = git(repositoryRoot, ["remote", "get-url", "--all", "--", remote]).trim().split(`
54499
+ `).filter(Boolean);
54500
+ const tracksPublication = fetchUrls.length === 1 && fetchUrls[0] === pushRemote;
54501
+ while (iterations < PROJECTION_WRITER_MAX_ITERATIONS) {
54502
+ iterations += 1;
54503
+ remoteGit(repositoryRoot, [
54504
+ "fetch",
54505
+ "--no-tags",
54506
+ "--no-write-fetch-head",
54507
+ "--",
54508
+ pushRemote,
54509
+ `+refs/heads/${base}:${paths.baseRef}`,
54510
+ ...tracksPublication ? [`+refs/heads/${base}:refs/remotes/${remote}/${base}`] : []
54511
+ ], env);
54512
+ baseSha = git(repositoryRoot, ["rev-parse", "--verify", `${paths.baseRef}^{commit}`]).trim();
54513
+ const successMatches = state.lastSuccessfulBranch === branch && state.lastSuccessfulSha === baseSha;
54514
+ const pending = state.publisherPending?.branch === branch && state.publisherPending.baseSha === baseSha ? state.publisherPending : successMatches && state.lastSuccessfulCommit && state.lastSuccessfulPublisher !== publisherIdentity ? { branch, baseSha, commitSha: state.lastSuccessfulCommit, files: state.lastSuccessfulFiles } : null;
54515
+ if (!pending && successMatches && state.lastSuccessfulPublisher === publisherIdentity && (state.lastSuccessfulCommit === null || lsRemoteBranch(repositoryRoot, pushRemote, branch, env) === state.lastSuccessfulCommit)) {
54516
+ return finish("already-processed", { publishedCommit: state.lastSuccessfulCommit });
54517
+ }
54518
+ input.assertOwner();
54519
+ prepareProjectionWriterWorktree(repositoryRoot, paths.worktree, baseSha);
54520
+ if (pending && lsRemoteBranch(repositoryRoot, pushRemote, branch, env) === pending.commitSha) {
54521
+ input.assertOwner();
54522
+ const published = await publishPullRequest(options.publisher, { worktree: paths.worktree, remote, base, branch, baseSha, commitSha: pending.commitSha, files: pending.files });
54523
+ if (!published.ok) {
54524
+ return failed(published.failure, { publishedCommit: pending.commitSha, files: pending.files }, { publisherPending: pending });
54525
+ }
54526
+ return finish("published", { publishedCommit: pending.commitSha, files: pending.files, pullRequest: published.pullRequest });
54527
+ }
54528
+ const ownedPatterns = projectionOwnedPaths(loadNativeModelFromArchContext(paths.worktree));
54529
+ const snapshot = runProjectionSnapshotCommand(paths.worktree);
54530
+ if (!snapshot.ok)
54531
+ throw envelopeFailure(snapshot, "projection-writer-snapshot-failed");
54532
+ const expected = snapshot.data.expected;
54533
+ const request = {
54534
+ schemaVersion: PROJECTION_REQUEST_SCHEMA_VERSION,
54535
+ requestId: `projection_publish.${baseSha.slice(0, 12)}.${runSequence}-${iterations}`,
54536
+ profile: "repo-harness/v1",
54537
+ mode: "apply",
54538
+ targets: [...PROJECTION_WRITER_TARGETS],
54539
+ changedPaths: [],
54540
+ expected,
54541
+ ...options.acceptObservedMajorChange ? { acceptObservedMajorChange: true } : {}
54542
+ };
54543
+ const applied = await options.apply(paths.worktree, request);
54544
+ input.assertOwner();
54545
+ if (!applied.ok) {
54546
+ const failure = envelopeFailure(applied, "projection-apply-failed");
54547
+ if (failure.code === "AC_CODE_FACTS_UNAVAILABLE") {
54548
+ throw new ProjectionWriterFailure(failure.code, failure.reasonCode, `${failure.message}; the writer never initializes CodeGraph itself: run \`codegraph init ${paths.worktree}\` once, then publish again`);
54549
+ }
54550
+ throw failure;
54551
+ }
54552
+ const result = applied.data;
54553
+ const projectionStatus = result.status;
54554
+ if (projectionStatus === "human-action-required" || projectionStatus === "adoption-required") {
54555
+ return finish("human-action-required", { projectionStatus, humanActions: publishHumanActions(result) });
54556
+ }
54557
+ if (!["noop", "applied", "applied-partial"].includes(projectionStatus)) {
54558
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-apply-status-unsupported", `projection apply ended with status ${projectionStatus}; the writer publishes only noop, applied and applied-partial results`);
54559
+ }
54560
+ const changed = worktreeChangedPaths(paths.worktree);
54561
+ if (changed.length === 0) {
54562
+ const tip = lsRemoteBranch(repositoryRoot, pushRemote, base, env);
54563
+ if (tip === null) {
54564
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-missing", `${remote} has no branch ${base}`), { projectionStatus });
54565
+ }
54566
+ if (tip !== baseSha)
54567
+ continue;
54568
+ return finish("up-to-date", { projectionStatus });
54569
+ }
54570
+ const written = new Set(result.files.filter((file) => file.action !== "unchanged").map((file) => file.path));
54571
+ const offending = changed.filter((path) => !written.has(path) || !isProjectionOwnedPath(path, ownedPatterns));
54572
+ if (offending.length > 0) {
54573
+ return failed(new ProjectionWriterFailure("AC_POLICY_VIOLATION", "projection-writer-non-owned-change", `projection apply changed paths the writer may not publish: ${offending.join(", ")}`), { projectionStatus });
54574
+ }
54575
+ input.assertOwner();
54576
+ git(paths.worktree, ["add", "-A", "--", ...changed]);
54577
+ const title = projectionPublishTitle(base, baseSha);
54578
+ try {
54579
+ git(paths.worktree, ["commit", "--no-verify", "-q", "-m", title, "-m", `Archctx-Projection-Source: ${baseSha}`]);
54580
+ } catch (error) {
54581
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-commit-failed", `git commit failed in the writer worktree; configure a git identity (user.name, user.email) for the repository or set GIT_AUTHOR_*/GIT_COMMITTER_*: ${errorMessage2(error)}`), { projectionStatus, files: changed });
54582
+ }
54583
+ const commitSha = git(paths.worktree, ["rev-parse", "HEAD"]).trim();
54584
+ const tip = lsRemoteBranch(repositoryRoot, pushRemote, base, env);
54585
+ if (tip === null) {
54586
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-missing", `${remote} has no branch ${base}`), { projectionStatus, files: changed });
54587
+ }
54588
+ if (tip !== baseSha)
54589
+ continue;
54590
+ const lease = lsRemoteBranch(repositoryRoot, pushRemote, branch, env);
54591
+ let publishedCommit = commitSha;
54592
+ let pushed = false;
54593
+ if (lease !== null && carriesSameProjection(paths.worktree, lease, commitSha, baseSha)) {
54594
+ publishedCommit = lease;
54595
+ } else {
54596
+ input.assertOwner();
54597
+ remoteGit(paths.worktree, ["push", "--no-verify", `--force-with-lease=refs/heads/${branch}:${lease ?? ""}`, "--", pushRemote, `${commitSha}:refs/heads/${branch}`], env);
54598
+ pushed = true;
54599
+ }
54600
+ input.assertOwner();
54601
+ const published = await publishPullRequest(options.publisher, { worktree: paths.worktree, remote, base, branch, baseSha, commitSha: publishedCommit, files: changed });
54602
+ if (!published.ok) {
54603
+ const pendingPublish = { baseSha, branch, commitSha: publishedCommit, files: changed };
54604
+ return failed(published.failure, { projectionStatus, files: changed, publishedCommit, pushed }, { publisherPending: pendingPublish });
54605
+ }
54606
+ return finish("published", { projectionStatus, files: changed, publishedCommit, pushed, pullRequest: published.pullRequest });
54607
+ }
54608
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-unstable", `${remote}/${base} moved during each of ${PROJECTION_WRITER_MAX_ITERATIONS} attempts; nothing was pushed`));
54609
+ } catch (error) {
54610
+ return failed(asWriterFailure(error));
54611
+ }
54612
+ }
54613
+ function nextProjectionWriterState(state, outcome) {
54614
+ const run = outcome.run;
54615
+ const next = { ...state, branch: run.branch, runSequence: outcome.runSequence, lastRun: run };
54616
+ if (run.result === "already-processed")
54617
+ return next;
54618
+ if (run.baseSha !== null)
54619
+ next.lastProcessedSha = run.baseSha;
54620
+ if (run.result === "published" || run.result === "up-to-date") {
54621
+ next.lastSuccessfulSha = run.baseSha;
54622
+ next.lastSuccessfulBranch = run.branch;
54623
+ next.lastSuccessfulPublisher = outcome.publisherIdentity;
54624
+ next.lastSuccessfulFiles = run.files;
54625
+ next.lastSuccessfulCommit = run.result === "published" ? run.publishedCommit : null;
54626
+ next.publisherPending = null;
54627
+ } else if (outcome.publisherPending) {
54628
+ next.publisherPending = outcome.publisherPending;
54629
+ } else if (run.result === "human-action-required") {
54630
+ next.publisherPending = null;
54631
+ }
54632
+ return next;
54633
+ }
54634
+ function projectionPublishEnvelope(run) {
54635
+ if (run.result !== "failed")
54636
+ return okEnvelope(PROJECTION_PUBLISH_REQUEST_ID, run);
54637
+ const error = run.error ?? { code: "AC_UNCLASSIFIED_FAILURE", message: "projection publish failed" };
54638
+ return {
54639
+ ...errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error.code, error.message, error.reasonCode),
54640
+ data: run
54641
+ };
54642
+ }
54643
+ function publishHumanActions(result) {
54644
+ return result.humanActions.map((action) => ({ reasonCode: action.reasonCode, ...action.path ? { path: action.path } : {} }));
54645
+ }
54646
+ function projectionPublishTitle(base, baseSha) {
54647
+ return `chore(projection): refresh generated architecture outputs for ${base}@${baseSha.slice(0, 12)}`;
54648
+ }
54649
+ function projectionPublishBody(input) {
54650
+ const listed = input.files.slice(0, PR_BODY_MAX_LISTED_PATHS).map((path) => `- \`${path}\``);
54651
+ if (input.files.length > PR_BODY_MAX_LISTED_PATHS)
54652
+ listed.push(`- … and ${input.files.length - PR_BODY_MAX_LISTED_PATHS} more`);
54653
+ return [
54654
+ PROJECTION_PR_BODY_MARKER,
54655
+ "",
54656
+ `Base: \`${input.base}\` at \`${input.baseSha}\``,
54657
+ `Commit: \`${input.commitSha}\``,
54658
+ "",
54659
+ "Changed generated paths:",
54660
+ "",
54661
+ ...listed,
54662
+ "",
54663
+ `Generated by \`archctx projection publish\`; touches generated paths only. Verify with \`archctx projection verify-pr --base ${input.base}\`.`,
54664
+ ""
54665
+ ].join(`
54666
+ `);
54667
+ }
54668
+ async function publishPullRequest(publisher, input) {
54669
+ if (publisher.kind === "none")
54670
+ return { ok: true, pullRequest: null };
54671
+ const dir = mkdtempSync5(join23(tmpdir4(), "archctx-projection-pr-body-"));
54672
+ try {
54673
+ const bodyFile = join23(dir, "body.md");
54674
+ writeFileSync11(bodyFile, projectionPublishBody(input), { mode: 384 });
54675
+ const pullRequest = await publisher.ensurePullRequest({
54676
+ worktree: input.worktree,
54677
+ remote: input.remote,
54678
+ base: input.base,
54679
+ branch: input.branch,
54680
+ baseSha: input.baseSha,
54681
+ commitSha: input.commitSha,
54682
+ title: projectionPublishTitle(input.base, input.baseSha),
54683
+ bodyFile
54684
+ });
54685
+ return { ok: true, pullRequest: { ...pullRequest.url ? { url: pullRequest.url } : {}, ...pullRequest.number !== undefined ? { number: pullRequest.number } : {} } };
54686
+ } catch (error) {
54687
+ if (error instanceof LocalEgressPolicyError2)
54688
+ return { ok: false, failure: new ProjectionWriterFailure("AC_POLICY_VIOLATION", "egress-policy-denied", error.message) };
54689
+ return { ok: false, failure: new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-publisher-failed", `${publisher.kind} publisher failed: ${errorMessage2(error)}`) };
54690
+ } finally {
54691
+ rmSync15(dir, { recursive: true, force: true });
54692
+ }
54693
+ }
54694
+ function runProjectionPublishStatusCommand(options) {
54695
+ const env = options.env ?? process.env;
54696
+ const remote = options.remote ?? PROJECTION_WRITER_DEFAULT_REMOTE;
54697
+ const base = options.base ?? PROJECTION_WRITER_DEFAULT_BASE;
54698
+ let repositoryRoot;
54699
+ try {
54700
+ repositoryRoot = findRepositoryRoot(options.cwd);
54701
+ } catch {
54702
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, "AC_REPO_NOT_FOUND", `projection publish-status requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54703
+ }
54704
+ const invalid = refNameIssue(repositoryRoot, "--base", base) ?? remoteNameIssue(remote);
54705
+ if (invalid)
54706
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, "AC_SCHEMA_INVALID", invalid, "projection-writer-input-invalid");
54707
+ try {
54708
+ const paths = projectionWriterPaths(repositoryRoot, remote, base, env);
54709
+ const state = readProjectionWriterState(paths.state, remote, base);
54710
+ const holder = readProjectionWriterLock(paths.lock);
54711
+ const status = {
54712
+ schemaVersion: PROJECTION_PUBLISH_STATUS_SCHEMA_VERSION,
54713
+ remote,
54714
+ base,
54715
+ branch: state.branch,
54716
+ worktreePath: paths.worktree,
54717
+ running: holder && isProcessAlive(holder.pid) ? holder : null,
54718
+ lastProcessedSha: state.lastProcessedSha,
54719
+ lastSuccessfulSha: state.lastSuccessfulSha,
54720
+ lastRun: state.lastRun
54721
+ };
54722
+ return okEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, status);
54723
+ } catch (error) {
54724
+ if (error instanceof ProjectionWriterFailure)
54725
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, error.code, error.message, error.reasonCode);
54726
+ return publicOperationErrorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, error);
54727
+ }
54728
+ }
54729
+ async function runProjectionPublishFollow(options) {
54730
+ let runs = 0;
54731
+ while (!options.shouldStop()) {
54732
+ let envelope;
54733
+ try {
54734
+ envelope = await options.runOnce();
54735
+ } catch (error) {
54736
+ envelope = publicOperationErrorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error);
54737
+ }
54738
+ runs += 1;
54739
+ options.onResult(envelope);
54740
+ if (options.shouldStop())
54741
+ break;
54742
+ await options.sleep(options.intervalMs);
54743
+ }
54744
+ return runs;
54745
+ }
54746
+ function runProjectionVerifyPrCommand(options) {
54747
+ const requestId = PROJECTION_VERIFY_PR_REQUEST_ID;
54748
+ let root;
54749
+ try {
54750
+ root = findRepositoryRoot(options.cwd);
54751
+ } catch {
54752
+ return errorEnvelope(requestId, "AC_REPO_NOT_FOUND", `projection verify-pr requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54753
+ }
54754
+ if (!isGitRevisionArgument(options.base))
54755
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", `projection verify-pr --base is not a usable revision: ${JSON.stringify(options.base)}`, "projection-verify-pr-input-invalid");
54756
+ try {
54757
+ if (!isTrackedWorktreeClean(root)) {
54758
+ return errorEnvelope(requestId, "AC_PRECONDITION_FAILED", "projection verify-pr requires a clean tracked worktree; HEAD is the pull request head", "projection-verify-pr-worktree-dirty");
54759
+ }
54760
+ let baseSha;
54761
+ let mergeBase;
54762
+ const headSha = git(root, ["rev-parse", "--verify", "HEAD^{commit}"]).trim();
54763
+ try {
54764
+ baseSha = git(root, ["rev-parse", "--verify", `${options.base}^{commit}`]).trim();
54765
+ mergeBase = git(root, ["merge-base", baseSha, headSha]).trim();
54766
+ } catch (error) {
54767
+ return errorEnvelope(requestId, "AC_PRECONDITION_FAILED", `projection verify-pr cannot resolve ${options.base} against HEAD: ${errorMessage2(error)}`, "projection-verify-pr-base-unresolved");
54768
+ }
54769
+ const changedPaths = [...new Set(git(root, ["diff", "--name-only", "-z", "--no-renames", `${mergeBase}..${headSha}`, "--"]).split("\x00").filter(Boolean))].sort();
54770
+ const verification = (fields) => okEnvelope(requestId, {
54771
+ schemaVersion: PROJECTION_PR_VERIFICATION_SCHEMA_VERSION,
54772
+ base: options.base,
54773
+ baseSha,
54774
+ headSha,
54775
+ mergeBase,
54776
+ changedPaths,
54777
+ nonGeneratedPaths: [],
54778
+ humanEditedPaths: [],
54779
+ generatedOnly: false,
54780
+ renderEquivalent: null,
54781
+ differingPaths: [],
54782
+ projectionStatus: null,
54783
+ ...fields
54784
+ });
54785
+ if (changedPaths.length === 0)
54786
+ return verification({ verdict: "no-changes" });
54787
+ const classified = classifyPullRequestPaths(root, mergeBase, headSha, changedPaths);
54788
+ if (classified.nonGeneratedPaths.length > 0) {
54789
+ return verification({ verdict: "touches-non-generated", nonGeneratedPaths: classified.nonGeneratedPaths, humanEditedPaths: classified.humanEditedPaths });
54790
+ }
54791
+ const snapshot = runProjectionSnapshotCommand(root);
54792
+ if (!snapshot.ok)
54793
+ return { ...snapshot, requestId };
54794
+ const request = {
54795
+ schemaVersion: PROJECTION_REQUEST_SCHEMA_VERSION,
54796
+ requestId: `projection_verify_pr.${headSha.slice(0, 12)}`,
54797
+ profile: "repo-harness/v1",
54798
+ mode: "check",
54799
+ targets: [...PROJECTION_WRITER_TARGETS],
54800
+ changedPaths: [],
54801
+ expected: snapshot.data.expected
54802
+ };
54803
+ const checked = (options.check ?? runProjectionCheckInvocation)(request, root);
54804
+ if (!checked.ok)
54805
+ return { ...checked, requestId };
54806
+ const result = checked.data;
54807
+ let agentContextDiffering;
54808
+ try {
54809
+ agentContextDiffering = agentContextProjectionDifferingPaths(root, classified.agentContextPaths);
54810
+ } catch {
54811
+ agentContextDiffering = [...classified.agentContextPaths];
54812
+ }
54813
+ const differingPaths = [...new Set([
54814
+ ...result.files.filter((file) => file.action !== "unchanged").map((file) => file.path),
54815
+ ...agentContextDiffering
54816
+ ])].sort();
54817
+ const renderEquivalent = result.status === "noop" && agentContextDiffering.length === 0;
54818
+ return verification({
54819
+ verdict: renderEquivalent ? "generated-only-render-equivalent" : "generated-only-not-render-equivalent",
54820
+ generatedOnly: true,
54821
+ renderEquivalent,
54822
+ differingPaths,
54823
+ projectionStatus: result.status
54824
+ });
54825
+ } catch (error) {
54826
+ return publicOperationErrorEnvelope(requestId, error);
54827
+ }
54828
+ }
54829
+ function classifyPullRequestPaths(root, mergeBase, headSha, changedPaths) {
54830
+ const baseTargets = projectionTargetsAtRevision(root, mergeBase);
54831
+ const headTargets = projectionTargetsAtRevision(root, headSha);
54832
+ const nonGenerated = [];
54833
+ const humanEdited = [];
54834
+ const agentContext = [];
54835
+ for (const path of changedPaths) {
54836
+ if (path === ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)
54837
+ continue;
54838
+ const atBase = baseTargets.get(path);
54839
+ const atHead = headTargets.get(path);
54840
+ if (!atBase && !atHead) {
54841
+ nonGenerated.push(path);
54842
+ continue;
54843
+ }
54844
+ const before = readFileAtRevision(root, mergeBase, path);
54845
+ const after = readFileAtRevision(root, headSha, path);
54846
+ if (!atHead && after !== null) {
54847
+ nonGenerated.push(path);
54848
+ continue;
54849
+ }
54850
+ const target = {
54851
+ whole: (atBase?.whole ?? false) || (atHead?.whole ?? false),
54852
+ mixedDocument: (atBase?.mixedDocument ?? false) || (atHead?.mixedDocument ?? false),
54853
+ agentNodeIds: [...new Set([...atBase?.agentNodeIds ?? [], ...atHead?.agentNodeIds ?? []])].sort()
54854
+ };
54855
+ if (!target.mixedDocument && target.agentNodeIds.length === 0)
54856
+ continue;
54857
+ if (!mixedTargetTextUnchanged(before, after, target)) {
54858
+ humanEdited.push(path);
54859
+ nonGenerated.push(path);
54860
+ } else if (target.agentNodeIds.length > 0) {
54861
+ agentContext.push(path);
54862
+ }
54863
+ }
54864
+ return { nonGeneratedPaths: nonGenerated.sort(), humanEditedPaths: humanEdited.sort(), agentContextPaths: agentContext.sort() };
54865
+ }
54866
+ function projectionTargetsAtRevision(root, revision) {
54867
+ const targets = new Map;
54868
+ const add = (path, ownership) => {
54869
+ const known = targets.get(path) ?? { whole: false, mixedDocument: false, agentNodeIds: [] };
54870
+ targets.set(path, {
54871
+ whole: known.whole || ownership.whole === true,
54872
+ mixedDocument: known.mixedDocument || ownership.mixedDocument === true,
54873
+ agentNodeIds: [...known.agentNodeIds, ...ownership.agentNodeIds ?? []]
54874
+ });
54875
+ };
54876
+ try {
54877
+ const manifest = readFileAtRevision(root, revision, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH);
54878
+ const rows = manifest === null ? {} : JSON.parse(manifest).targets;
54879
+ if (rows && typeof rows === "object" && !Array.isArray(rows)) {
54880
+ for (const row of Object.values(rows)) {
54881
+ if (!row || typeof row.path !== "string")
54882
+ continue;
54883
+ add(row.path, row.ownership === "generated" ? { whole: true } : { mixedDocument: true });
54884
+ }
54885
+ }
54886
+ } catch {}
54887
+ try {
54888
+ const listed = git(root, ["ls-tree", "-r", "-z", "--name-only", revision, "--", ".archcontext/model"]).split("\x00").filter(Boolean);
54889
+ const files = new Map(listed.map((path) => [path, readFileAtRevision(root, revision, path) ?? ""]));
54890
+ for (const target of agentContextProjectionTargetPaths(loadNativeModelFromModelFiles(files)))
54891
+ add(target.path, { agentNodeIds: [target.nodeId] });
54892
+ } catch {}
54893
+ return targets;
54894
+ }
54895
+ function mixedTargetTextUnchanged(before, after, target) {
54896
+ if (before === null || after === null)
54897
+ return false;
54898
+ try {
54899
+ return JSON.stringify(textOutsideGeneratedRegions(before, target)) === JSON.stringify(textOutsideGeneratedRegions(after, target));
54900
+ } catch {
54901
+ return false;
54902
+ }
54903
+ }
54904
+ function textOutsideGeneratedRegions(body, target) {
54905
+ const text = target.agentNodeIds.length > 0 ? agentContextTextOutsideRegions(body, target.agentNodeIds) : body;
54906
+ if (!target.mixedDocument)
54907
+ return [text];
54908
+ const scan = scanDocumentRegions(text);
54909
+ if (scan.diagnostics.length > 0 || scan.generatedRegions.some((region) => !region.closed))
54910
+ throw new Error("projection-generated-regions-unbalanced");
54911
+ return scan.humanRegions.map((region) => region.text);
54912
+ }
54913
+ function readFileAtRevision(root, revision, path) {
54914
+ if (!gitSucceeds2(root, ["cat-file", "-e", `${revision}:${path}`]))
54915
+ return null;
54916
+ return git(root, ["cat-file", "blob", `${revision}:${path}`]);
54917
+ }
54918
+ function createNoneProjectionPublisher() {
54919
+ return { kind: "none", ensurePullRequest: async () => ({}) };
54920
+ }
54921
+ function createGhProjectionPublisher(options = {}) {
54922
+ const exec = options.exec ?? nodeProjectionPublisherExec;
54923
+ const admissionEnv = options.env ?? process.env;
54924
+ const timeoutMs = options.timeoutMs ?? PUBLISHER_TIMEOUT_MS;
54925
+ return {
54926
+ kind: "gh",
54927
+ async ensurePullRequest(input) {
54928
+ assertLocalEgressAllowed2("projection-publishing", admissionEnv);
54929
+ const repository = projectionPublisherRepository(input.worktree, input.remote);
54930
+ const env = { ...stringEnv(process.env), GH_PROMPT_DISABLED: "1" };
54931
+ delete env.GH_REPO;
54932
+ const gh = (args) => withLocalEgress2("projection-publishing", () => exec("gh", [...args, "--repo", repository], { cwd: input.worktree, env, timeoutMs }), admissionEnv);
54933
+ const listed = await gh(["pr", "list", "--head", input.branch, "--base", input.base, "--state", "open", "--json", "number,url,isCrossRepository,headRefName,baseRefName", "--limit", "100"]);
54934
+ const open = parseGhPullRequestList(listed.stdout, input.branch, input.base);
54935
+ if (open) {
54936
+ await gh(["pr", "edit", String(open.number), "--title", input.title, "--body-file", input.bodyFile]);
54937
+ return { number: open.number, ...open.url ? { url: open.url } : {} };
54938
+ }
54939
+ const created = await gh(["pr", "create", "--base", input.base, "--head", input.branch, "--title", input.title, "--body-file", input.bodyFile]);
54940
+ const url = created.stdout.trim().split(`
54941
+ `).map((line) => line.trim()).filter(Boolean).pop() ?? "";
54942
+ const number = /\/pull\/(\d+)(?:[/?#]|$)/.exec(url)?.[1];
54943
+ return { ...url ? { url } : {}, ...number ? { number: Number(number) } : {} };
54944
+ }
54945
+ };
54946
+ }
54947
+ function projectionWriterPushUrl(worktree, remote) {
54948
+ const urls = git(worktree, ["remote", "get-url", "--push", "--all", "--", remote]).trim().split(`
54949
+ `).filter(Boolean);
54950
+ if (urls.length !== 1) {
54951
+ throw new ProjectionWriterFailure("AC_SCHEMA_INVALID", "projection-writer-push-target-ambiguous", "projection publish requires exactly one push URL for the selected remote");
54952
+ }
54953
+ return urls[0];
54954
+ }
54955
+ function projectionPublisherRepository(worktree, remote) {
54956
+ const source = projectionWriterPushUrl(worktree, remote);
54957
+ const scp = /^[^/@:]+@([A-Za-z0-9.-]+):([^?#]+)$/.exec(source);
54958
+ let host;
54959
+ let path;
54960
+ if (scp) {
54961
+ host = scp[1];
54962
+ path = scp[2];
54963
+ } else {
54964
+ const url = new URL(source);
54965
+ if (!["https:", "ssh:"].includes(url.protocol) || url.search || url.hash || url.password || url.protocol === "https:" && url.username) {
54966
+ throw new Error("the selected remote must be an SSH or HTTPS GitHub repository URL");
54967
+ }
54968
+ host = url.protocol === "ssh:" ? url.hostname : url.host;
54969
+ path = url.pathname.replace(/^\//, "");
54970
+ }
54971
+ path = path.replace(/\.git$/, "");
54972
+ if (!/^[A-Za-z0-9.-]+(?::[0-9]+)?$/.test(host) || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(path) || path.split("/").some((part) => part === "." || part === "..")) {
54973
+ throw new Error("the selected remote must identify one GitHub host, owner, and repository");
54974
+ }
54975
+ return `${host.toLowerCase()}/${path}`;
54976
+ }
54977
+ function createCommandProjectionPublisher(options) {
54978
+ const exec = options.exec ?? nodeProjectionPublisherExec;
54979
+ const admissionEnv = options.env ?? process.env;
54980
+ const sourceEnv = options.sourceEnv ?? process.env;
54981
+ const platform = options.platform ?? process.platform;
54982
+ const timeoutMs = options.timeoutMs ?? PUBLISHER_TIMEOUT_MS;
54983
+ return {
54984
+ kind: "command",
54985
+ identity: `command:${resolve30(options.command)}`,
54986
+ async ensurePullRequest(input) {
54987
+ const env = {
54988
+ ...commandPublisherBaseEnv(sourceEnv, platform),
54989
+ ARCHCTX_PUBLISH_REMOTE: input.remote,
54990
+ ARCHCTX_PUBLISH_BASE: input.base,
54991
+ ARCHCTX_PUBLISH_BRANCH: input.branch,
54992
+ ARCHCTX_PUBLISH_BASE_SHA: input.baseSha,
54993
+ ARCHCTX_PUBLISH_COMMIT: input.commitSha,
54994
+ ARCHCTX_PUBLISH_TITLE: input.title,
54995
+ ARCHCTX_PUBLISH_BODY_FILE: input.bodyFile
54996
+ };
54997
+ const { stdout } = await withLocalEgress2("projection-publishing", async () => {
54998
+ const invocation = commandPublisherInvocation(options.command, platform);
54999
+ try {
55000
+ return await exec(invocation.file, invocation.args, { cwd: input.worktree, env, timeoutMs });
55001
+ } catch (error) {
55002
+ if (invocation.interpreter !== undefined && error.code === "ENOENT") {
55003
+ throw new Error(`publisher command ${options.command} needs the interpreter "${invocation.interpreter}" from its #! line, which was not found on PATH`);
55004
+ }
55005
+ throw error;
55006
+ }
55007
+ }, admissionEnv);
55008
+ return parsePublisherCommandOutput(stdout);
55009
+ }
55010
+ };
55011
+ }
55012
+ var COMMAND_PUBLISHER_POSIX_ENV = ["PATH", "HOME"];
55013
+ var COMMAND_PUBLISHER_WINDOWS_ENV = ["PATH", "HOME", "SystemRoot", "windir", "ComSpec", "PATHEXT", "TEMP", "TMP", "USERPROFILE"];
55014
+ function commandPublisherBaseEnv(source, platform) {
55015
+ const env = {};
55016
+ if (platform !== "win32") {
55017
+ for (const name of COMMAND_PUBLISHER_POSIX_ENV) {
55018
+ const value = source[name];
55019
+ if (value)
55020
+ env[name] = value;
55021
+ }
55022
+ return env;
55023
+ }
55024
+ const allowed = new Set(COMMAND_PUBLISHER_WINDOWS_ENV.map((name) => name.toUpperCase()));
55025
+ const forwarded = new Set;
55026
+ for (const [name, value] of Object.entries(source)) {
55027
+ const key = name.toUpperCase();
55028
+ if (!value || !allowed.has(key) || forwarded.has(key))
55029
+ continue;
55030
+ forwarded.add(key);
55031
+ env[name] = value;
55032
+ }
55033
+ return env;
55034
+ }
55035
+ function commandPublisherInvocation(command, platform) {
55036
+ if (platform !== "win32")
55037
+ return { file: command, args: [] };
55038
+ const extension = extname2(command).toLowerCase();
55039
+ if (extension === ".cmd" || extension === ".bat") {
55040
+ throw new Error(`publisher command ${command} is a ${extension} script, which only runs through cmd.exe; the command publisher runs without a shell, so use an executable or a #! script`);
55041
+ }
55042
+ const firstLine = readCommandFirstLine(command);
55043
+ if (!firstLine.startsWith("#!"))
55044
+ return { file: command, args: [] };
55045
+ const tokens = firstLine.slice(2).trim().split(/\s+/).filter((token) => token !== "");
55046
+ if (tokens.length === 0)
55047
+ throw new Error(`publisher command ${command} has an empty #! line`);
55048
+ let interpreter = commandBasename(tokens[0]);
55049
+ let interpreterArgs = tokens.slice(1);
55050
+ if (interpreter === "env") {
55051
+ const next = interpreterArgs[0];
55052
+ if (next === undefined || next.startsWith("-")) {
55053
+ throw new Error(`publisher command ${command} has an unsupported #! line "${firstLine}": /usr/bin/env must be followed by an interpreter name`);
55054
+ }
55055
+ interpreter = commandBasename(next);
55056
+ interpreterArgs = interpreterArgs.slice(1);
55057
+ }
55058
+ return { file: interpreter, args: [...interpreterArgs, command], interpreter };
55059
+ }
55060
+ function commandBasename(token) {
55061
+ const name = token.split(/[\\/]/).pop() ?? token;
55062
+ return name.toLowerCase().endsWith(".exe") ? name.slice(0, -4) : name;
55063
+ }
55064
+ function readCommandFirstLine(command) {
55065
+ let fd;
55066
+ try {
55067
+ fd = openSync10(command, "r");
55068
+ } catch (error) {
55069
+ throw new Error(`publisher command ${command} cannot be read: ${error instanceof Error ? error.message : String(error)}`);
55070
+ }
55071
+ try {
55072
+ const buffer = Buffer.alloc(1024);
55073
+ const length = readSync5(fd, buffer, 0, buffer.length, 0);
55074
+ const head = buffer.subarray(0, length).toString("utf8");
55075
+ const newline = head.indexOf(`
55076
+ `);
55077
+ return (newline === -1 ? head : head.slice(0, newline)).replace(/\r$/, "");
55078
+ } finally {
55079
+ closeSync9(fd);
55080
+ }
55081
+ }
55082
+ function parseGhPullRequestList(stdout, branch, base) {
55083
+ const parsed = JSON.parse(stdout);
55084
+ if (!Array.isArray(parsed))
55085
+ throw new Error("gh pr list returned an unexpected shape");
55086
+ if (parsed.length >= 100)
55087
+ throw new Error("gh pr list reached the 100-result limit; resolve rolling PR ambiguity manually");
55088
+ const matches = [];
55089
+ for (const row of parsed) {
55090
+ if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.isCrossRepository !== "boolean" || row.headRefName !== branch || row.baseRefName !== base || typeof row.number !== "number" || !Number.isInteger(row.number) || row.number <= 0) {
55091
+ throw new Error("gh pr list returned a pull request without verified identity");
55092
+ }
55093
+ if (!row.isCrossRepository) {
55094
+ matches.push({ number: row.number, ...typeof row.url === "string" && row.url !== "" ? { url: row.url } : {} });
55095
+ }
55096
+ }
55097
+ if (matches.length > 1)
55098
+ throw new Error("gh pr list returned multiple same-repository rolling pull requests");
55099
+ return matches[0];
55100
+ }
55101
+ function parsePublisherCommandOutput(stdout) {
55102
+ let parsed;
55103
+ try {
55104
+ parsed = JSON.parse(stdout.trim());
55105
+ } catch {
55106
+ return {};
55107
+ }
55108
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
55109
+ return {};
55110
+ const value = parsed;
55111
+ return {
55112
+ ...typeof value.url === "string" && value.url !== "" ? { url: value.url } : {},
55113
+ ...typeof value.number === "number" && Number.isInteger(value.number) && value.number > 0 ? { number: value.number } : {}
55114
+ };
55115
+ }
55116
+ var nodeProjectionPublisherExec = (file, args, options) => new Promise((resolvePromise, rejectPromise) => {
55117
+ execFile2(file, args, { cwd: options.cwd, env: options.env, timeout: options.timeoutMs, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => {
55118
+ if (error) {
55119
+ rejectPromise(Object.assign(new Error(`${[file, ...args.slice(0, 2)].join(" ")} failed: ${(stderr ?? "").trim() || error.message}`), { code: error.code }));
55120
+ return;
55121
+ }
55122
+ resolvePromise({ stdout });
55123
+ });
55124
+ });
55125
+ function stringEnv(env) {
55126
+ return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
55127
+ }
55128
+ function projectionWriterTargetIssue(repositoryRoot, input) {
55129
+ const issue = refNameIssue(repositoryRoot, "--base", input.base) ?? refNameIssue(repositoryRoot, "--branch", input.branch) ?? remoteNameIssue(input.remote);
55130
+ if (issue)
55131
+ return issue;
55132
+ if (!input.branch.startsWith(PROJECTION_WRITER_BRANCH_PREFIX))
55133
+ return `projection publish --branch must start with ${PROJECTION_WRITER_BRANCH_PREFIX}; the writer never pushes to any other ref`;
55134
+ if (input.branch === input.base)
55135
+ return "projection publish --branch must differ from --base";
55136
+ if (!gitSucceeds2(repositoryRoot, ["remote", "get-url", "--", input.remote]))
55137
+ return `projection publish --remote names no configured remote: ${input.remote}`;
55138
+ return;
55139
+ }
55140
+ function refNameIssue(repositoryRoot, flag, name) {
55141
+ if (name === "" || name.startsWith("-") || name.includes("@{") || !gitSucceeds2(repositoryRoot, ["check-ref-format", "--branch", name])) {
55142
+ return `${flag} is not a valid branch name: ${JSON.stringify(name)}`;
55143
+ }
55144
+ return;
55145
+ }
55146
+ function remoteNameIssue(remote) {
55147
+ if (remote === "" || remote.startsWith("-") || !/^[A-Za-z0-9._-]+$/.test(remote))
55148
+ return `--remote is not a valid remote name: ${JSON.stringify(remote)}`;
55149
+ return;
55150
+ }
55151
+ function acquireProjectionWriterLock(lockPath, startedAt) {
55152
+ let ownership;
55153
+ try {
55154
+ ownership = acquireLocalStoreWriterOwnership(`${lockPath}.coordination`);
55155
+ } catch (error) {
55156
+ if (error instanceof FailureError && error.reasonCode === "runtime-writer-owned") {
55157
+ const holder = readProjectionWriterLock(lockPath);
55158
+ if (holder && isProcessAlive(holder.pid))
55159
+ return { kind: "busy", pid: holder.pid };
55160
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-unavailable", "another writer holds the OS lock; its diagnostic owner record is unavailable");
55161
+ }
55162
+ throw error;
55163
+ }
55164
+ try {
55165
+ const body = readPrivateControlFile(lockPath);
55166
+ if (body === undefined && existsSync29(lockPath)) {
55167
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-unverified", "the writer owner record has unverified permissions");
55168
+ }
55169
+ const holder = body === undefined ? undefined : parseProjectionWriterLock(body);
55170
+ if (holder && isProcessAlive(holder.pid)) {
55171
+ ownership.release();
55172
+ return { kind: "busy", pid: holder.pid };
55173
+ }
55174
+ rmSync15(lockPath, { force: true });
55175
+ const record = JSON.stringify({ pid: process.pid, startedAt, token: randomUUID3() }, null, 2);
55176
+ createPrivateControlFile(lockPath, record);
55177
+ return { kind: "acquired", record, release() {
55178
+ try {
55179
+ if (readPrivateControlFile(lockPath) === record)
55180
+ rmSync15(lockPath, { force: true });
55181
+ } finally {
55182
+ ownership.release();
55183
+ }
55184
+ } };
55185
+ } catch (error) {
55186
+ ownership.release();
55187
+ throw error;
55188
+ }
55189
+ }
55190
+ function assertProjectionWriterOwner(lockPath, record) {
55191
+ if (readPrivateControlFile(lockPath) !== record) {
55192
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-lost", "the writer owner record changed; publication stopped");
55193
+ }
55194
+ }
55195
+ function readProjectionWriterLock(lockPath) {
55196
+ const body = readPrivateControlFile(lockPath);
55197
+ return body === undefined ? undefined : parseProjectionWriterLock(body);
55198
+ }
55199
+ function parseProjectionWriterLock(body) {
55200
+ try {
55201
+ const value = JSON.parse(body);
55202
+ if (typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0)
55203
+ return;
55204
+ return { pid: value.pid, startedAt: typeof value.startedAt === "string" ? value.startedAt : "" };
55205
+ } catch {
55206
+ return;
55207
+ }
55208
+ }
55209
+ function emptyProjectionWriterState(remote, base) {
55210
+ return {
55211
+ schemaVersion: PROJECTION_WRITER_STATE_SCHEMA_VERSION,
55212
+ remote,
55213
+ base,
55214
+ branch: null,
55215
+ runSequence: 0,
55216
+ lastProcessedSha: null,
55217
+ lastSuccessfulSha: null,
55218
+ lastSuccessfulCommit: null,
55219
+ lastSuccessfulBranch: null,
55220
+ lastSuccessfulPublisher: null,
55221
+ lastSuccessfulFiles: [],
55222
+ publisherPending: null,
55223
+ lastRun: null
55224
+ };
55225
+ }
55226
+ function readProjectionWriterState(path, remote, base) {
55227
+ if (!existsSync29(path))
55228
+ return emptyProjectionWriterState(remote, base);
55229
+ let value;
55230
+ try {
55231
+ value = JSON.parse(readFileSync27(path, "utf8"));
55232
+ } catch (error) {
55233
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-invalid", `projection writer state is unreadable at ${path}: ${errorMessage2(error)}`);
55234
+ }
55235
+ if (value?.schemaVersion !== PROJECTION_WRITER_STATE_SCHEMA_VERSION || value.remote !== remote || value.base !== base || typeof value.runSequence !== "number") {
55236
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-invalid", `projection writer state at ${path} does not belong to ${remote}/${base}`);
55237
+ }
55238
+ return { ...emptyProjectionWriterState(remote, base), ...value };
55239
+ }
55240
+ function writeProjectionWriterState(path, state) {
55241
+ const temporary = `${path}.${process.pid}.tmp`;
55242
+ writeFileSync11(temporary, `${JSON.stringify(state, null, 2)}
55243
+ `, { encoding: "utf8", mode: 384 });
55244
+ renameSync7(temporary, path);
55245
+ }
55246
+ function prepareProjectionWriterWorktree(repositoryRoot, worktree, sha) {
55247
+ git(repositoryRoot, ["worktree", "prune"]);
55248
+ if (!isRegisteredWorktree(repositoryRoot, worktree)) {
55249
+ rmSync15(worktree, { recursive: true, force: true });
55250
+ git(repositoryRoot, ["worktree", "add", "--detach", worktree, sha]);
55251
+ }
55252
+ git(worktree, ["checkout", "-q", "--force", "--detach", sha]);
55253
+ git(worktree, ["reset", "-q", "--hard", sha]);
55254
+ git(worktree, ["clean", "-ffdq"]);
55255
+ }
55256
+ function isRegisteredWorktree(repositoryRoot, worktree) {
55257
+ if (!existsSync29(join23(worktree, ".git")))
55258
+ return false;
55259
+ const wanted = canonical(worktree);
55260
+ return git(repositoryRoot, ["worktree", "list", "--porcelain", "-z"]).split("\x00").filter((line) => line.startsWith("worktree ")).some((line) => canonical(line.slice("worktree ".length)) === wanted);
55261
+ }
55262
+ function canonical(path) {
55263
+ try {
55264
+ return realpathSync12.native(path);
55265
+ } catch {
55266
+ return resolve30(path);
55267
+ }
55268
+ }
55269
+ function worktreeChangedPaths(worktree) {
55270
+ const tokens = git(worktree, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]).split("\x00");
55271
+ const paths = new Set;
55272
+ for (let index = 0;index < tokens.length; index += 1) {
55273
+ const entry = tokens[index];
55274
+ if (entry.length < 4)
55275
+ continue;
55276
+ paths.add(entry.slice(3));
55277
+ if (entry[0] === "R" || entry[0] === "C") {
55278
+ const source = tokens[index + 1];
55279
+ if (source)
55280
+ paths.add(source);
55281
+ index += 1;
55282
+ }
55283
+ }
55284
+ return [...paths].sort();
55285
+ }
55286
+ function isProjectionOwnedPath(path, patterns) {
55287
+ return patterns.some((pattern) => pattern.endsWith("/**") ? path.startsWith(pattern.slice(0, -2)) : path === pattern);
55288
+ }
55289
+ function carriesSameProjection(worktree, remoteTip, commitSha, baseSha) {
55290
+ if (!gitSucceeds2(worktree, ["cat-file", "-e", `${remoteTip}^{commit}`]))
55291
+ return false;
55292
+ const parents = git(worktree, ["rev-list", "--parents", "-n", "1", remoteTip]).trim().split(/\s+/).slice(1);
55293
+ if (parents.length !== 1 || parents[0] !== baseSha)
55294
+ return false;
55295
+ return git(worktree, ["rev-parse", `${remoteTip}^{tree}`]).trim() === git(worktree, ["rev-parse", `${commitSha}^{tree}`]).trim();
55296
+ }
55297
+ function lsRemoteBranch(repositoryRoot, remote, branch, env) {
55298
+ const line = remoteGit(repositoryRoot, ["ls-remote", "--", remote, `refs/heads/${branch}`], env).split(`
55299
+ `).find((entry) => entry.endsWith(` refs/heads/${branch}`));
55300
+ return line ? line.split("\t")[0] : null;
55301
+ }
55302
+ function remoteGit(cwd, args, env) {
55303
+ return withLocalEgress2("projection-publishing", () => git(cwd, args, { GIT_TERMINAL_PROMPT: "0" }), env);
55304
+ }
55305
+ function git(cwd, args, extraEnv = {}) {
55306
+ const result = spawnSync5("git", args, {
55307
+ cwd,
55308
+ encoding: "utf8",
55309
+ env: { ...process.env, ...extraEnv },
55310
+ stdio: ["ignore", "pipe", "pipe"],
55311
+ maxBuffer: 256 * 1024 * 1024
55312
+ });
55313
+ if (result.error)
55314
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-git-failed", `git ${args[0]} could not run: ${result.error.message}`);
55315
+ if (result.status !== 0) {
55316
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-git-failed", `git ${args[0]} failed: ${(result.stderr ?? "").trim() || `exit ${result.status}`}`);
55317
+ }
55318
+ return result.stdout ?? "";
55319
+ }
55320
+ function gitSucceeds2(cwd, args) {
55321
+ return spawnSync5("git", args, { cwd, stdio: ["ignore", "ignore", "ignore"] }).status === 0;
55322
+ }
55323
+ function envelopeFailure(envelope, fallbackReason) {
55324
+ const error = envelope.error;
55325
+ return new ProjectionWriterFailure(error?.code ?? "AC_UNCLASSIFIED_FAILURE", error?.reasonCode ?? fallbackReason, error?.message ?? fallbackReason);
55326
+ }
55327
+ function asWriterFailure(error) {
55328
+ if (error instanceof ProjectionWriterFailure)
55329
+ return error;
55330
+ if (error instanceof LocalEgressPolicyError2)
55331
+ return new ProjectionWriterFailure("AC_POLICY_VIOLATION", "egress-policy-denied", error.message);
55332
+ const envelope = publicOperationErrorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error);
55333
+ return envelopeFailure(envelope, "projection-writer-failed");
55334
+ }
55335
+ function writerError(failure) {
55336
+ return { code: failure.code, reasonCode: failure.reasonCode, message: failure.message };
55337
+ }
55338
+ function errorMessage2(error) {
55339
+ return error instanceof Error ? error.message : String(error);
55340
+ }
54329
55341
  // packages/local-runtime/runtime-daemon/src/capability-dependencies.ts
54330
55342
  init_src();
54331
55343
  var CODEGRAPH_BINARY2 = "codegraph";
@@ -54449,8 +55461,8 @@ function runFlowSelectorCheckCommand(cwd, options = {}) {
54449
55461
  }
54450
55462
  }
54451
55463
  // packages/local-runtime/runtime-daemon/src/recommendations.ts
54452
- import { existsSync as existsSync29 } from "node:fs";
54453
- import { join as join23 } from "node:path";
55464
+ import { existsSync as existsSync30 } from "node:fs";
55465
+ import { join as join24 } from "node:path";
54454
55466
 
54455
55467
  // packages/local-runtime/runtime-daemon/src/refactor-verify.ts
54456
55468
  init_src();
@@ -55052,7 +56064,7 @@ class RecommendationsService {
55052
56064
  });
55053
56065
  }
55054
56066
  readGitOnlyDecision(repositoryRoot, recommendationId, surface) {
55055
- if (!existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
56067
+ if (!existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
55056
56068
  return;
55057
56069
  let decisions;
55058
56070
  try {
@@ -55722,7 +56734,7 @@ function lifecycleEnvelope(input) {
55722
56734
  });
55723
56735
  }
55724
56736
  function refactorDecisionsInPlay(repositoryRoot, records) {
55725
- return records.some(isRefactorRecommendation) || existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
56737
+ return records.some(isRefactorRecommendation) || existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
55726
56738
  }
55727
56739
  function ledgerAuditDecisionRefused(surface, record, recommendations, decisions) {
55728
56740
  let head = record.recommendationId;
@@ -55843,15 +56855,15 @@ function recommendationActorKind(input) {
55843
56855
  // packages/local-runtime/runtime-daemon/src/changeset-authority.ts
55844
56856
  init_src();
55845
56857
  init_src();
55846
- import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
55847
- import { resolve as resolve31 } from "node:path";
56858
+ import { existsSync as existsSync31, readFileSync as readFileSync29 } from "node:fs";
56859
+ import { resolve as resolve32 } from "node:path";
55848
56860
  init_src();
55849
56861
 
55850
56862
  // packages/local-runtime/runtime-daemon/src/committed-change-acceptance.ts
55851
56863
  init_src();
55852
56864
  import { execFileSync as execFileSync10 } from "node:child_process";
55853
- import { closeSync as closeSync9, constants as fsConstants3, fstatSync as fstatSync4, lstatSync as lstatSync15, openSync as openSync10, readdirSync as readdirSync15, readFileSync as readFileSync27 } from "node:fs";
55854
- import { resolve as resolve30 } from "node:path";
56865
+ import { closeSync as closeSync10, constants as fsConstants3, fstatSync as fstatSync4, lstatSync as lstatSync15, openSync as openSync11, readdirSync as readdirSync15, readFileSync as readFileSync28 } from "node:fs";
56866
+ import { resolve as resolve31 } from "node:path";
55855
56867
  init_src();
55856
56868
  var SEMANTIC_MODEL_DIRECTORIES = ["nodes", "relations", "flows"];
55857
56869
  var SEMANTIC_MODEL_PATH = /^\.archcontext\/model\/(nodes|relations|flows)\/[^/]+\.ya?ml$/;
@@ -55916,7 +56928,7 @@ function readSemanticModelSnapshot(root, readEntry) {
55916
56928
  }
55917
56929
  for (const entry of entries.filter((name) => /\.ya?ml$/.test(name)).sort()) {
55918
56930
  const path = `${relativeDirectory}/${entry}`;
55919
- bodies.set(path, strictUtf8(readEntry(resolve30(absolute, entry), path), path));
56931
+ bodies.set(path, strictUtf8(readEntry(resolve31(absolute, entry), path), path));
55920
56932
  }
55921
56933
  }
55922
56934
  const hashes = new Map([...bodies].map(([path, body]) => [path, digestJson({ body })]));
@@ -55925,13 +56937,13 @@ function readSemanticModelSnapshot(root, readEntry) {
55925
56937
  function readRegularFileBytes(absolute, path) {
55926
56938
  if (!lstatSync15(absolute).isFile())
55927
56939
  throw new FailureError("AC_PRECONDITION_FAILED", "semantic-model-entry-not-regular");
55928
- const fd = openSync10(absolute, fsConstants3.O_RDONLY | (fsConstants3.O_NOFOLLOW ?? 0) | (fsConstants3.O_NONBLOCK ?? 0));
56940
+ const fd = openSync11(absolute, fsConstants3.O_RDONLY | (fsConstants3.O_NOFOLLOW ?? 0) | (fsConstants3.O_NONBLOCK ?? 0));
55929
56941
  try {
55930
56942
  if (!fstatSync4(fd).isFile())
55931
56943
  throw new FailureError("AC_PRECONDITION_FAILED", "semantic-model-entry-not-regular");
55932
- return readFileSync27(fd);
56944
+ return readFileSync28(fd);
55933
56945
  } finally {
55934
- closeSync9(fd);
56946
+ closeSync10(fd);
55935
56947
  }
55936
56948
  }
55937
56949
  function strictUtf8(bytes, path) {
@@ -56429,10 +57441,10 @@ class ChangeSetAuthorityService {
56429
57441
  return publicFailureEnvelope("practices.waive", "AC_SCHEMA_INVALID", "waiver-input-invalid");
56430
57442
  }
56431
57443
  const path = `.archcontext/waivers/${waiverId}.json`;
56432
- const absolute = resolve31(session.workspace.root, path);
57444
+ const absolute = resolve32(session.workspace.root, path);
56433
57445
  const body = `${JSON.stringify(waiver, null, 2)}
56434
57446
  `;
56435
- const expectedHash = existsSync30(absolute) ? digestJson({ body: readFileSync28(absolute, "utf8") }) : "missing";
57447
+ const expectedHash = existsSync31(absolute) ? digestJson({ body: readFileSync29(absolute, "utf8") }) : "missing";
56436
57448
  const draft = this.changeSetEngine.plan({
56437
57449
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
56438
57450
  base: {
@@ -56868,7 +57880,7 @@ function architectureLedgerWriteAppendsEvents(mode) {
56868
57880
  // packages/local-runtime/runtime-daemon/src/rpc-server.ts
56869
57881
  init_src();
56870
57882
  import { createServer as createServer2 } from "node:http";
56871
- import { closeSync as closeSync10, existsSync as existsSync31, mkdirSync as mkdirSync12 } from "node:fs";
57883
+ import { closeSync as closeSync11, existsSync as existsSync32, mkdirSync as mkdirSync13 } from "node:fs";
56872
57884
  import { dirname as dirname14 } from "node:path";
56873
57885
  import { randomBytes as randomBytes5 } from "node:crypto";
56874
57886
  var DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 30 * 60000;
@@ -56926,7 +57938,7 @@ class ArchctxRuntimeRpcServer {
56926
57938
  if (connection)
56927
57939
  await removePrivateControlFile(connection.connectionPath);
56928
57940
  if (this.lockFd !== undefined) {
56929
- closeSync10(this.lockFd);
57941
+ closeSync11(this.lockFd);
56930
57942
  this.lockFd = undefined;
56931
57943
  await removePrivateControlFile(this.options.lockPath ?? defaultDaemonLockPath(this.options.root ?? process.cwd()));
56932
57944
  }
@@ -56939,8 +57951,8 @@ class ArchctxRuntimeRpcServer {
56939
57951
  const root = this.options.root ?? process.cwd();
56940
57952
  const connectionPath = this.options.connectionPath ?? defaultDaemonConnectionPath(root);
56941
57953
  const lockPath = this.options.lockPath ?? defaultDaemonLockPath(root);
56942
- mkdirSync12(dirname14(connectionPath), { recursive: true });
56943
- mkdirSync12(dirname14(lockPath), { recursive: true });
57954
+ mkdirSync13(dirname14(connectionPath), { recursive: true });
57955
+ mkdirSync13(dirname14(lockPath), { recursive: true });
56944
57956
  this.lockFd = acquireDaemonLock(lockPath, root);
56945
57957
  const token = this.options.token ?? randomBytes5(18).toString("base64url");
56946
57958
  const server = createServer2((request, response) => {
@@ -56980,23 +57992,31 @@ class ArchctxRuntimeRpcServer {
56980
57992
  this.server = undefined;
56981
57993
  const connection = this.connection;
56982
57994
  this.connection = undefined;
57995
+ const errors = [];
57996
+ const cleanup = async (action) => {
57997
+ try {
57998
+ await action();
57999
+ } catch (error) {
58000
+ errors.push(error);
58001
+ }
58002
+ };
56983
58003
  if (server) {
56984
- await new Promise((resolveClose, rejectClose) => {
58004
+ await cleanup(() => new Promise((resolveClose, rejectClose) => {
56985
58005
  server.close((error) => error ? rejectClose(error) : resolveClose());
56986
- });
56987
- }
56988
- await this.daemon.stop();
56989
- try {
56990
- if (connection)
56991
- await removePrivateControlFile(connection.connectionPath);
56992
- } finally {
56993
- if (this.lockFd !== undefined)
56994
- closeSync10(this.lockFd);
56995
- this.lockFd = undefined;
58006
+ }));
56996
58007
  }
58008
+ await cleanup(() => this.daemon.stop());
58009
+ if (connection)
58010
+ await cleanup(() => removePrivateControlFile(connection.connectionPath));
58011
+ const lockFd = this.lockFd;
58012
+ this.lockFd = undefined;
58013
+ if (lockFd !== undefined)
58014
+ await cleanup(() => closeSync11(lockFd));
56997
58015
  if (connection)
56998
- await removePrivateControlFile(connection.lockPath);
56999
- this.options.onStop?.();
58016
+ await cleanup(() => removePrivateControlFile(connection.lockPath));
58017
+ await cleanup(() => this.options.onStop?.());
58018
+ if (errors.length > 0)
58019
+ throw errors[0];
57000
58020
  }
57001
58021
  armIdleTimer() {
57002
58022
  if (this.idleTimeoutMs <= 0 || !this.server)
@@ -57033,7 +58053,7 @@ class ArchctxRuntimeRpcServer {
57033
58053
  });
57034
58054
  if (this.server && this.connection && (this.inFlightRpcRequests > 0 || this.authorizedRequests !== requestsBeforeRemoval)) {
57035
58055
  try {
57036
- if (!existsSync31(connection.connectionPath))
58056
+ if (!existsSync32(connection.connectionPath))
57037
58057
  createPrivateControlFile(connection.connectionPath, JSON.stringify(connection, null, 2));
57038
58058
  this.idleExitPending = false;
57039
58059
  this.armIdleTimer();
@@ -57246,8 +58266,8 @@ async function readRpcRequestBody(request, limits) {
57246
58266
  }
57247
58267
 
57248
58268
  // packages/local-runtime/runtime-daemon/src/index.ts
57249
- import { existsSync as existsSync32, readFileSync as readFileSync29 } from "node:fs";
57250
- import { resolve as resolve32 } from "node:path";
58269
+ import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
58270
+ import { resolve as resolve33 } from "node:path";
57251
58271
  init_src();
57252
58272
 
57253
58273
  // packages/local-runtime/runtime-daemon/src/egress.ts
@@ -57283,6 +58303,8 @@ function effectiveEgressChannels2(env, live) {
57283
58303
  }
57284
58304
  if (live.updateCheckRequested)
57285
58305
  channels.push({ channel: "npm-update-check", destination: "configured npm registry", trigger: "explicit update --check or doctor --check-updates", data: "archctx package version lookup", status: "enabled" });
58306
+ if (live.projectionPublishRequested)
58307
+ channels.push({ channel: "projection-publishing", destination: "configured git remote, plus GitHub via gh or the operator's publisher command", trigger: "explicit archctx projection publish", data: "one commit of generated projection outputs and a pull request title/body listing their paths", status: "enabled" });
57286
58308
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
57287
58309
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
57288
58310
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -57437,7 +58459,7 @@ class ArchctxDaemon {
57437
58459
  const model = loadNativeModelFromArchContext(root);
57438
58460
  const canonical = renderAgentContextProjection({ model, sourceDigest: digestJson({ model }), existingFiles: loadAgentContextProjectionFiles(root, model) });
57439
58461
  const canonicalBody = canonical.files.find((candidate) => candidate.path === file.path)?.body;
57440
- if (canonicalBody === undefined || projectionOwnedWriteBody(canonicalBody, readFileSync29(resolve32(root, file.path), "utf8")) !== file.body) {
58462
+ if (canonicalBody === undefined || projectionOwnedWriteBody(canonicalBody, readFileSync30(resolve33(root, file.path), "utf8")) !== file.body) {
57441
58463
  throw new Error(`Root contract requires canonical marker-only content: ${file.path}`);
57442
58464
  }
57443
58465
  }
@@ -57510,6 +58532,7 @@ class ArchctxDaemon {
57510
58532
  localStore: this.localStore
57511
58533
  });
57512
58534
  this.agentJobs = new AgentJobService({
58535
+ abortOwnedAudit: (jobId) => this.auditRunAbortControllers.get(jobId)?.abort(),
57513
58536
  assertRunning: () => this.assertRunning(),
57514
58537
  openSession: (root) => this.openSession(root),
57515
58538
  architectureLedgerScope: (root) => this.architectureLedgerScope(root),
@@ -57696,7 +58719,7 @@ class ArchctxDaemon {
57696
58719
  if (this.auditRunAbortControllers.size > 0)
57697
58720
  return true;
57698
58721
  for (const [repositoryId, session] of this.sessions) {
57699
- if (!existsSync32(session.workspace.root)) {
58722
+ if (!existsSync33(session.workspace.root)) {
57700
58723
  this.sessions.delete(repositoryId);
57701
58724
  continue;
57702
58725
  }
@@ -57900,7 +58923,7 @@ class ArchctxDaemon {
57900
58923
  try {
57901
58924
  projectionDrift = completeTaskProjectionDrift(session.workspace.root);
57902
58925
  projectionFreshness = completeTaskProjectionFreshness(session.workspace.root);
57903
- if (model.valid || !existsSync32(resolve32(session.workspace.root, ".archcontext/manifest.yaml"))) {
58926
+ if (model.valid || !existsSync33(resolve33(session.workspace.root, ".archcontext/manifest.yaml"))) {
57904
58927
  architectureDebt = readCompleteTaskArchitectureDebt(session.workspace.root, practiceEnforcement);
57905
58928
  }
57906
58929
  } catch (error) {
@@ -58389,7 +59412,7 @@ class ArchctxDaemon {
58389
59412
  }
58390
59413
  async restoreRepositorySessions() {
58391
59414
  for (const record of await this.localStore.listRepositorySessions()) {
58392
- if (!record.root || !existsSync32(record.root))
59415
+ if (!record.root || !existsSync33(record.root))
58393
59416
  continue;
58394
59417
  if (repositoryFingerprint(record.root) !== record.repositoryId)
58395
59418
  continue;
@@ -58687,7 +59710,7 @@ function blockedProductionInjections(deps) {
58687
59710
  ].filter((key) => (key in deps));
58688
59711
  }
58689
59712
  function completeTaskProjectionDrift(root) {
58690
- if (!existsSync32(resolve32(root, "docs/architecture/.projection-manifest.json")))
59713
+ if (!existsSync33(resolve33(root, "docs/architecture/.projection-manifest.json")))
58691
59714
  return;
58692
59715
  const profile = loadArchitectureDocumentationProfile(root);
58693
59716
  const loaded = loadArchitectureDocumentationInputs(root, profile);
@@ -58735,9 +59758,9 @@ function completeTaskProjectionFreshness(root) {
58735
59758
  // packages/local-runtime/codegraph-adapter/src/index.ts
58736
59759
  init_src();
58737
59760
  import { execFileSync as execFileSync11 } from "node:child_process";
58738
- import { accessSync as accessSync2, closeSync as closeSync11, constants as fsConstants4, existsSync as existsSync33, openSync as openSync11, readFileSync as readFileSync30, readSync as readSync5, realpathSync as realpathSync12, statSync as statSync10 } from "node:fs";
59761
+ import { accessSync as accessSync2, closeSync as closeSync12, constants as fsConstants4, existsSync as existsSync34, openSync as openSync12, readFileSync as readFileSync31, readSync as readSync6, realpathSync as realpathSync13, statSync as statSync10 } from "node:fs";
58739
59762
  import { createRequire as createRequire5 } from "node:module";
58740
- import { basename as basename7, delimiter as delimiter2, dirname as dirname15, isAbsolute as isAbsolute10, join as join25, posix as posix4, resolve as resolve33 } from "node:path";
59763
+ import { basename as basename7, delimiter as delimiter2, dirname as dirname15, isAbsolute as isAbsolute10, join as join26, posix as posix4, resolve as resolve34 } from "node:path";
58741
59764
  init_src();
58742
59765
  var REQUIRED_CODEGRAPH_PACKAGE2 = "@colbymchenry/codegraph";
58743
59766
  var REQUIRED_CODEGRAPH_VERSION2 = productVersionManifest().runtime.codeGraph.requiredVersion;
@@ -58774,15 +59797,15 @@ function codeGraphCliInvocation2(binary, cwd, pathValue = process.env.PATH ?? ""
58774
59797
  }
58775
59798
  function resolveExecutable2(binary, cwd, pathValue) {
58776
59799
  if (binary.includes("/") || binary.includes("\\")) {
58777
- const path = isAbsolute10(binary) ? binary : join25(cwd, binary);
58778
- return existsSync33(path) ? realpathSync12.native(path) : undefined;
59800
+ const path = isAbsolute10(binary) ? binary : join26(cwd, binary);
59801
+ return existsSync34(path) ? realpathSync13.native(path) : undefined;
58779
59802
  }
58780
59803
  for (const dir of pathValue.split(delimiter2)) {
58781
59804
  if (!dir)
58782
59805
  continue;
58783
- const path = join25(dir, binary);
59806
+ const path = join26(dir, binary);
58784
59807
  if (isExecutablePath2(path))
58785
- return realpathSync12.native(path);
59808
+ return realpathSync13.native(path);
58786
59809
  }
58787
59810
  return;
58788
59811
  }
@@ -58800,12 +59823,12 @@ function isExecutablePath2(path) {
58800
59823
  function resolvePackagedCodeGraphShim2() {
58801
59824
  try {
58802
59825
  const manifestPath = requireFromAdapter2.resolve(`${REQUIRED_CODEGRAPH_PACKAGE2}/package.json`);
58803
- const manifest = JSON.parse(readFileSync30(manifestPath, "utf8"));
59826
+ const manifest = JSON.parse(readFileSync31(manifestPath, "utf8"));
58804
59827
  const relativeBin = manifest.bin?.codegraph;
58805
59828
  if (manifest.version !== REQUIRED_CODEGRAPH_VERSION2 || !relativeBin)
58806
59829
  return;
58807
- const shimPath = resolve33(dirname15(manifestPath), relativeBin);
58808
- return existsSync33(shimPath) ? realpathSync12.native(shimPath) : undefined;
59830
+ const shimPath = resolve34(dirname15(manifestPath), relativeBin);
59831
+ return existsSync34(shimPath) ? realpathSync13.native(shimPath) : undefined;
58809
59832
  } catch {
58810
59833
  return;
58811
59834
  }
@@ -58819,15 +59842,15 @@ function isNodeRuntimeScript2(path) {
58819
59842
  function readFilePrefix2(path, length) {
58820
59843
  let fd;
58821
59844
  try {
58822
- fd = openSync11(path, "r");
59845
+ fd = openSync12(path, "r");
58823
59846
  const buffer = Buffer.alloc(length);
58824
- const bytes = readSync5(fd, buffer, 0, length, 0);
59847
+ const bytes = readSync6(fd, buffer, 0, length, 0);
58825
59848
  return buffer.subarray(0, bytes).toString("utf8");
58826
59849
  } catch {
58827
59850
  return "";
58828
59851
  } finally {
58829
59852
  if (fd !== undefined)
58830
- closeSync11(fd);
59853
+ closeSync12(fd);
58831
59854
  }
58832
59855
  }
58833
59856
  var CODEGRAPH_IMPORT_NODE_OUTPUT_BYTES2 = 4 * 1024;
@@ -58865,7 +59888,7 @@ function sameRealPath2(left, right) {
58865
59888
  if (typeof left !== "string")
58866
59889
  return false;
58867
59890
  try {
58868
- return realpathSync12.native(left) === realpathSync12.native(right);
59891
+ return realpathSync13.native(left) === realpathSync13.native(right);
58869
59892
  } catch {
58870
59893
  return false;
58871
59894
  }
@@ -58955,7 +59978,7 @@ function inspectRepositoryCodeFacts2(root, options = {}) {
58955
59978
  };
58956
59979
  }
58957
59980
  function codeGraphIndexAvailable2(workspaceRoot) {
58958
- return existsSync33(join25(workspaceRoot, ".codegraph"));
59981
+ return existsSync34(join26(workspaceRoot, ".codegraph"));
58959
59982
  }
58960
59983
  var NON_DECLARATION_KINDS2 = new Set(["file", "import", "export", "parameter"]);
58961
59984
  var RUNTIME_BUILTIN_CALL_NAMES2 = new Set([
@@ -59880,6 +60903,12 @@ var AUDIT_RUN_VALUE_FLAGS = ["--task-session-id", "--reason", "--risk", "--uncer
59880
60903
  var AUDIT_RUN_BOOLEAN_FLAGS = ["--no-wait", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
59881
60904
  var AUDIT_APPROVE_VALUE_FLAGS = ["--run-id", "--confirm-public-repo", ...AUDIT_GLOBAL_VALUE_FLAGS];
59882
60905
  var AUDIT_APPROVE_BOOLEAN_FLAGS = ["--resume", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
60906
+ var PROJECTION_PUBLISH_VALUE_FLAGS = ["--base", "--branch", "--interval", "--publisher", "--publisher-command", "--remote"];
60907
+ var PROJECTION_PUBLISH_BOOLEAN_FLAGS = ["--accept-observed-major-change", "--follow"];
60908
+ var PROJECTION_PUBLISH_STATUS_VALUE_FLAGS = ["--base", "--remote"];
60909
+ var PROJECTION_VERIFY_PR_VALUE_FLAGS = ["--base"];
60910
+ var PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS = 300;
60911
+ var PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS = 30;
59883
60912
  var CLI_COMMAND_FLAGS = {
59884
60913
  debt: { value: ["--changed-path"], boolean: [] },
59885
60914
  "agent-context": { value: ["--expected-worktree-digest", "--id", "--task-session-id"], boolean: ["--approved"] },
@@ -59993,7 +61022,10 @@ var CLI_COMMAND_FLAGS = {
59993
61022
  recover: { value: ["--request-json"], boolean: [] },
59994
61023
  readback: { value: ["--request-json"], boolean: [] },
59995
61024
  snapshot: { value: [], boolean: [] },
59996
- freshness: { value: ["--changed-path"], boolean: [] }
61025
+ freshness: { value: ["--changed-path"], boolean: [] },
61026
+ publish: { value: PROJECTION_PUBLISH_VALUE_FLAGS, boolean: PROJECTION_PUBLISH_BOOLEAN_FLAGS },
61027
+ "publish-status": { value: PROJECTION_PUBLISH_STATUS_VALUE_FLAGS, boolean: [] },
61028
+ "verify-pr": { value: PROJECTION_VERIFY_PR_VALUE_FLAGS, boolean: [] }
59997
61029
  }
59998
61030
  },
59999
61031
  recommendations: {
@@ -60128,6 +61160,48 @@ var CLI_PROJECTION_FRESHNESS_HELP = {
60128
61160
  writes: "none; no daemon, no local store, no CodeGraph sync. Outside a Git worktree it fails with AC_REPO_NOT_FOUND (git-worktree-required).",
60129
61161
  capability: "projection-freshness-v1"
60130
61162
  };
61163
+ var CLI_PROJECTION_PUBLISH_HELP = {
61164
+ usage: "archctx projection publish [--base <branch>] [--remote <name>] [--branch <archctx/...>] [--publisher gh|command|none] [--publisher-command <executable>] [--accept-observed-major-change] [--follow [--interval <seconds>]] [--json]",
61165
+ description: "Projection writer mode. Fetches the remote base tip, resets a writer-owned worktree to it, applies the projection through that worktree's archctxd, and pushes the result as exactly one commit on top of the base tip to a rolling archctx/* branch, then opens or updates its pull request. Only paths the apply wrote that are projection-owned outputs may change; anything else fails before a commit. One run per remote and base at a time (busy otherwise); the same base SHA is a no-op once published or up to date; a base that moves during the run is re-evaluated and the stale commit is never pushed.",
61166
+ options: {
61167
+ "--base <branch>": "The branch the projection is computed for (default: main).",
61168
+ "--remote <name>": "The remote to fetch the base from and push the rolling branch to (default: origin).",
61169
+ "--branch <archctx/...>": "The rolling branch (default: archctx/projection). It must start with archctx/ and differ from --base; the writer never pushes to any other ref.",
61170
+ "--publisher gh|command|none": "gh (default): the operator's own gh lists, then edits or creates the pull request. command: run --publisher-command. none: push only.",
61171
+ "--publisher-command <executable>": "With --publisher command: an executable run with no shell or arguments in the owned worktree, with PATH, HOME and ARCHCTX_PUBLISH_REMOTE/BASE/BRANCH/BASE_SHA/COMMIT/TITLE/BODY_FILE only (on Windows also SystemRoot, windir, ComSpec, PATHEXT, TEMP, TMP, USERPROFILE; #! scripts run through their interpreter on PATH; .cmd/.bat are rejected). Optional stdout: a JSON object {url?, number?}.",
61172
+ "--accept-observed-major-change": "Accept the major change the run observes (ProjectionRequestV1.acceptObservedMajorChange). Without it a major change stops at human-action-required and nothing is pushed.",
61173
+ "--follow": "Run as a foreground loop: one publish per --interval, one JSON envelope line per run on stdout, until SIGINT or SIGTERM. For launchd/systemd KeepAlive on an always-on host.",
61174
+ "--interval <seconds>": `With --follow: seconds between runs (default ${PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS}, minimum ${PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS}).`,
61175
+ "--json": "Accepted for symmetry; output is always JSON.",
61176
+ "--help, -h": "Show help without fetching, pushing or starting a daemon."
61177
+ },
61178
+ results: "published, up-to-date, already-processed, human-action-required, busy (ok) or failed (ok: false with the run as data).",
61179
+ writes: "the writer state, lock and owned worktree under the repository runtime state directory; one commit on the owned worktree; a push to refs/heads/<--branch> only. Outbound: the projection-publishing egress channel (git fetch/ls-remote/push, gh, the publisher command); ARCHCONTEXT_EGRESS_MODE=local-only refuses it with AC_POLICY_VIOLATION. archctx stores no credentials. It never runs codegraph init; when code facts are required, run codegraph init <worktreePath> once (see projection publish-status).",
61180
+ capability: "projection-publish-v1"
61181
+ };
61182
+ var CLI_PROJECTION_PUBLISH_STATUS_HELP = {
61183
+ usage: "archctx projection publish-status [--base <branch>] [--remote <name>] [--json]",
61184
+ description: "Read-only state of the projection writer for one remote and base: the owned worktree path, a running writer's pid, the last processed and last successful base SHAs and the last run.",
61185
+ options: {
61186
+ "--base <branch>": "Default: main.",
61187
+ "--remote <name>": "Default: origin.",
61188
+ "--json": "Accepted for symmetry; output is always JSON.",
61189
+ "--help, -h": "Show help without reading anything."
61190
+ },
61191
+ writes: "none; no fetch, no daemon.",
61192
+ capability: "projection-publish-v1"
61193
+ };
61194
+ var CLI_PROJECTION_VERIFY_PR_HELP = {
61195
+ usage: "archctx projection verify-pr --base <ref> [--json]",
61196
+ description: "Checks the checked-out pull request head (HEAD, clean tracked worktree) against --base. A changed path is generated only when it is the projection manifest, a manifest target path or an agent-context target path at HEAD or at the merge base, and, for a mixed-ownership target, its text outside the generated regions is unchanged (otherwise it is listed in humanEditedPaths). Only a generated-only diff is rendered (projection check, plus the agent-context render for changed contract files). verdict is no-changes, touches-non-generated (answered from the diff alone, nothing rendered), generated-only-render-equivalent (generated files and regions equal a fresh render and no text outside them changed) or generated-only-not-render-equivalent. A mixed target that is added or deleted, or whose markers do not parse, is conservatively a human edit. The envelope is ok for every valid evaluation; the verdict carries the decision.",
61197
+ options: {
61198
+ "--base <ref>": "Required. The pull request base, for example origin/main.",
61199
+ "--json": "Accepted for symmetry; output is always JSON.",
61200
+ "--help, -h": "Show help without reading the repository."
61201
+ },
61202
+ writes: "none; no daemon, no local store. A dirty tracked worktree fails with AC_PRECONDITION_FAILED. A generated-only diff renders in-process like projection check, so it needs code facts when the manifest requires them.",
61203
+ capability: "projection-verify-pr-v1"
61204
+ };
60131
61205
  function refactorFlagValue(args, flag) {
60132
61206
  const index = args.indexOf(flag);
60133
61207
  if (index === -1)
@@ -60264,14 +61338,14 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
60264
61338
  case "status":
60265
61339
  return (await runtime()).runtimeStatus(cwd);
60266
61340
  case "projection":
60267
- return await runProjectionProtocolCommand2(args, cwd, runtime);
61341
+ return await runProjectionProtocolCommand2(args, cwd, runtime, deps);
60268
61342
  case "flows":
60269
61343
  return runFlowsCommand(args, cwd);
60270
61344
  case "repo": {
60271
61345
  const subcommand = args[0] ?? "list";
60272
61346
  if (subcommand === "add") {
60273
61347
  const root = readFlag(args, "--root") ?? cwd;
60274
- if (resolve34(root) !== resolve34(cwd)) {
61348
+ if (resolve35(root) !== resolve35(cwd)) {
60275
61349
  return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
60276
61350
  }
60277
61351
  const daemon = await runtime();
@@ -60615,6 +61689,12 @@ function cliHelpEnvelope(command, args) {
60615
61689
  case "projection":
60616
61690
  if (subcommand === "freshness")
60617
61691
  return okEnvelope("help", CLI_PROJECTION_FRESHNESS_HELP);
61692
+ if (subcommand === "publish")
61693
+ return okEnvelope("help", CLI_PROJECTION_PUBLISH_HELP);
61694
+ if (subcommand === "publish-status")
61695
+ return okEnvelope("help", CLI_PROJECTION_PUBLISH_STATUS_HELP);
61696
+ if (subcommand === "verify-pr")
61697
+ return okEnvelope("help", CLI_PROJECTION_VERIFY_PR_HELP);
60618
61698
  break;
60619
61699
  case "daemon":
60620
61700
  return okEnvelope("daemon", {
@@ -61331,8 +62411,10 @@ async function runAgentContextProjectionCommand2(args, cwd, daemon) {
61331
62411
  expectedWorktreeDigest: readFlag(args, "--expected-worktree-digest")
61332
62412
  });
61333
62413
  }
61334
- async function runProjectionProtocolCommand2(args, cwd, runtime) {
62414
+ async function runProjectionProtocolCommand2(args, cwd, runtime, deps) {
61335
62415
  const action = args[0] ?? "run";
62416
+ if (action === "publish" || action === "publish-status" || action === "verify-pr")
62417
+ return runProjectionWriterCliCommand(action, args.slice(1), cwd, deps);
61336
62418
  if (action === "snapshot") {
61337
62419
  const unexpected = args.slice(1).filter((arg) => arg !== "--json");
61338
62420
  if (unexpected.length > 0)
@@ -61342,7 +62424,7 @@ async function runProjectionProtocolCommand2(args, cwd, runtime) {
61342
62424
  if (action === "freshness")
61343
62425
  return runProjectionFreshnessCliCommand(args.slice(1), cwd);
61344
62426
  if (!["run", "recover", "readback"].includes(action))
61345
- return errorEnvelope("projection", "AC_SCHEMA_INVALID", "projection requires run|recover|readback --request-json <request>, or snapshot|freshness [--json]");
62427
+ return errorEnvelope("projection", "AC_SCHEMA_INVALID", "projection requires run|recover|readback --request-json <request>, snapshot|freshness [--json], or publish|publish-status|verify-pr");
61346
62428
  const raw = readFlag(args, "--request-json");
61347
62429
  if (!raw)
61348
62430
  return errorEnvelope(`projection.${action}`, "AC_SCHEMA_INVALID", `projection ${action} requires --request-json`);
@@ -61379,6 +62461,145 @@ function runProjectionFreshnessCliCommand(args, cwd) {
61379
62461
  }
61380
62462
  return runProjectionFreshnessCommand(cwd, changedPaths.length > 0 ? { changedPaths } : {});
61381
62463
  }
62464
+ async function runProjectionWriterCliCommand(action, args, cwd, deps) {
62465
+ const requestId = `projection.${action}`;
62466
+ const valueFlags = action === "publish" ? PROJECTION_PUBLISH_VALUE_FLAGS : action === "publish-status" ? PROJECTION_PUBLISH_STATUS_VALUE_FLAGS : PROJECTION_VERIFY_PR_VALUE_FLAGS;
62467
+ const stray = projectionWriterStrayToken(args, valueFlags);
62468
+ if (stray !== undefined)
62469
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", `projection ${action} does not accept ${stray}; use --help`, "cli-input-invalid");
62470
+ if (action === "verify-pr") {
62471
+ const base = readFlag(args, "--base");
62472
+ if (!base)
62473
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", "projection verify-pr requires --base <ref>", "cli-input-invalid");
62474
+ return runProjectionVerifyPrCommand({ cwd, base });
62475
+ }
62476
+ if (action === "publish-status") {
62477
+ return runProjectionPublishStatusCommand({ cwd, base: readFlag(args, "--base"), remote: readFlag(args, "--remote") });
62478
+ }
62479
+ const parsed = parseProjectionPublishCliInput(args, cwd);
62480
+ if (!parsed.ok)
62481
+ return parsed.envelope;
62482
+ if (parsed.input.follow) {
62483
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", "projection publish --follow is a foreground process loop of the archctx executable; without --follow this command runs once", "cli-input-invalid");
62484
+ }
62485
+ return runProjectionPublishOnce(parsed.input, cwd, deps);
62486
+ }
62487
+ function projectionWriterStrayToken(args, valueFlags) {
62488
+ for (let index = 0;index < args.length; index += 1) {
62489
+ const token = args[index];
62490
+ if (valueFlags.includes(token) || token === "--format") {
62491
+ index += 1;
62492
+ continue;
62493
+ }
62494
+ if (isCliFlagToken(token))
62495
+ continue;
62496
+ return token;
62497
+ }
62498
+ return;
62499
+ }
62500
+ function parseProjectionPublishCliInput(args, cwd) {
62501
+ const invalid = (message) => ({ ok: false, envelope: errorEnvelope("projection.publish", "AC_SCHEMA_INVALID", message, "cli-input-invalid") });
62502
+ const publisherKind = readFlag(args, "--publisher") ?? "gh";
62503
+ const publisherCommand = readFlag(args, "--publisher-command");
62504
+ const follow = args.includes("--follow");
62505
+ const intervalFlag = readFlag(args, "--interval");
62506
+ if (intervalFlag !== undefined && !follow)
62507
+ return invalid("projection publish --interval requires --follow");
62508
+ const intervalSeconds = intervalFlag === undefined ? PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS : Number(intervalFlag);
62509
+ if (!/^\d+$/.test(intervalFlag ?? "0") || !Number.isSafeInteger(intervalSeconds) || intervalSeconds < PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS) {
62510
+ return invalid(`projection publish --interval must be an integer number of seconds, at least ${PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS}`);
62511
+ }
62512
+ let publisher;
62513
+ if (publisherKind === "gh" || publisherKind === "none") {
62514
+ if (publisherCommand !== undefined)
62515
+ return invalid("projection publish --publisher-command requires --publisher command");
62516
+ publisher = publisherKind === "gh" ? createGhProjectionPublisher() : createNoneProjectionPublisher();
62517
+ } else if (publisherKind === "command") {
62518
+ if (!publisherCommand)
62519
+ return invalid("projection publish --publisher command requires --publisher-command <executable>");
62520
+ const command = resolve35(cwd, publisherCommand);
62521
+ if (!existsSync35(command) || !statSync11(command).isFile())
62522
+ return invalid(`projection publish --publisher-command is not a file: ${command}`);
62523
+ publisher = createCommandProjectionPublisher({ command });
62524
+ } else {
62525
+ return invalid("projection publish --publisher must be gh, command or none");
62526
+ }
62527
+ return {
62528
+ ok: true,
62529
+ input: {
62530
+ remote: readFlag(args, "--remote"),
62531
+ base: readFlag(args, "--base"),
62532
+ branch: readFlag(args, "--branch"),
62533
+ publisher,
62534
+ acceptObservedMajorChange: args.includes("--accept-observed-major-change"),
62535
+ follow,
62536
+ intervalSeconds
62537
+ }
62538
+ };
62539
+ }
62540
+ function runProjectionPublishOnce(input, cwd, deps) {
62541
+ const clients = new Map;
62542
+ return runProjectionPublishCommand({
62543
+ cwd,
62544
+ remote: input.remote,
62545
+ base: input.base,
62546
+ branch: input.branch,
62547
+ publisher: input.publisher,
62548
+ acceptObservedMajorChange: input.acceptObservedMajorChange,
62549
+ apply: async (worktree, request) => {
62550
+ let client = deps.runtimeClient ?? clients.get(worktree);
62551
+ if (!client) {
62552
+ client = await createOrStartRuntimeRpcClient(worktree);
62553
+ clients.set(worktree, client);
62554
+ }
62555
+ return client.projection(worktree, { action: "run", request });
62556
+ }
62557
+ });
62558
+ }
62559
+ async function runProjectionPublishFollowEntry(args, cwd) {
62560
+ const stray = projectionWriterStrayToken(args, PROJECTION_PUBLISH_VALUE_FLAGS);
62561
+ const parsed = stray === undefined ? parseProjectionPublishCliInput(args, cwd) : { ok: false, envelope: errorEnvelope("projection.publish", "AC_SCHEMA_INVALID", `projection publish does not accept ${stray}; use --help`, "cli-input-invalid") };
62562
+ if (!parsed.ok) {
62563
+ process.stdout.write(`${JSON.stringify(parsed.envelope)}
62564
+ `);
62565
+ return 1;
62566
+ }
62567
+ const controller = new AbortController;
62568
+ const stop = () => controller.abort();
62569
+ process.on("SIGINT", stop);
62570
+ process.on("SIGTERM", stop);
62571
+ try {
62572
+ await runProjectionPublishFollow({
62573
+ runOnce: () => runProjectionPublishOnce(parsed.input, cwd, {}),
62574
+ intervalMs: parsed.input.intervalSeconds * 1000,
62575
+ sleep: (ms) => sleepUnlessAborted(ms, controller.signal),
62576
+ shouldStop: () => controller.signal.aborted,
62577
+ onResult: (envelope) => {
62578
+ process.stdout.write(`${JSON.stringify(envelope)}
62579
+ `);
62580
+ }
62581
+ });
62582
+ } finally {
62583
+ process.off("SIGINT", stop);
62584
+ process.off("SIGTERM", stop);
62585
+ }
62586
+ return 0;
62587
+ }
62588
+ function sleepUnlessAborted(ms, signal) {
62589
+ return new Promise((resolvePromise) => {
62590
+ if (signal.aborted) {
62591
+ resolvePromise();
62592
+ return;
62593
+ }
62594
+ const timer = setTimeout(done, ms);
62595
+ function done() {
62596
+ clearTimeout(timer);
62597
+ signal.removeEventListener("abort", done);
62598
+ resolvePromise();
62599
+ }
62600
+ signal.addEventListener("abort", done, { once: true });
62601
+ });
62602
+ }
61382
62603
  function runFlowsCommand(args, cwd) {
61383
62604
  const formatAt = args.indexOf("--format");
61384
62605
  const unexpected = args.slice(1).filter((arg, index) => arg !== "--json" && (formatAt < 0 || index + 1 !== formatAt && index !== formatAt));
@@ -61742,12 +62963,12 @@ function auditManifestGateRoot(cwd) {
61742
62963
  }
61743
62964
  }
61744
62965
  function auditGithubIssuesEnabled(cwd) {
61745
- const manifestPath = resolve34(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
61746
- if (!existsSync34(manifestPath))
62966
+ const manifestPath = resolve35(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
62967
+ if (!existsSync35(manifestPath))
61747
62968
  return false;
61748
62969
  let raw;
61749
62970
  try {
61750
- raw = readFileSync31(manifestPath, "utf8");
62971
+ raw = readFileSync32(manifestPath, "utf8");
61751
62972
  } catch {
61752
62973
  return false;
61753
62974
  }
@@ -62032,8 +63253,8 @@ async function runGithubCommand(args, cwd, deps) {
62032
63253
  }
62033
63254
  if (subcommand === "disconnect") {
62034
63255
  const connection = await deps.githubConnectionReader?.readVerifiedConnection({ repositoryRoot: cwd });
62035
- const localRecordRemoved = existsSync34(connectionPath);
62036
- rmSync15(connectionPath, { force: true });
63256
+ const localRecordRemoved = existsSync35(connectionPath);
63257
+ rmSync16(connectionPath, { force: true });
62037
63258
  return okEnvelope("github.disconnect", {
62038
63259
  connected: connection !== undefined,
62039
63260
  localRecordRemoved,
@@ -62320,7 +63541,7 @@ async function digestReviewChallenge(challenge) {
62320
63541
  return reviewChallengeV2Digest(challenge);
62321
63542
  }
62322
63543
  function defaultGithubConnectionPath(cwd) {
62323
- return join26(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
63544
+ return join27(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
62324
63545
  }
62325
63546
  function sanitizeGithubConnection(record, connectionPath) {
62326
63547
  return {
@@ -62344,7 +63565,7 @@ async function readReviewChallengeV2Arg(args, cwd, commandName = "github verify-
62344
63565
  if (!inline && !challengePath)
62345
63566
  return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
62346
63567
  try {
62347
- const raw = inline ?? readFileSync31(resolve34(cwd, challengePath), "utf8");
63568
+ const raw = inline ?? readFileSync32(resolve35(cwd, challengePath), "utf8");
62348
63569
  const parsed = JSON.parse(raw);
62349
63570
  const attestation = await Promise.resolve().then(() => (init_src6(), {}));
62350
63571
  const assertReviewChallengeV2 = assertReviewChallengeV22;
@@ -62728,7 +63949,7 @@ function readLatestPackageVersion(env) {
62728
63949
  if (env[LATEST_VERSION_ENV]) {
62729
63950
  return { source: "env", version: env[LATEST_VERSION_ENV] };
62730
63951
  }
62731
- const result = withLocalEgress("npm-update-check", () => spawnSync5("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
63952
+ const result = withLocalEgress("npm-update-check", () => spawnSync6("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
62732
63953
  encoding: "utf8",
62733
63954
  timeout: NPM_VIEW_TIMEOUT_MS,
62734
63955
  shell: false
@@ -62822,10 +64043,10 @@ function doctorSqlite(cwd) {
62822
64043
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
62823
64044
  return {
62824
64045
  path,
62825
- exists: existsSync34(path),
64046
+ exists: existsSync35(path),
62826
64047
  migrations: productVersionManifest().runtime.sqliteMigrations,
62827
64048
  legacyPath: paths.legacyLocalStorePath,
62828
- legacyExists: existsSync34(paths.legacyLocalStorePath),
64049
+ legacyExists: existsSync35(paths.legacyLocalStorePath),
62829
64050
  legacyLocalStore
62830
64051
  };
62831
64052
  }
@@ -62846,8 +64067,8 @@ function runtimePathsReport(cwd) {
62846
64067
  ...paths,
62847
64068
  legacyLocalStore: inspectLegacyLocalStoreMigration(cwd),
62848
64069
  runtimeRepositoryId: repositoryFingerprint2(paths.repositoryRoot),
62849
- repositoryTruthDir: join26(paths.repositoryRoot, ".archcontext"),
62850
- codeGraphIndexDir: join26(paths.repositoryRoot, ".codegraph"),
64070
+ repositoryTruthDir: join27(paths.repositoryRoot, ".archcontext"),
64071
+ codeGraphIndexDir: join27(paths.repositoryRoot, ".codegraph"),
62851
64072
  npmGlobalInstallState: "forbidden",
62852
64073
  overrides: {
62853
64074
  stateRootEnv: "ARCHCONTEXT_STATE_DIR",
@@ -62856,7 +64077,7 @@ function runtimePathsReport(cwd) {
62856
64077
  };
62857
64078
  }
62858
64079
  function pathAccess(path) {
62859
- const exists = existsSync34(path);
64080
+ const exists = existsSync35(path);
62860
64081
  return {
62861
64082
  path,
62862
64083
  exists,
@@ -63097,7 +64318,7 @@ function daemonStopStateDir(args, cwd) {
63097
64318
  return { ok: false, envelope: errorEnvelope("daemon.stop", "AC_SCHEMA_INVALID", "daemon stop accepts at most one of --state-dir or --workspace-id") };
63098
64319
  }
63099
64320
  if (stateDir !== undefined)
63100
- return { ok: true, stateDir: resolve34(cwd, stateDir) };
64321
+ return { ok: true, stateDir: resolve35(cwd, stateDir) };
63101
64322
  if (workspaceId === undefined)
63102
64323
  return { ok: true };
63103
64324
  if (!/^ws\.[0-9a-f]{16}$/.test(workspaceId)) {
@@ -63133,9 +64354,9 @@ async function startBackgroundDaemon(args, cwd) {
63133
64354
  const recovery = discovered.recovery;
63134
64355
  const connectionPath = defaultDaemonConnectionPath(cwd);
63135
64356
  const controlDir = dirname16(connectionPath);
63136
- const logPath = join26(controlDir, "archctxd.log");
63137
- mkdirSync13(controlDir, { recursive: true });
63138
- const logFd = openSync12(logPath, "a", 384);
64357
+ const logPath = join27(controlDir, "archctxd.log");
64358
+ mkdirSync14(controlDir, { recursive: true });
64359
+ const logFd = openSync13(logPath, "a", 384);
63139
64360
  try {
63140
64361
  let childExit;
63141
64362
  let childError;
@@ -63175,7 +64396,7 @@ async function startBackgroundDaemon(args, cwd) {
63175
64396
  ...recoveryData(recovery)
63176
64397
  });
63177
64398
  } finally {
63178
- closeSync12(logFd);
64399
+ closeSync13(logFd);
63179
64400
  }
63180
64401
  }
63181
64402
  async function upgradeDaemon(args, cwd) {
@@ -63184,7 +64405,7 @@ async function upgradeDaemon(args, cwd) {
63184
64405
  const record = readRuntimeRpcConnectionFile(cwd);
63185
64406
  const unchanged = () => JSON.stringify(readRuntimeRpcConnectionFile(cwd)) === JSON.stringify(record);
63186
64407
  const refuse = (reason) => errorEnvelope("daemon.upgrade", "AC_RUNTIME_VERSION_UNSUPPORTED", `Daemon ownership is unproven (${reason}); no process was signalled and no replacement was started. Run the previous archctx daemon stop, or verify and stop the original daemon in its owning terminal, then run archctx daemon upgrade.`, "daemon-ownership-unproven");
63187
- if (existsSync34(connectionPath) && (!record || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0)) {
64408
+ if (existsSync35(connectionPath) && (!record || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0)) {
63188
64409
  return refuse("connection record cannot prove a daemon identity");
63189
64410
  }
63190
64411
  let lockOwner;
@@ -63234,9 +64455,9 @@ async function upgradeDaemon(args, cwd) {
63234
64455
  if (!stopped.ok)
63235
64456
  return refuse("live instance refused the expected identity");
63236
64457
  const deadline = Date.now() + daemonStartTimeoutMs(args);
63237
- while (existsSync34(connectionPath) || existsSync34(lockPath)) {
64458
+ while (existsSync35(connectionPath) || existsSync35(lockPath)) {
63238
64459
  const current = readRuntimeRpcConnectionFile(cwd);
63239
- if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync34(connectionPath)) {
64460
+ if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync35(connectionPath)) {
63240
64461
  return refuse("connection changed or cannot be verified during self-stop");
63241
64462
  }
63242
64463
  const owner = liveDaemonLockOwnerPid(cwd);
@@ -63256,7 +64477,7 @@ async function upgradeDaemon(args, cwd) {
63256
64477
  const started = await startBackgroundDaemon(args, cwd);
63257
64478
  return started.ok ? { ...started, requestId: "daemon.upgrade", data: { ...started.data, upgraded: false, reason: "runtime-compatible" } } : started;
63258
64479
  }
63259
- if (selfStopped ? existsSync34(connectionPath) || existsSync34(lockPath) : !unchanged() || record?.pid !== undefined && isPidAlive(record.pid) || liveDaemonLockOwnerPid(cwd) !== undefined) {
64480
+ if (selfStopped ? existsSync35(connectionPath) || existsSync35(lockPath) : !unchanged() || record?.pid !== undefined && isPidAlive(record.pid) || liveDaemonLockOwnerPid(cwd) !== undefined) {
63260
64481
  return refuse("ownership changed before replacement");
63261
64482
  }
63262
64483
  const recovery = recoverStaleDaemonControlFiles(cwd, { removeUnhealthyConnection: true });
@@ -63422,7 +64643,7 @@ function daemonStartTimeoutMs(args) {
63422
64643
  }
63423
64644
  function daemonStartFailureMessage(cwd, logPath, childPid, childExit, childError) {
63424
64645
  const childState = childError ? `childError=${childError.message}` : childExit ? `childExit=${childExit.code ?? "null"}/${childExit.signal ?? "none"}` : "childState=still-running";
63425
- const lockOwnerPid = existsSync34(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
64646
+ const lockOwnerPid = existsSync35(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
63426
64647
  const owner = lockOwnerPid === undefined || lockOwnerPid === childPid ? "" : `; ${orphanedDaemonMessage(lockOwnerPid)}`;
63427
64648
  return `archctxd did not become ready; ${childState}${owner}; log=${logPath}; logTail=${readFileTail(logPath)}`;
63428
64649
  }
@@ -63443,7 +64664,7 @@ function sleep2(ms) {
63443
64664
  }
63444
64665
  function readFileTail(path, maxBytes = 4096) {
63445
64666
  try {
63446
- const content = readFileSync31(path, "utf8");
64667
+ const content = readFileSync32(path, "utf8");
63447
64668
  return content.slice(Math.max(0, content.length - maxBytes)).replace(/\s+/g, " ").trim();
63448
64669
  } catch {
63449
64670
  return "<unavailable>";
@@ -63528,7 +64749,7 @@ function resolveCommandExitCode(result) {
63528
64749
  return 2;
63529
64750
  return 1;
63530
64751
  }
63531
- var CLI_HELP_EXAMPLES = ["archctx init --name MyApp", "archctx projection run --request-json '{...}'", "archctx projection snapshot --json", "archctx projection freshness --json", "archctx projection freshness --changed-path src/app.ts --json", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body '<complete YAML>'", "archctx plan --id changeset.<id> --op delete_entity --path .archcontext/model/flows/<flow>.yaml --expected-hash sha256:<64-hex>", "archctx hash --path .archcontext/model/nodes/<node>.yaml", "archctx plan --id changeset.<id> --operations-file <operations.json>", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body-file <entity.yaml>", "archctx apply --id changeset.<id> --approved --expected-worktree-digest <draft.base.worktreeDigest>", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx recommendations list --status rejected", "archctx refactor scan --json", "archctx refactor verify --request-json '{...}' --json", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --changed-path src/app.ts", "archctx practices checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit consent", "archctx audit consent --revoke", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<host>/<owner>/<repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx docs anchors --json", "archctx export native --worktree --json", "archctx export native --ref HEAD --profile repo-harness/v1 --json", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel", "archctx refactor scan --import-node-limit 100000 --json", "archctx resolve --paths-from changed-paths.txt", "archctx flows check --json", "archctx recommendations defer --id recommendation.<id> --import-node-limit 100000 --reason 'Scanned with the same limit.'", "archctx deps --json", "archctx deps --into capability.<id> --json"];
64752
+ var CLI_HELP_EXAMPLES = ["archctx init --name MyApp", "archctx projection run --request-json '{...}'", "archctx projection snapshot --json", "archctx projection freshness --json", "archctx projection freshness --changed-path src/app.ts --json", "archctx projection publish --json", "archctx projection publish --publisher none --json", "archctx projection publish --follow --interval 300", "archctx projection publish-status --json", "archctx projection verify-pr --base origin/main --json", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body '<complete YAML>'", "archctx plan --id changeset.<id> --op delete_entity --path .archcontext/model/flows/<flow>.yaml --expected-hash sha256:<64-hex>", "archctx hash --path .archcontext/model/nodes/<node>.yaml", "archctx plan --id changeset.<id> --operations-file <operations.json>", "archctx plan --id changeset.<id> --op update_entity_fields --path .archcontext/model/nodes/<node>.yaml --expected-hash sha256:<64-hex> --body-file <entity.yaml>", "archctx apply --id changeset.<id> --approved --expected-worktree-digest <draft.base.worktreeDigest>", "archctx state recover --from-git", "archctx ledger migrate --from-yaml --dry-run", "archctx ledger promote --mode authoritative --preflight --rollback-plan", "archctx book recommendations --open --explain", "archctx recommendations accept --id recommendation.<id> --reason 'Accepted after local readback.'", "archctx recommendations metrics", "archctx recommendations list --status rejected", "archctx refactor scan --json", "archctx refactor verify --request-json '{...}' --json", "archctx practices validate --strict", "archctx practices list --json", "archctx practices waivers", "archctx practices waive --practice-id modularity.no-new-cycle --owner team-architecture --reason 'External migration window requires this edge until cutover.' --review-at 2026-07-10T00:00:00.000Z --expires-at 2026-07-24T00:00:00.000Z --evidence-digest sha256:<64-hex> --subject module.a->module.b", "archctx checkpoint --changed-path src/app.ts", "archctx practices checkpoint --task-session-id task_cli", "archctx investigate --runner-port codex", "archctx agents status --status queued,running", "archctx agents budget", "archctx hook enqueue --event post-edit --path src/app.ts", "archctx jobs list --status queued", "archctx audit consent", "archctx audit consent --revoke", "archctx audit run --reason 'quarterly architecture audit'", "archctx audit run --no-wait", "archctx audit list --status pending", "archctx audit show audit_run.<id>", "archctx audit approve audit_run.<id>", "archctx audit approve audit_run.<id> --confirm-public-repo public:<host>/<owner>/<repo>:<baseSha>:<runId>", "archctx audit approve audit_run.<id> --resume", "archctx hooks install --host codex", "archctx paths", "archctx update --check", "archctx doctor --check-updates", "archctx github connect", "archctx github status", "archctx daemon start", "archctx explore start --foreground", "archctx docs anchors --json", "archctx export native --worktree --json", "archctx export native --ref HEAD --profile repo-harness/v1 --json", "archctx export likec4", "archctx import structurizr --content '<json>'", "archctx resolve --path packages/core/projection-engine/src/index.ts", "archctx tunnel", "archctx refactor scan --import-node-limit 100000 --json", "archctx resolve --paths-from changed-paths.txt", "archctx flows check --json", "archctx recommendations defer --id recommendation.<id> --import-node-limit 100000 --reason 'Scanned with the same limit.'", "archctx deps --json", "archctx deps --into capability.<id> --json"];
63532
64753
  var CLI_RESOLVE_HELP = {
63533
64754
  command: "archctx resolve (--path <repo-relative path> | --paths-from <file> | --paths-from -)",
63534
64755
  rule: "ADR-0043. source.exclude applies first. When every matching node lies on one parent chain, the deepest node owns the path; refactor scan uses the same rule. When matching nodes lie on different chains, an ancestor yields to its own descendant, then the longest literal source.include prefix wins, and an equal-length tie is ambiguous.",
@@ -63537,7 +64758,7 @@ var CLI_RESOLVE_HELP = {
63537
64758
  exitCodes: "--path: 0 matched, 1 no match or error, 2 ambiguous. --paths-from: 0 when no path is ambiguous (unowned paths included), 2 when any path is ambiguous, 1 on error."
63538
64759
  };
63539
64760
  function loadResolveModel(cwd) {
63540
- if (!existsSync34(join26(cwd, ".archcontext/manifest.yaml"))) {
64761
+ if (!existsSync35(join27(cwd, ".archcontext/manifest.yaml"))) {
63541
64762
  throw new NativeContextError("native-context-model-missing", { path: ".archcontext/manifest.yaml" });
63542
64763
  }
63543
64764
  let model;
@@ -63593,7 +64814,7 @@ async function readResolvePathsFrom(args, cwd, deps) {
63593
64814
  return { problem: "resolve --paths-from requires a file path or - for stdin" };
63594
64815
  let content;
63595
64816
  try {
63596
- content = source === "-" ? await (deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8")) : readFileSync31(resolve34(cwd, source), "utf8");
64817
+ content = source === "-" ? await (deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8")) : readFileSync32(resolve35(cwd, source), "utf8");
63597
64818
  } catch (error) {
63598
64819
  return { problem: `resolve --paths-from ${source} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63599
64820
  }
@@ -63681,7 +64902,7 @@ function readRepositoryInputFile(cwd, flag, file) {
63681
64902
  const absolute = assertPathHasNoSymlinkSegments2(cwd, file);
63682
64903
  if (!statSync11(absolute).isFile())
63683
64904
  return { problem: `plan ${flag} ${file} is not a regular file` };
63684
- return { content: readFileSync31(absolute, "utf8") };
64905
+ return { content: readFileSync32(absolute, "utf8") };
63685
64906
  } catch (error) {
63686
64907
  return { problem: `plan ${flag} ${file} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63687
64908
  }
@@ -63699,10 +64920,10 @@ function runHashCommand(args, cwd) {
63699
64920
  } catch (error) {
63700
64921
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
63701
64922
  }
63702
- if (!existsSync34(absolute) || !statSync11(absolute).isFile()) {
64923
+ if (!existsSync35(absolute) || !statSync11(absolute).isFile()) {
63703
64924
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", `hash --path is not an existing file: ${path}`);
63704
64925
  }
63705
- return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync31(absolute, "utf8") }) });
64926
+ return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync32(absolute, "utf8") }) });
63706
64927
  }
63707
64928
  async function readCliPlanOperations(args, cwd, deps) {
63708
64929
  if (args.includes("--operations-file")) {
@@ -63712,7 +64933,7 @@ async function readCliPlanOperations(args, cwd, deps) {
63712
64933
  const file = readFlag(args, "--operations-file");
63713
64934
  if (!file)
63714
64935
  return "plan --operations-file requires a path";
63715
- const read = file === "-" ? { content: await (deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8")) } : readRepositoryInputFile(cwd, "--operations-file", file);
64936
+ const read = file === "-" ? { content: await (deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8")) } : readRepositoryInputFile(cwd, "--operations-file", file);
63716
64937
  if ("problem" in read)
63717
64938
  return read.problem;
63718
64939
  let parsed;
@@ -63774,7 +64995,7 @@ async function readCliPlanBody(args, cwd, deps) {
63774
64995
  return { problem: "plan --body requires a value (use - to read stdin)" };
63775
64996
  if (inline !== "-")
63776
64997
  return inline;
63777
- return deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8");
64998
+ return deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8");
63778
64999
  }
63779
65000
  function readFlag(args, flag) {
63780
65001
  const index = args.indexOf(flag);
@@ -63866,6 +65087,8 @@ if (__require.main == __require.module) {
63866
65087
  await runStdioMcpLoop(stdinLines(), (line) => process.stdout.write(`${line}
63867
65088
  `), (line) => process.stderr.write(`${line}
63868
65089
  `), { runtimeResolver: (root, options) => createOrStartRuntimeRpcClient(root, options?.preserveHealthFailure) });
65090
+ } else if (command === "projection" && args[0] === "publish" && args.includes("--follow") && preflightCliArguments(command, args) === undefined) {
65091
+ process.exitCode = await runProjectionPublishFollowEntry(args.slice(1), process.cwd());
63869
65092
  } else if (command === "daemon" && args[0] === "start" && args.includes("--foreground") && preflightCliArguments(command, args) === undefined) {
63870
65093
  await runForegroundDaemon(process.cwd(), args).catch((error) => {
63871
65094
  const message = error instanceof Error ? error.message : String(error);