archctx 0.8.1 → 0.9.0

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
@@ -1753,7 +1753,9 @@ var init_projection = __esm(() => {
1753
1753
  "projection-preview-v1",
1754
1754
  "projection-prior-committed-applies-v1",
1755
1755
  "projection-protocol-v2",
1756
+ "projection-publish-v1",
1756
1757
  "projection-snapshot-v1",
1758
+ "projection-verify-pr-v1",
1757
1759
  "recommendation-scan-decision-v1",
1758
1760
  "recommendation-v3",
1759
1761
  "refactor-assessment-v1",
@@ -3363,6 +3365,10 @@ var init_ports = __esm(() => {
3363
3365
  // packages/contracts/src/practices.ts
3364
3366
  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
3367
 
3368
+ // packages/contracts/src/projection-writer.ts
3369
+ 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";
3370
+ var init_projection_writer = () => {};
3371
+
3366
3372
  // packages/contracts/src/projection-manifest.ts
3367
3373
  function architectureDocsProjectionManifestIssues(value) {
3368
3374
  const issues = [];
@@ -3867,7 +3873,7 @@ function compareVersionTriples(left, right) {
3867
3873
  }
3868
3874
  return 0;
3869
3875
  }
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";
3876
+ var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.9.0", 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
3877
 
3872
3878
  // packages/contracts/src/refactor.ts
3873
3879
  function refactorNamespaceIssues(value, label = "product.id") {
@@ -4969,6 +4975,7 @@ var init_src = __esm(() => {
4969
4975
  init_ports();
4970
4976
  init_projection();
4971
4977
  init_projection_recovery();
4978
+ init_projection_writer();
4972
4979
  init_projection_manifest();
4973
4980
  init_refactor();
4974
4981
  init_schema();
@@ -15377,7 +15384,7 @@ var init_src6 = __esm(() => {
15377
15384
 
15378
15385
  // packages/local-runtime/egress-admission/src/index.ts
15379
15386
  var LOCAL_EGRESS_MODE_ENV = "ARCHCONTEXT_EGRESS_MODE";
15380
- var LOCAL_EGRESS_CHANNELS = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "codegraph-telemetry"];
15387
+ var LOCAL_EGRESS_CHANNELS = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "projection-publishing", "codegraph-telemetry"];
15381
15388
 
15382
15389
  class LocalEgressPolicyError extends Error {
15383
15390
  }
@@ -21015,6 +21022,16 @@ ${wrapped}`;
21015
21022
  }
21016
21023
  return `${existing.slice(0, region.start)}${wrapped}${existing.slice(region.end)}`;
21017
21024
  }
21025
+ function agentContextTextOutsideRegions(body, nodeIds) {
21026
+ let text = body;
21027
+ for (const nodeId of [...new Set(nodeIds)].sort()) {
21028
+ const region = findAgentContextRegion(text, nodeId);
21029
+ if (!region)
21030
+ throw new Error(`agent-context-marker-missing: ${nodeId}`);
21031
+ text = `${text.slice(0, region.start)}\x00archcontext-agent-context-region:${nodeId}\x00${text.slice(region.end)}`;
21032
+ }
21033
+ return text;
21034
+ }
21018
21035
  function findAgentContextRegion(body, nodeId) {
21019
21036
  const startPattern = new RegExp(`<!-- BEGIN ARCHCONTEXT AGENT CONTEXT id="${escapeRegExp(nodeId)}"[^>]*-->`);
21020
21037
  const startMatch = startPattern.exec(body);
@@ -22998,7 +23015,7 @@ function isGitWorktreeError(error) {
22998
23015
 
22999
23016
  // packages/local-runtime/egress-admission/src/index.ts
23000
23017
  var LOCAL_EGRESS_MODE_ENV2 = "ARCHCONTEXT_EGRESS_MODE";
23001
- var LOCAL_EGRESS_CHANNELS2 = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "codegraph-telemetry"];
23018
+ var LOCAL_EGRESS_CHANNELS2 = ["context7", "agent-audit", "github-issue-publishing", "npm-update-check", "projection-publishing", "codegraph-telemetry"];
23002
23019
 
23003
23020
  class LocalEgressPolicyError2 extends Error {
23004
23021
  }
@@ -33880,6 +33897,19 @@ async function runAgentContextProjectionCommand(input, cwd, daemon) {
33880
33897
  throw publicOperationFailure(error);
33881
33898
  }
33882
33899
  }
33900
+ function agentContextProjectionDifferingPaths(root, paths) {
33901
+ const wanted = new Set(paths);
33902
+ if (wanted.size === 0)
33903
+ return [];
33904
+ const projection = buildAgentContextProjection(root);
33905
+ const onDisk = new Map(projection.existingFiles.map((file) => [file.path, file.onDisk]));
33906
+ const written = projectionFilesBoundToRenderInput(projection.existingFiles, projection.plan.files);
33907
+ const rendered = new Set(written.map((file) => file.path));
33908
+ return [...new Set([
33909
+ ...written.filter((file) => wanted.has(file.path) && onDisk.get(file.path) !== file.body).map((file) => file.path),
33910
+ ...[...wanted].filter((path) => !rendered.has(path))
33911
+ ])].sort();
33912
+ }
33883
33913
  function buildAgentContextProjection(root) {
33884
33914
  const model = loadNativeModelFromArchContext(root);
33885
33915
  const sourceDigest = digestJson({ model });
@@ -34566,6 +34596,8 @@ function effectiveEgressChannels(env, live) {
34566
34596
  }
34567
34597
  if (live.updateCheckRequested)
34568
34598
  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" });
34599
+ if (live.projectionPublishRequested)
34600
+ 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
34601
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
34570
34602
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
34571
34603
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -34600,9 +34632,9 @@ function localEgressStatus(env = process.env, live = {}) {
34600
34632
  }
34601
34633
 
34602
34634
  // 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";
34635
+ import { spawn as spawn2, spawnSync as spawnSync6 } from "child_process";
34636
+ 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";
34637
+ import { dirname as dirname16, join as join27, parse, resolve as resolve35 } from "path";
34606
34638
  import { fileURLToPath as fileURLToPath2 } from "url";
34607
34639
 
34608
34640
  // packages/contracts/src/index.ts
@@ -34617,6 +34649,7 @@ init_ownership_dependencies();
34617
34649
  init_ports();
34618
34650
  init_projection();
34619
34651
  init_projection_recovery();
34652
+ init_projection_writer();
34620
34653
  init_projection_manifest();
34621
34654
  init_refactor();
34622
34655
  init_schema();
@@ -54326,6 +54359,947 @@ function runtimeProjectionOwnedOutputDigest(projection) {
54326
54359
  files: projection.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
54327
54360
  });
54328
54361
  }
54362
+ // packages/local-runtime/runtime-daemon/src/projection-writer.ts
54363
+ import { execFile as execFile2, spawnSync as spawnSync5 } from "node:child_process";
54364
+ import { createHash as createHash13, randomUUID as randomUUID3 } from "node:crypto";
54365
+ 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";
54366
+ import { tmpdir as tmpdir4 } from "node:os";
54367
+ import { extname as extname2, join as join23, resolve as resolve30 } from "node:path";
54368
+ init_src();
54369
+ var PROJECTION_PUBLISH_REQUEST_ID = "projection.publish";
54370
+ var PROJECTION_PUBLISH_STATUS_REQUEST_ID = "projection.publish-status";
54371
+ var PROJECTION_VERIFY_PR_REQUEST_ID = "projection.verify-pr";
54372
+ var PROJECTION_WRITER_DEFAULT_BASE = "main";
54373
+ var PROJECTION_WRITER_DEFAULT_REMOTE = "origin";
54374
+ var PROJECTION_PR_BODY_MARKER = "<!-- archctx-projection-writer -->";
54375
+ var PROJECTION_WRITER_STATE_SCHEMA_VERSION = "archcontext.projection-writer-state/v1";
54376
+ var PROJECTION_WRITER_MAX_ITERATIONS = 3;
54377
+ var PROJECTION_WRITER_TARGETS = ["agent-context", "architecture-docs"];
54378
+ var PUBLISHER_TIMEOUT_MS = 120000;
54379
+ var PR_BODY_MAX_LISTED_PATHS = 200;
54380
+
54381
+ class ProjectionWriterFailure extends Error {
54382
+ code;
54383
+ reasonCode;
54384
+ constructor(code, reasonCode, message) {
54385
+ super(message);
54386
+ this.code = code;
54387
+ this.reasonCode = reasonCode;
54388
+ this.name = "ProjectionWriterFailure";
54389
+ }
54390
+ }
54391
+ function projectionWriterPaths(repositoryRoot, remote, base, env = process.env) {
54392
+ const key = createHash13("sha256").update(`${remote}\x00${base}`, "utf8").digest("hex").slice(0, 16);
54393
+ const dir = join23(runtimeStatePaths(repositoryRoot, env).repositoryStateDir, "projection-writer", key);
54394
+ return { dir, worktree: join23(dir, "worktree"), state: join23(dir, "state.json"), lock: join23(dir, "lock") };
54395
+ }
54396
+ async function runProjectionPublishCommand(options) {
54397
+ const env = options.env ?? process.env;
54398
+ const now = options.now ?? (() => new Date);
54399
+ const remote = options.remote ?? PROJECTION_WRITER_DEFAULT_REMOTE;
54400
+ const base = options.base ?? PROJECTION_WRITER_DEFAULT_BASE;
54401
+ const branch = options.branch ?? PROJECTION_WRITER_DEFAULT_BRANCH;
54402
+ let repositoryRoot;
54403
+ try {
54404
+ repositoryRoot = findRepositoryRoot(options.cwd);
54405
+ } catch {
54406
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_REPO_NOT_FOUND", `projection publish requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54407
+ }
54408
+ const invalid = projectionWriterTargetIssue(repositoryRoot, { remote, base, branch });
54409
+ if (invalid)
54410
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_SCHEMA_INVALID", invalid, "projection-writer-input-invalid");
54411
+ try {
54412
+ assertLocalEgressAllowed2("projection-publishing", env);
54413
+ } catch (error) {
54414
+ return errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, "AC_POLICY_VIOLATION", errorMessage2(error), "egress-policy-denied");
54415
+ }
54416
+ const startedAt = now().toISOString();
54417
+ const paths = projectionWriterPaths(repositoryRoot, remote, base, env);
54418
+ const blank = () => ({
54419
+ schemaVersion: PROJECTION_PUBLISH_RUN_SCHEMA_VERSION,
54420
+ result: "failed",
54421
+ remote,
54422
+ base,
54423
+ branch,
54424
+ baseSha: null,
54425
+ publishedCommit: null,
54426
+ pushed: false,
54427
+ pullRequest: null,
54428
+ publisher: options.publisher.kind,
54429
+ files: [],
54430
+ projectionStatus: null,
54431
+ humanActions: [],
54432
+ error: null,
54433
+ startedAt,
54434
+ finishedAt: startedAt,
54435
+ iterations: 0
54436
+ });
54437
+ let lock;
54438
+ try {
54439
+ mkdirSync12(paths.dir, { recursive: true, mode: 448 });
54440
+ lock = acquireProjectionWriterLock(paths.lock, startedAt);
54441
+ } catch (error) {
54442
+ const failure = asWriterFailure(error);
54443
+ return projectionPublishEnvelope({ ...blank(), finishedAt: now().toISOString(), error: writerError(failure) });
54444
+ }
54445
+ if (lock.kind === "busy") {
54446
+ return projectionPublishEnvelope({ ...blank(), result: "busy", holderPid: lock.pid, finishedAt: now().toISOString() });
54447
+ }
54448
+ try {
54449
+ let state;
54450
+ try {
54451
+ state = readProjectionWriterState(paths.state, remote, base);
54452
+ } catch (error) {
54453
+ return projectionPublishEnvelope({ ...blank(), finishedAt: now().toISOString(), error: writerError(asWriterFailure(error)) });
54454
+ }
54455
+ const outcome = await runLockedProjectionPublish({ options, env, now, repositoryRoot, paths, remote, base, branch, state, assertOwner: () => assertProjectionWriterOwner(paths.lock, lock.record), blank: blank() });
54456
+ const next = nextProjectionWriterState(state, outcome);
54457
+ try {
54458
+ writeProjectionWriterState(paths.state, next);
54459
+ } catch (error) {
54460
+ return projectionPublishEnvelope({
54461
+ ...outcome.run,
54462
+ result: "failed",
54463
+ error: writerError(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-unwritable", errorMessage2(error)))
54464
+ });
54465
+ }
54466
+ return projectionPublishEnvelope(outcome.run);
54467
+ } finally {
54468
+ lock.release();
54469
+ }
54470
+ }
54471
+ async function runLockedProjectionPublish(input) {
54472
+ const { options, env, now, repositoryRoot, paths, remote, base, branch, state } = input;
54473
+ const runSequence = state.runSequence + 1;
54474
+ const publisherIdentity = options.publisher.identity ?? options.publisher.kind;
54475
+ let iterations = 0;
54476
+ let baseSha = null;
54477
+ const finish = (result, fields = {}, extra = {}) => ({
54478
+ run: { ...input.blank, ...fields, result, baseSha, iterations, finishedAt: now().toISOString() },
54479
+ runSequence,
54480
+ publisherIdentity,
54481
+ ...extra
54482
+ });
54483
+ const failed = (failure, fields = {}, extra = {}) => finish("failed", { ...fields, error: writerError(failure) }, extra);
54484
+ try {
54485
+ const pushRemote = projectionWriterPushUrl(repositoryRoot, remote);
54486
+ while (iterations < PROJECTION_WRITER_MAX_ITERATIONS) {
54487
+ iterations += 1;
54488
+ remoteGit(repositoryRoot, ["fetch", "--no-tags", remote, `+refs/heads/${base}:refs/remotes/${remote}/${base}`], env);
54489
+ baseSha = git(repositoryRoot, ["rev-parse", "--verify", `refs/remotes/${remote}/${base}^{commit}`]).trim();
54490
+ const successMatches = state.lastSuccessfulBranch === branch && state.lastSuccessfulSha === baseSha;
54491
+ const pending = state.publisherPending?.branch === branch && state.publisherPending.baseSha === baseSha ? state.publisherPending : successMatches && state.lastSuccessfulCommit && state.lastSuccessfulPublisher !== publisherIdentity ? { branch, baseSha, commitSha: state.lastSuccessfulCommit, files: state.lastSuccessfulFiles } : null;
54492
+ if (!pending && successMatches && state.lastSuccessfulPublisher === publisherIdentity && (state.lastSuccessfulCommit === null || lsRemoteBranch(repositoryRoot, pushRemote, branch, env) === state.lastSuccessfulCommit)) {
54493
+ return finish("already-processed", { publishedCommit: state.lastSuccessfulCommit });
54494
+ }
54495
+ input.assertOwner();
54496
+ prepareProjectionWriterWorktree(repositoryRoot, paths.worktree, baseSha);
54497
+ if (pending && lsRemoteBranch(repositoryRoot, pushRemote, branch, env) === pending.commitSha) {
54498
+ input.assertOwner();
54499
+ const published = await publishPullRequest(options.publisher, { worktree: paths.worktree, remote, base, branch, baseSha, commitSha: pending.commitSha, files: pending.files });
54500
+ if (!published.ok) {
54501
+ return failed(published.failure, { publishedCommit: pending.commitSha, files: pending.files }, { publisherPending: pending });
54502
+ }
54503
+ return finish("published", { publishedCommit: pending.commitSha, files: pending.files, pullRequest: published.pullRequest });
54504
+ }
54505
+ const ownedPatterns = projectionOwnedPaths(loadNativeModelFromArchContext(paths.worktree));
54506
+ const snapshot = runProjectionSnapshotCommand(paths.worktree);
54507
+ if (!snapshot.ok)
54508
+ throw envelopeFailure(snapshot, "projection-writer-snapshot-failed");
54509
+ const expected = snapshot.data.expected;
54510
+ const request = {
54511
+ schemaVersion: PROJECTION_REQUEST_SCHEMA_VERSION,
54512
+ requestId: `projection_publish.${baseSha.slice(0, 12)}.${runSequence}-${iterations}`,
54513
+ profile: "repo-harness/v1",
54514
+ mode: "apply",
54515
+ targets: [...PROJECTION_WRITER_TARGETS],
54516
+ changedPaths: [],
54517
+ expected,
54518
+ ...options.acceptObservedMajorChange ? { acceptObservedMajorChange: true } : {}
54519
+ };
54520
+ const applied = await options.apply(paths.worktree, request);
54521
+ input.assertOwner();
54522
+ if (!applied.ok) {
54523
+ const failure = envelopeFailure(applied, "projection-apply-failed");
54524
+ if (failure.code === "AC_CODE_FACTS_UNAVAILABLE") {
54525
+ throw new ProjectionWriterFailure(failure.code, failure.reasonCode, `${failure.message}; the writer never initializes CodeGraph itself: run \`codegraph init ${paths.worktree}\` once, then publish again`);
54526
+ }
54527
+ throw failure;
54528
+ }
54529
+ const result = applied.data;
54530
+ const projectionStatus = result.status;
54531
+ if (projectionStatus === "human-action-required" || projectionStatus === "adoption-required") {
54532
+ return finish("human-action-required", { projectionStatus, humanActions: publishHumanActions(result) });
54533
+ }
54534
+ if (!["noop", "applied", "applied-partial"].includes(projectionStatus)) {
54535
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-apply-status-unsupported", `projection apply ended with status ${projectionStatus}; the writer publishes only noop, applied and applied-partial results`);
54536
+ }
54537
+ const changed = worktreeChangedPaths(paths.worktree);
54538
+ if (changed.length === 0)
54539
+ return finish("up-to-date", { projectionStatus });
54540
+ const written = new Set(result.files.filter((file) => file.action !== "unchanged").map((file) => file.path));
54541
+ const offending = changed.filter((path) => !written.has(path) || !isProjectionOwnedPath(path, ownedPatterns));
54542
+ if (offending.length > 0) {
54543
+ return failed(new ProjectionWriterFailure("AC_POLICY_VIOLATION", "projection-writer-non-owned-change", `projection apply changed paths the writer may not publish: ${offending.join(", ")}`), { projectionStatus });
54544
+ }
54545
+ input.assertOwner();
54546
+ git(paths.worktree, ["add", "-A", "--", ...changed]);
54547
+ const title = projectionPublishTitle(base, baseSha);
54548
+ try {
54549
+ git(paths.worktree, ["commit", "--no-verify", "-q", "-m", title, "-m", `Archctx-Projection-Source: ${baseSha}`]);
54550
+ } catch (error) {
54551
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-commit-failed", `git commit failed in the writer worktree; configure a git identity (user.name, user.email) for the repository or set GIT_AUTHOR_*/GIT_COMMITTER_*: ${errorMessage2(error)}`), { projectionStatus, files: changed });
54552
+ }
54553
+ const commitSha = git(paths.worktree, ["rev-parse", "HEAD"]).trim();
54554
+ const tip = lsRemoteBranch(repositoryRoot, remote, base, env);
54555
+ if (tip === null) {
54556
+ return failed(new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-base-missing", `${remote} has no branch ${base}`), { projectionStatus, files: changed });
54557
+ }
54558
+ if (tip !== baseSha)
54559
+ continue;
54560
+ const lease = lsRemoteBranch(repositoryRoot, pushRemote, branch, env);
54561
+ let publishedCommit = commitSha;
54562
+ let pushed = false;
54563
+ if (lease !== null && carriesSameProjection(paths.worktree, lease, commitSha, baseSha)) {
54564
+ publishedCommit = lease;
54565
+ } else {
54566
+ input.assertOwner();
54567
+ remoteGit(paths.worktree, ["push", "--no-verify", `--force-with-lease=refs/heads/${branch}:${lease ?? ""}`, "--", pushRemote, `${commitSha}:refs/heads/${branch}`], env);
54568
+ pushed = true;
54569
+ }
54570
+ input.assertOwner();
54571
+ const published = await publishPullRequest(options.publisher, { worktree: paths.worktree, remote, base, branch, baseSha, commitSha: publishedCommit, files: changed });
54572
+ if (!published.ok) {
54573
+ const pendingPublish = { baseSha, branch, commitSha: publishedCommit, files: changed };
54574
+ return failed(published.failure, { projectionStatus, files: changed, publishedCommit, pushed }, { publisherPending: pendingPublish });
54575
+ }
54576
+ return finish("published", { projectionStatus, files: changed, publishedCommit, pushed, pullRequest: published.pullRequest });
54577
+ }
54578
+ 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`));
54579
+ } catch (error) {
54580
+ return failed(asWriterFailure(error));
54581
+ }
54582
+ }
54583
+ function nextProjectionWriterState(state, outcome) {
54584
+ const run = outcome.run;
54585
+ const next = { ...state, branch: run.branch, runSequence: outcome.runSequence, lastRun: run };
54586
+ if (run.result === "already-processed")
54587
+ return next;
54588
+ if (run.baseSha !== null)
54589
+ next.lastProcessedSha = run.baseSha;
54590
+ if (run.result === "published" || run.result === "up-to-date") {
54591
+ next.lastSuccessfulSha = run.baseSha;
54592
+ next.lastSuccessfulBranch = run.branch;
54593
+ next.lastSuccessfulPublisher = outcome.publisherIdentity;
54594
+ next.lastSuccessfulFiles = run.files;
54595
+ next.lastSuccessfulCommit = run.result === "published" ? run.publishedCommit : null;
54596
+ next.publisherPending = null;
54597
+ } else if (outcome.publisherPending) {
54598
+ next.publisherPending = outcome.publisherPending;
54599
+ } else if (run.result === "human-action-required") {
54600
+ next.publisherPending = null;
54601
+ }
54602
+ return next;
54603
+ }
54604
+ function projectionPublishEnvelope(run) {
54605
+ if (run.result !== "failed")
54606
+ return okEnvelope(PROJECTION_PUBLISH_REQUEST_ID, run);
54607
+ const error = run.error ?? { code: "AC_UNCLASSIFIED_FAILURE", message: "projection publish failed" };
54608
+ return {
54609
+ ...errorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error.code, error.message, error.reasonCode),
54610
+ data: run
54611
+ };
54612
+ }
54613
+ function publishHumanActions(result) {
54614
+ return result.humanActions.map((action) => ({ reasonCode: action.reasonCode, ...action.path ? { path: action.path } : {} }));
54615
+ }
54616
+ function projectionPublishTitle(base, baseSha) {
54617
+ return `chore(projection): refresh generated architecture outputs for ${base}@${baseSha.slice(0, 12)}`;
54618
+ }
54619
+ function projectionPublishBody(input) {
54620
+ const listed = input.files.slice(0, PR_BODY_MAX_LISTED_PATHS).map((path) => `- \`${path}\``);
54621
+ if (input.files.length > PR_BODY_MAX_LISTED_PATHS)
54622
+ listed.push(`- … and ${input.files.length - PR_BODY_MAX_LISTED_PATHS} more`);
54623
+ return [
54624
+ PROJECTION_PR_BODY_MARKER,
54625
+ "",
54626
+ `Base: \`${input.base}\` at \`${input.baseSha}\``,
54627
+ `Commit: \`${input.commitSha}\``,
54628
+ "",
54629
+ "Changed generated paths:",
54630
+ "",
54631
+ ...listed,
54632
+ "",
54633
+ `Generated by \`archctx projection publish\`; touches generated paths only. Verify with \`archctx projection verify-pr --base ${input.base}\`.`,
54634
+ ""
54635
+ ].join(`
54636
+ `);
54637
+ }
54638
+ async function publishPullRequest(publisher, input) {
54639
+ if (publisher.kind === "none")
54640
+ return { ok: true, pullRequest: null };
54641
+ const dir = mkdtempSync5(join23(tmpdir4(), "archctx-projection-pr-body-"));
54642
+ try {
54643
+ const bodyFile = join23(dir, "body.md");
54644
+ writeFileSync11(bodyFile, projectionPublishBody(input), { mode: 384 });
54645
+ const pullRequest = await publisher.ensurePullRequest({
54646
+ worktree: input.worktree,
54647
+ remote: input.remote,
54648
+ base: input.base,
54649
+ branch: input.branch,
54650
+ baseSha: input.baseSha,
54651
+ commitSha: input.commitSha,
54652
+ title: projectionPublishTitle(input.base, input.baseSha),
54653
+ bodyFile
54654
+ });
54655
+ return { ok: true, pullRequest: { ...pullRequest.url ? { url: pullRequest.url } : {}, ...pullRequest.number !== undefined ? { number: pullRequest.number } : {} } };
54656
+ } catch (error) {
54657
+ if (error instanceof LocalEgressPolicyError2)
54658
+ return { ok: false, failure: new ProjectionWriterFailure("AC_POLICY_VIOLATION", "egress-policy-denied", error.message) };
54659
+ return { ok: false, failure: new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-publisher-failed", `${publisher.kind} publisher failed: ${errorMessage2(error)}`) };
54660
+ } finally {
54661
+ rmSync15(dir, { recursive: true, force: true });
54662
+ }
54663
+ }
54664
+ function runProjectionPublishStatusCommand(options) {
54665
+ const env = options.env ?? process.env;
54666
+ const remote = options.remote ?? PROJECTION_WRITER_DEFAULT_REMOTE;
54667
+ const base = options.base ?? PROJECTION_WRITER_DEFAULT_BASE;
54668
+ let repositoryRoot;
54669
+ try {
54670
+ repositoryRoot = findRepositoryRoot(options.cwd);
54671
+ } catch {
54672
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, "AC_REPO_NOT_FOUND", `projection publish-status requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54673
+ }
54674
+ const invalid = refNameIssue(repositoryRoot, "--base", base) ?? remoteNameIssue(remote);
54675
+ if (invalid)
54676
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, "AC_SCHEMA_INVALID", invalid, "projection-writer-input-invalid");
54677
+ try {
54678
+ const paths = projectionWriterPaths(repositoryRoot, remote, base, env);
54679
+ const state = readProjectionWriterState(paths.state, remote, base);
54680
+ const holder = readProjectionWriterLock(paths.lock);
54681
+ const status = {
54682
+ schemaVersion: PROJECTION_PUBLISH_STATUS_SCHEMA_VERSION,
54683
+ remote,
54684
+ base,
54685
+ branch: state.branch,
54686
+ worktreePath: paths.worktree,
54687
+ running: holder && isProcessAlive(holder.pid) ? holder : null,
54688
+ lastProcessedSha: state.lastProcessedSha,
54689
+ lastSuccessfulSha: state.lastSuccessfulSha,
54690
+ lastRun: state.lastRun
54691
+ };
54692
+ return okEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, status);
54693
+ } catch (error) {
54694
+ if (error instanceof ProjectionWriterFailure)
54695
+ return errorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, error.code, error.message, error.reasonCode);
54696
+ return publicOperationErrorEnvelope(PROJECTION_PUBLISH_STATUS_REQUEST_ID, error);
54697
+ }
54698
+ }
54699
+ async function runProjectionPublishFollow(options) {
54700
+ let runs = 0;
54701
+ while (!options.shouldStop()) {
54702
+ let envelope;
54703
+ try {
54704
+ envelope = await options.runOnce();
54705
+ } catch (error) {
54706
+ envelope = publicOperationErrorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error);
54707
+ }
54708
+ runs += 1;
54709
+ options.onResult(envelope);
54710
+ if (options.shouldStop())
54711
+ break;
54712
+ await options.sleep(options.intervalMs);
54713
+ }
54714
+ return runs;
54715
+ }
54716
+ function runProjectionVerifyPrCommand(options) {
54717
+ const requestId = PROJECTION_VERIFY_PR_REQUEST_ID;
54718
+ let root;
54719
+ try {
54720
+ root = findRepositoryRoot(options.cwd);
54721
+ } catch {
54722
+ return errorEnvelope(requestId, "AC_REPO_NOT_FOUND", `projection verify-pr requires a Git worktree at ${options.cwd}`, "git-worktree-required");
54723
+ }
54724
+ if (!isGitRevisionArgument(options.base))
54725
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", `projection verify-pr --base is not a usable revision: ${JSON.stringify(options.base)}`, "projection-verify-pr-input-invalid");
54726
+ try {
54727
+ if (!isTrackedWorktreeClean(root)) {
54728
+ 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");
54729
+ }
54730
+ let baseSha;
54731
+ let mergeBase;
54732
+ const headSha = git(root, ["rev-parse", "--verify", "HEAD^{commit}"]).trim();
54733
+ try {
54734
+ baseSha = git(root, ["rev-parse", "--verify", `${options.base}^{commit}`]).trim();
54735
+ mergeBase = git(root, ["merge-base", baseSha, headSha]).trim();
54736
+ } catch (error) {
54737
+ return errorEnvelope(requestId, "AC_PRECONDITION_FAILED", `projection verify-pr cannot resolve ${options.base} against HEAD: ${errorMessage2(error)}`, "projection-verify-pr-base-unresolved");
54738
+ }
54739
+ const changedPaths = [...new Set(git(root, ["diff", "--name-only", "-z", "--no-renames", `${mergeBase}..${headSha}`, "--"]).split("\x00").filter(Boolean))].sort();
54740
+ const verification = (fields) => okEnvelope(requestId, {
54741
+ schemaVersion: PROJECTION_PR_VERIFICATION_SCHEMA_VERSION,
54742
+ base: options.base,
54743
+ baseSha,
54744
+ headSha,
54745
+ mergeBase,
54746
+ changedPaths,
54747
+ nonGeneratedPaths: [],
54748
+ humanEditedPaths: [],
54749
+ generatedOnly: false,
54750
+ renderEquivalent: null,
54751
+ differingPaths: [],
54752
+ projectionStatus: null,
54753
+ ...fields
54754
+ });
54755
+ if (changedPaths.length === 0)
54756
+ return verification({ verdict: "no-changes" });
54757
+ const classified = classifyPullRequestPaths(root, mergeBase, headSha, changedPaths);
54758
+ if (classified.nonGeneratedPaths.length > 0) {
54759
+ return verification({ verdict: "touches-non-generated", nonGeneratedPaths: classified.nonGeneratedPaths, humanEditedPaths: classified.humanEditedPaths });
54760
+ }
54761
+ const snapshot = runProjectionSnapshotCommand(root);
54762
+ if (!snapshot.ok)
54763
+ return { ...snapshot, requestId };
54764
+ const request = {
54765
+ schemaVersion: PROJECTION_REQUEST_SCHEMA_VERSION,
54766
+ requestId: `projection_verify_pr.${headSha.slice(0, 12)}`,
54767
+ profile: "repo-harness/v1",
54768
+ mode: "check",
54769
+ targets: [...PROJECTION_WRITER_TARGETS],
54770
+ changedPaths: [],
54771
+ expected: snapshot.data.expected
54772
+ };
54773
+ const checked = (options.check ?? runProjectionCheckInvocation)(request, root);
54774
+ if (!checked.ok)
54775
+ return { ...checked, requestId };
54776
+ const result = checked.data;
54777
+ let agentContextDiffering;
54778
+ try {
54779
+ agentContextDiffering = agentContextProjectionDifferingPaths(root, classified.agentContextPaths);
54780
+ } catch {
54781
+ agentContextDiffering = [...classified.agentContextPaths];
54782
+ }
54783
+ const differingPaths = [...new Set([
54784
+ ...result.files.filter((file) => file.action !== "unchanged").map((file) => file.path),
54785
+ ...agentContextDiffering
54786
+ ])].sort();
54787
+ const renderEquivalent = result.status === "noop" && agentContextDiffering.length === 0;
54788
+ return verification({
54789
+ verdict: renderEquivalent ? "generated-only-render-equivalent" : "generated-only-not-render-equivalent",
54790
+ generatedOnly: true,
54791
+ renderEquivalent,
54792
+ differingPaths,
54793
+ projectionStatus: result.status
54794
+ });
54795
+ } catch (error) {
54796
+ return publicOperationErrorEnvelope(requestId, error);
54797
+ }
54798
+ }
54799
+ function classifyPullRequestPaths(root, mergeBase, headSha, changedPaths) {
54800
+ const baseTargets = projectionTargetsAtRevision(root, mergeBase);
54801
+ const headTargets = projectionTargetsAtRevision(root, headSha);
54802
+ const nonGenerated = [];
54803
+ const humanEdited = [];
54804
+ const agentContext = [];
54805
+ for (const path of changedPaths) {
54806
+ if (path === ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH)
54807
+ continue;
54808
+ const atBase = baseTargets.get(path);
54809
+ const atHead = headTargets.get(path);
54810
+ if (!atBase && !atHead) {
54811
+ nonGenerated.push(path);
54812
+ continue;
54813
+ }
54814
+ const before = readFileAtRevision(root, mergeBase, path);
54815
+ const after = readFileAtRevision(root, headSha, path);
54816
+ if (!atHead && after !== null) {
54817
+ nonGenerated.push(path);
54818
+ continue;
54819
+ }
54820
+ const target = {
54821
+ whole: (atBase?.whole ?? false) || (atHead?.whole ?? false),
54822
+ mixedDocument: (atBase?.mixedDocument ?? false) || (atHead?.mixedDocument ?? false),
54823
+ agentNodeIds: [...new Set([...atBase?.agentNodeIds ?? [], ...atHead?.agentNodeIds ?? []])].sort()
54824
+ };
54825
+ if (!target.mixedDocument && target.agentNodeIds.length === 0)
54826
+ continue;
54827
+ if (!mixedTargetTextUnchanged(before, after, target)) {
54828
+ humanEdited.push(path);
54829
+ nonGenerated.push(path);
54830
+ } else if (target.agentNodeIds.length > 0) {
54831
+ agentContext.push(path);
54832
+ }
54833
+ }
54834
+ return { nonGeneratedPaths: nonGenerated.sort(), humanEditedPaths: humanEdited.sort(), agentContextPaths: agentContext.sort() };
54835
+ }
54836
+ function projectionTargetsAtRevision(root, revision) {
54837
+ const targets = new Map;
54838
+ const add = (path, ownership) => {
54839
+ const known = targets.get(path) ?? { whole: false, mixedDocument: false, agentNodeIds: [] };
54840
+ targets.set(path, {
54841
+ whole: known.whole || ownership.whole === true,
54842
+ mixedDocument: known.mixedDocument || ownership.mixedDocument === true,
54843
+ agentNodeIds: [...known.agentNodeIds, ...ownership.agentNodeIds ?? []]
54844
+ });
54845
+ };
54846
+ try {
54847
+ const manifest = readFileAtRevision(root, revision, ARCHITECTURE_DOCS_PROJECTION_MANIFEST_PATH);
54848
+ const rows = manifest === null ? {} : JSON.parse(manifest).targets;
54849
+ if (rows && typeof rows === "object" && !Array.isArray(rows)) {
54850
+ for (const row of Object.values(rows)) {
54851
+ if (!row || typeof row.path !== "string")
54852
+ continue;
54853
+ add(row.path, row.ownership === "generated" ? { whole: true } : { mixedDocument: true });
54854
+ }
54855
+ }
54856
+ } catch {}
54857
+ try {
54858
+ const listed = git(root, ["ls-tree", "-r", "-z", "--name-only", revision, "--", ".archcontext/model"]).split("\x00").filter(Boolean);
54859
+ const files = new Map(listed.map((path) => [path, readFileAtRevision(root, revision, path) ?? ""]));
54860
+ for (const target of agentContextProjectionTargetPaths(loadNativeModelFromModelFiles(files)))
54861
+ add(target.path, { agentNodeIds: [target.nodeId] });
54862
+ } catch {}
54863
+ return targets;
54864
+ }
54865
+ function mixedTargetTextUnchanged(before, after, target) {
54866
+ if (before === null || after === null)
54867
+ return false;
54868
+ try {
54869
+ return JSON.stringify(textOutsideGeneratedRegions(before, target)) === JSON.stringify(textOutsideGeneratedRegions(after, target));
54870
+ } catch {
54871
+ return false;
54872
+ }
54873
+ }
54874
+ function textOutsideGeneratedRegions(body, target) {
54875
+ const text = target.agentNodeIds.length > 0 ? agentContextTextOutsideRegions(body, target.agentNodeIds) : body;
54876
+ if (!target.mixedDocument)
54877
+ return [text];
54878
+ const scan = scanDocumentRegions(text);
54879
+ if (scan.diagnostics.length > 0 || scan.generatedRegions.some((region) => !region.closed))
54880
+ throw new Error("projection-generated-regions-unbalanced");
54881
+ return scan.humanRegions.map((region) => region.text);
54882
+ }
54883
+ function readFileAtRevision(root, revision, path) {
54884
+ if (!gitSucceeds2(root, ["cat-file", "-e", `${revision}:${path}`]))
54885
+ return null;
54886
+ return git(root, ["cat-file", "blob", `${revision}:${path}`]);
54887
+ }
54888
+ function createNoneProjectionPublisher() {
54889
+ return { kind: "none", ensurePullRequest: async () => ({}) };
54890
+ }
54891
+ function createGhProjectionPublisher(options = {}) {
54892
+ const exec = options.exec ?? nodeProjectionPublisherExec;
54893
+ const admissionEnv = options.env ?? process.env;
54894
+ const timeoutMs = options.timeoutMs ?? PUBLISHER_TIMEOUT_MS;
54895
+ return {
54896
+ kind: "gh",
54897
+ async ensurePullRequest(input) {
54898
+ assertLocalEgressAllowed2("projection-publishing", admissionEnv);
54899
+ const repository = projectionPublisherRepository(input.worktree, input.remote);
54900
+ const env = { ...stringEnv(process.env), GH_PROMPT_DISABLED: "1" };
54901
+ delete env.GH_REPO;
54902
+ const gh = (args) => withLocalEgress2("projection-publishing", () => exec("gh", [...args, "--repo", repository], { cwd: input.worktree, env, timeoutMs }), admissionEnv);
54903
+ const listed = await gh(["pr", "list", "--head", input.branch, "--base", input.base, "--state", "open", "--json", "number,url", "--limit", "1"]);
54904
+ const open = parseGhPullRequestList(listed.stdout);
54905
+ if (open) {
54906
+ await gh(["pr", "edit", String(open.number), "--title", input.title, "--body-file", input.bodyFile]);
54907
+ return { number: open.number, ...open.url ? { url: open.url } : {} };
54908
+ }
54909
+ const created = await gh(["pr", "create", "--base", input.base, "--head", input.branch, "--title", input.title, "--body-file", input.bodyFile]);
54910
+ const url = created.stdout.trim().split(`
54911
+ `).map((line) => line.trim()).filter(Boolean).pop() ?? "";
54912
+ const number = /\/pull\/(\d+)(?:[/?#]|$)/.exec(url)?.[1];
54913
+ return { ...url ? { url } : {}, ...number ? { number: Number(number) } : {} };
54914
+ }
54915
+ };
54916
+ }
54917
+ function projectionWriterPushUrl(worktree, remote) {
54918
+ const urls = git(worktree, ["remote", "get-url", "--push", "--all", "--", remote]).trim().split(`
54919
+ `).filter(Boolean);
54920
+ if (urls.length !== 1) {
54921
+ throw new ProjectionWriterFailure("AC_SCHEMA_INVALID", "projection-writer-push-target-ambiguous", "projection publish requires exactly one push URL for the selected remote");
54922
+ }
54923
+ return urls[0];
54924
+ }
54925
+ function projectionPublisherRepository(worktree, remote) {
54926
+ const source = projectionWriterPushUrl(worktree, remote);
54927
+ const scp = /^[^/@:]+@([A-Za-z0-9.-]+):([^?#]+)$/.exec(source);
54928
+ let host;
54929
+ let path;
54930
+ if (scp) {
54931
+ host = scp[1];
54932
+ path = scp[2];
54933
+ } else {
54934
+ const url = new URL(source);
54935
+ if (!["https:", "ssh:"].includes(url.protocol) || url.search || url.hash || url.password || url.protocol === "https:" && url.username) {
54936
+ throw new Error("the selected remote must be an SSH or HTTPS GitHub repository URL");
54937
+ }
54938
+ host = url.protocol === "ssh:" ? url.hostname : url.host;
54939
+ path = url.pathname.replace(/^\//, "");
54940
+ }
54941
+ path = path.replace(/\.git$/, "");
54942
+ 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 === "..")) {
54943
+ throw new Error("the selected remote must identify one GitHub host, owner, and repository");
54944
+ }
54945
+ return `${host.toLowerCase()}/${path}`;
54946
+ }
54947
+ function createCommandProjectionPublisher(options) {
54948
+ const exec = options.exec ?? nodeProjectionPublisherExec;
54949
+ const admissionEnv = options.env ?? process.env;
54950
+ const sourceEnv = options.sourceEnv ?? process.env;
54951
+ const platform = options.platform ?? process.platform;
54952
+ const timeoutMs = options.timeoutMs ?? PUBLISHER_TIMEOUT_MS;
54953
+ return {
54954
+ kind: "command",
54955
+ identity: `command:${resolve30(options.command)}`,
54956
+ async ensurePullRequest(input) {
54957
+ const env = {
54958
+ ...commandPublisherBaseEnv(sourceEnv, platform),
54959
+ ARCHCTX_PUBLISH_REMOTE: input.remote,
54960
+ ARCHCTX_PUBLISH_BASE: input.base,
54961
+ ARCHCTX_PUBLISH_BRANCH: input.branch,
54962
+ ARCHCTX_PUBLISH_BASE_SHA: input.baseSha,
54963
+ ARCHCTX_PUBLISH_COMMIT: input.commitSha,
54964
+ ARCHCTX_PUBLISH_TITLE: input.title,
54965
+ ARCHCTX_PUBLISH_BODY_FILE: input.bodyFile
54966
+ };
54967
+ const { stdout } = await withLocalEgress2("projection-publishing", async () => {
54968
+ const invocation = commandPublisherInvocation(options.command, platform);
54969
+ try {
54970
+ return await exec(invocation.file, invocation.args, { cwd: input.worktree, env, timeoutMs });
54971
+ } catch (error) {
54972
+ if (invocation.interpreter !== undefined && error.code === "ENOENT") {
54973
+ throw new Error(`publisher command ${options.command} needs the interpreter "${invocation.interpreter}" from its #! line, which was not found on PATH`);
54974
+ }
54975
+ throw error;
54976
+ }
54977
+ }, admissionEnv);
54978
+ return parsePublisherCommandOutput(stdout);
54979
+ }
54980
+ };
54981
+ }
54982
+ var COMMAND_PUBLISHER_POSIX_ENV = ["PATH", "HOME"];
54983
+ var COMMAND_PUBLISHER_WINDOWS_ENV = ["PATH", "HOME", "SystemRoot", "windir", "ComSpec", "PATHEXT", "TEMP", "TMP", "USERPROFILE"];
54984
+ function commandPublisherBaseEnv(source, platform) {
54985
+ const env = {};
54986
+ if (platform !== "win32") {
54987
+ for (const name of COMMAND_PUBLISHER_POSIX_ENV) {
54988
+ const value = source[name];
54989
+ if (value)
54990
+ env[name] = value;
54991
+ }
54992
+ return env;
54993
+ }
54994
+ const allowed = new Set(COMMAND_PUBLISHER_WINDOWS_ENV.map((name) => name.toUpperCase()));
54995
+ const forwarded = new Set;
54996
+ for (const [name, value] of Object.entries(source)) {
54997
+ const key = name.toUpperCase();
54998
+ if (!value || !allowed.has(key) || forwarded.has(key))
54999
+ continue;
55000
+ forwarded.add(key);
55001
+ env[name] = value;
55002
+ }
55003
+ return env;
55004
+ }
55005
+ function commandPublisherInvocation(command, platform) {
55006
+ if (platform !== "win32")
55007
+ return { file: command, args: [] };
55008
+ const extension = extname2(command).toLowerCase();
55009
+ if (extension === ".cmd" || extension === ".bat") {
55010
+ 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`);
55011
+ }
55012
+ const firstLine = readCommandFirstLine(command);
55013
+ if (!firstLine.startsWith("#!"))
55014
+ return { file: command, args: [] };
55015
+ const tokens = firstLine.slice(2).trim().split(/\s+/).filter((token) => token !== "");
55016
+ if (tokens.length === 0)
55017
+ throw new Error(`publisher command ${command} has an empty #! line`);
55018
+ let interpreter = commandBasename(tokens[0]);
55019
+ let interpreterArgs = tokens.slice(1);
55020
+ if (interpreter === "env") {
55021
+ const next = interpreterArgs[0];
55022
+ if (next === undefined || next.startsWith("-")) {
55023
+ throw new Error(`publisher command ${command} has an unsupported #! line "${firstLine}": /usr/bin/env must be followed by an interpreter name`);
55024
+ }
55025
+ interpreter = commandBasename(next);
55026
+ interpreterArgs = interpreterArgs.slice(1);
55027
+ }
55028
+ return { file: interpreter, args: [...interpreterArgs, command], interpreter };
55029
+ }
55030
+ function commandBasename(token) {
55031
+ const name = token.split(/[\\/]/).pop() ?? token;
55032
+ return name.toLowerCase().endsWith(".exe") ? name.slice(0, -4) : name;
55033
+ }
55034
+ function readCommandFirstLine(command) {
55035
+ let fd;
55036
+ try {
55037
+ fd = openSync10(command, "r");
55038
+ } catch (error) {
55039
+ throw new Error(`publisher command ${command} cannot be read: ${error instanceof Error ? error.message : String(error)}`);
55040
+ }
55041
+ try {
55042
+ const buffer = Buffer.alloc(1024);
55043
+ const length = readSync5(fd, buffer, 0, buffer.length, 0);
55044
+ const head = buffer.subarray(0, length).toString("utf8");
55045
+ const newline = head.indexOf(`
55046
+ `);
55047
+ return (newline === -1 ? head : head.slice(0, newline)).replace(/\r$/, "");
55048
+ } finally {
55049
+ closeSync9(fd);
55050
+ }
55051
+ }
55052
+ function parseGhPullRequestList(stdout) {
55053
+ const parsed = JSON.parse(stdout.trim() === "" ? "[]" : stdout);
55054
+ if (!Array.isArray(parsed))
55055
+ throw new Error("gh pr list returned an unexpected shape");
55056
+ const first = parsed[0];
55057
+ if (!first)
55058
+ return;
55059
+ if (typeof first.number !== "number" || !Number.isInteger(first.number) || first.number <= 0)
55060
+ throw new Error("gh pr list returned a pull request without a number");
55061
+ return { number: first.number, ...typeof first.url === "string" && first.url !== "" ? { url: first.url } : {} };
55062
+ }
55063
+ function parsePublisherCommandOutput(stdout) {
55064
+ let parsed;
55065
+ try {
55066
+ parsed = JSON.parse(stdout.trim());
55067
+ } catch {
55068
+ return {};
55069
+ }
55070
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
55071
+ return {};
55072
+ const value = parsed;
55073
+ return {
55074
+ ...typeof value.url === "string" && value.url !== "" ? { url: value.url } : {},
55075
+ ...typeof value.number === "number" && Number.isInteger(value.number) && value.number > 0 ? { number: value.number } : {}
55076
+ };
55077
+ }
55078
+ var nodeProjectionPublisherExec = (file, args, options) => new Promise((resolvePromise, rejectPromise) => {
55079
+ execFile2(file, args, { cwd: options.cwd, env: options.env, timeout: options.timeoutMs, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => {
55080
+ if (error) {
55081
+ rejectPromise(Object.assign(new Error(`${[file, ...args.slice(0, 2)].join(" ")} failed: ${(stderr ?? "").trim() || error.message}`), { code: error.code }));
55082
+ return;
55083
+ }
55084
+ resolvePromise({ stdout });
55085
+ });
55086
+ });
55087
+ function stringEnv(env) {
55088
+ return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
55089
+ }
55090
+ function projectionWriterTargetIssue(repositoryRoot, input) {
55091
+ const issue = refNameIssue(repositoryRoot, "--base", input.base) ?? refNameIssue(repositoryRoot, "--branch", input.branch) ?? remoteNameIssue(input.remote);
55092
+ if (issue)
55093
+ return issue;
55094
+ if (!input.branch.startsWith(PROJECTION_WRITER_BRANCH_PREFIX))
55095
+ return `projection publish --branch must start with ${PROJECTION_WRITER_BRANCH_PREFIX}; the writer never pushes to any other ref`;
55096
+ if (input.branch === input.base)
55097
+ return "projection publish --branch must differ from --base";
55098
+ if (!gitSucceeds2(repositoryRoot, ["remote", "get-url", "--", input.remote]))
55099
+ return `projection publish --remote names no configured remote: ${input.remote}`;
55100
+ return;
55101
+ }
55102
+ function refNameIssue(repositoryRoot, flag, name) {
55103
+ if (name === "" || name.startsWith("-") || name.includes("@{") || !gitSucceeds2(repositoryRoot, ["check-ref-format", "--branch", name])) {
55104
+ return `${flag} is not a valid branch name: ${JSON.stringify(name)}`;
55105
+ }
55106
+ return;
55107
+ }
55108
+ function remoteNameIssue(remote) {
55109
+ if (remote === "" || remote.startsWith("-") || !/^[A-Za-z0-9._-]+$/.test(remote))
55110
+ return `--remote is not a valid remote name: ${JSON.stringify(remote)}`;
55111
+ return;
55112
+ }
55113
+ function acquireProjectionWriterLock(lockPath, startedAt) {
55114
+ let ownership;
55115
+ try {
55116
+ ownership = acquireLocalStoreWriterOwnership(`${lockPath}.coordination`);
55117
+ } catch (error) {
55118
+ if (error instanceof FailureError && error.reasonCode === "runtime-writer-owned") {
55119
+ const holder = readProjectionWriterLock(lockPath);
55120
+ if (holder && isProcessAlive(holder.pid))
55121
+ return { kind: "busy", pid: holder.pid };
55122
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-unavailable", "another writer holds the OS lock; its diagnostic owner record is unavailable");
55123
+ }
55124
+ throw error;
55125
+ }
55126
+ try {
55127
+ const body = readPrivateControlFile(lockPath);
55128
+ if (body === undefined && existsSync29(lockPath)) {
55129
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-unverified", "the writer owner record has unverified permissions");
55130
+ }
55131
+ const holder = body === undefined ? undefined : parseProjectionWriterLock(body);
55132
+ if (holder && isProcessAlive(holder.pid)) {
55133
+ ownership.release();
55134
+ return { kind: "busy", pid: holder.pid };
55135
+ }
55136
+ rmSync15(lockPath, { force: true });
55137
+ const record = JSON.stringify({ pid: process.pid, startedAt, token: randomUUID3() }, null, 2);
55138
+ createPrivateControlFile(lockPath, record);
55139
+ return { kind: "acquired", record, release() {
55140
+ try {
55141
+ if (readPrivateControlFile(lockPath) === record)
55142
+ rmSync15(lockPath, { force: true });
55143
+ } finally {
55144
+ ownership.release();
55145
+ }
55146
+ } };
55147
+ } catch (error) {
55148
+ ownership.release();
55149
+ throw error;
55150
+ }
55151
+ }
55152
+ function assertProjectionWriterOwner(lockPath, record) {
55153
+ if (readPrivateControlFile(lockPath) !== record) {
55154
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-lock-lost", "the writer owner record changed; publication stopped");
55155
+ }
55156
+ }
55157
+ function readProjectionWriterLock(lockPath) {
55158
+ const body = readPrivateControlFile(lockPath);
55159
+ return body === undefined ? undefined : parseProjectionWriterLock(body);
55160
+ }
55161
+ function parseProjectionWriterLock(body) {
55162
+ try {
55163
+ const value = JSON.parse(body);
55164
+ if (typeof value.pid !== "number" || !Number.isInteger(value.pid) || value.pid <= 0)
55165
+ return;
55166
+ return { pid: value.pid, startedAt: typeof value.startedAt === "string" ? value.startedAt : "" };
55167
+ } catch {
55168
+ return;
55169
+ }
55170
+ }
55171
+ function emptyProjectionWriterState(remote, base) {
55172
+ return {
55173
+ schemaVersion: PROJECTION_WRITER_STATE_SCHEMA_VERSION,
55174
+ remote,
55175
+ base,
55176
+ branch: null,
55177
+ runSequence: 0,
55178
+ lastProcessedSha: null,
55179
+ lastSuccessfulSha: null,
55180
+ lastSuccessfulCommit: null,
55181
+ lastSuccessfulBranch: null,
55182
+ lastSuccessfulPublisher: null,
55183
+ lastSuccessfulFiles: [],
55184
+ publisherPending: null,
55185
+ lastRun: null
55186
+ };
55187
+ }
55188
+ function readProjectionWriterState(path, remote, base) {
55189
+ if (!existsSync29(path))
55190
+ return emptyProjectionWriterState(remote, base);
55191
+ let value;
55192
+ try {
55193
+ value = JSON.parse(readFileSync27(path, "utf8"));
55194
+ } catch (error) {
55195
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-invalid", `projection writer state is unreadable at ${path}: ${errorMessage2(error)}`);
55196
+ }
55197
+ if (value?.schemaVersion !== PROJECTION_WRITER_STATE_SCHEMA_VERSION || value.remote !== remote || value.base !== base || typeof value.runSequence !== "number") {
55198
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-state-invalid", `projection writer state at ${path} does not belong to ${remote}/${base}`);
55199
+ }
55200
+ return { ...emptyProjectionWriterState(remote, base), ...value };
55201
+ }
55202
+ function writeProjectionWriterState(path, state) {
55203
+ const temporary = `${path}.${process.pid}.tmp`;
55204
+ writeFileSync11(temporary, `${JSON.stringify(state, null, 2)}
55205
+ `, { encoding: "utf8", mode: 384 });
55206
+ renameSync7(temporary, path);
55207
+ }
55208
+ function prepareProjectionWriterWorktree(repositoryRoot, worktree, sha) {
55209
+ git(repositoryRoot, ["worktree", "prune"]);
55210
+ if (!isRegisteredWorktree(repositoryRoot, worktree)) {
55211
+ rmSync15(worktree, { recursive: true, force: true });
55212
+ git(repositoryRoot, ["worktree", "add", "--detach", worktree, sha]);
55213
+ }
55214
+ git(worktree, ["checkout", "-q", "--force", "--detach", sha]);
55215
+ git(worktree, ["reset", "-q", "--hard", sha]);
55216
+ git(worktree, ["clean", "-ffdq"]);
55217
+ }
55218
+ function isRegisteredWorktree(repositoryRoot, worktree) {
55219
+ if (!existsSync29(join23(worktree, ".git")))
55220
+ return false;
55221
+ const wanted = canonical(worktree);
55222
+ return git(repositoryRoot, ["worktree", "list", "--porcelain", "-z"]).split("\x00").filter((line) => line.startsWith("worktree ")).some((line) => canonical(line.slice("worktree ".length)) === wanted);
55223
+ }
55224
+ function canonical(path) {
55225
+ try {
55226
+ return realpathSync12.native(path);
55227
+ } catch {
55228
+ return resolve30(path);
55229
+ }
55230
+ }
55231
+ function worktreeChangedPaths(worktree) {
55232
+ const tokens = git(worktree, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]).split("\x00");
55233
+ const paths = new Set;
55234
+ for (let index = 0;index < tokens.length; index += 1) {
55235
+ const entry = tokens[index];
55236
+ if (entry.length < 4)
55237
+ continue;
55238
+ paths.add(entry.slice(3));
55239
+ if (entry[0] === "R" || entry[0] === "C") {
55240
+ const source = tokens[index + 1];
55241
+ if (source)
55242
+ paths.add(source);
55243
+ index += 1;
55244
+ }
55245
+ }
55246
+ return [...paths].sort();
55247
+ }
55248
+ function isProjectionOwnedPath(path, patterns) {
55249
+ return patterns.some((pattern) => pattern.endsWith("/**") ? path.startsWith(pattern.slice(0, -2)) : path === pattern);
55250
+ }
55251
+ function carriesSameProjection(worktree, remoteTip, commitSha, baseSha) {
55252
+ if (!gitSucceeds2(worktree, ["cat-file", "-e", `${remoteTip}^{commit}`]))
55253
+ return false;
55254
+ const parents = git(worktree, ["rev-list", "--parents", "-n", "1", remoteTip]).trim().split(/\s+/).slice(1);
55255
+ if (parents.length !== 1 || parents[0] !== baseSha)
55256
+ return false;
55257
+ return git(worktree, ["rev-parse", `${remoteTip}^{tree}`]).trim() === git(worktree, ["rev-parse", `${commitSha}^{tree}`]).trim();
55258
+ }
55259
+ function lsRemoteBranch(repositoryRoot, remote, branch, env) {
55260
+ const line = remoteGit(repositoryRoot, ["ls-remote", "--", remote, `refs/heads/${branch}`], env).split(`
55261
+ `).find((entry) => entry.endsWith(` refs/heads/${branch}`));
55262
+ return line ? line.split("\t")[0] : null;
55263
+ }
55264
+ function remoteGit(cwd, args, env) {
55265
+ return withLocalEgress2("projection-publishing", () => git(cwd, args, { GIT_TERMINAL_PROMPT: "0" }), env);
55266
+ }
55267
+ function git(cwd, args, extraEnv = {}) {
55268
+ const result = spawnSync5("git", args, {
55269
+ cwd,
55270
+ encoding: "utf8",
55271
+ env: { ...process.env, ...extraEnv },
55272
+ stdio: ["ignore", "pipe", "pipe"],
55273
+ maxBuffer: 256 * 1024 * 1024
55274
+ });
55275
+ if (result.error)
55276
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-git-failed", `git ${args[0]} could not run: ${result.error.message}`);
55277
+ if (result.status !== 0) {
55278
+ throw new ProjectionWriterFailure("AC_PRECONDITION_FAILED", "projection-writer-git-failed", `git ${args[0]} failed: ${(result.stderr ?? "").trim() || `exit ${result.status}`}`);
55279
+ }
55280
+ return result.stdout ?? "";
55281
+ }
55282
+ function gitSucceeds2(cwd, args) {
55283
+ return spawnSync5("git", args, { cwd, stdio: ["ignore", "ignore", "ignore"] }).status === 0;
55284
+ }
55285
+ function envelopeFailure(envelope, fallbackReason) {
55286
+ const error = envelope.error;
55287
+ return new ProjectionWriterFailure(error?.code ?? "AC_UNCLASSIFIED_FAILURE", error?.reasonCode ?? fallbackReason, error?.message ?? fallbackReason);
55288
+ }
55289
+ function asWriterFailure(error) {
55290
+ if (error instanceof ProjectionWriterFailure)
55291
+ return error;
55292
+ if (error instanceof LocalEgressPolicyError2)
55293
+ return new ProjectionWriterFailure("AC_POLICY_VIOLATION", "egress-policy-denied", error.message);
55294
+ const envelope = publicOperationErrorEnvelope(PROJECTION_PUBLISH_REQUEST_ID, error);
55295
+ return envelopeFailure(envelope, "projection-writer-failed");
55296
+ }
55297
+ function writerError(failure) {
55298
+ return { code: failure.code, reasonCode: failure.reasonCode, message: failure.message };
55299
+ }
55300
+ function errorMessage2(error) {
55301
+ return error instanceof Error ? error.message : String(error);
55302
+ }
54329
55303
  // packages/local-runtime/runtime-daemon/src/capability-dependencies.ts
54330
55304
  init_src();
54331
55305
  var CODEGRAPH_BINARY2 = "codegraph";
@@ -54449,8 +55423,8 @@ function runFlowSelectorCheckCommand(cwd, options = {}) {
54449
55423
  }
54450
55424
  }
54451
55425
  // packages/local-runtime/runtime-daemon/src/recommendations.ts
54452
- import { existsSync as existsSync29 } from "node:fs";
54453
- import { join as join23 } from "node:path";
55426
+ import { existsSync as existsSync30 } from "node:fs";
55427
+ import { join as join24 } from "node:path";
54454
55428
 
54455
55429
  // packages/local-runtime/runtime-daemon/src/refactor-verify.ts
54456
55430
  init_src();
@@ -55052,7 +56026,7 @@ class RecommendationsService {
55052
56026
  });
55053
56027
  }
55054
56028
  readGitOnlyDecision(repositoryRoot, recommendationId, surface) {
55055
- if (!existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
56029
+ if (!existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
55056
56030
  return;
55057
56031
  let decisions;
55058
56032
  try {
@@ -55722,7 +56696,7 @@ function lifecycleEnvelope(input) {
55722
56696
  });
55723
56697
  }
55724
56698
  function refactorDecisionsInPlay(repositoryRoot, records) {
55725
- return records.some(isRefactorRecommendation) || existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
56699
+ return records.some(isRefactorRecommendation) || existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
55726
56700
  }
55727
56701
  function ledgerAuditDecisionRefused(surface, record, recommendations, decisions) {
55728
56702
  let head = record.recommendationId;
@@ -55843,15 +56817,15 @@ function recommendationActorKind(input) {
55843
56817
  // packages/local-runtime/runtime-daemon/src/changeset-authority.ts
55844
56818
  init_src();
55845
56819
  init_src();
55846
- import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
55847
- import { resolve as resolve31 } from "node:path";
56820
+ import { existsSync as existsSync31, readFileSync as readFileSync29 } from "node:fs";
56821
+ import { resolve as resolve32 } from "node:path";
55848
56822
  init_src();
55849
56823
 
55850
56824
  // packages/local-runtime/runtime-daemon/src/committed-change-acceptance.ts
55851
56825
  init_src();
55852
56826
  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";
56827
+ 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";
56828
+ import { resolve as resolve31 } from "node:path";
55855
56829
  init_src();
55856
56830
  var SEMANTIC_MODEL_DIRECTORIES = ["nodes", "relations", "flows"];
55857
56831
  var SEMANTIC_MODEL_PATH = /^\.archcontext\/model\/(nodes|relations|flows)\/[^/]+\.ya?ml$/;
@@ -55916,7 +56890,7 @@ function readSemanticModelSnapshot(root, readEntry) {
55916
56890
  }
55917
56891
  for (const entry of entries.filter((name) => /\.ya?ml$/.test(name)).sort()) {
55918
56892
  const path = `${relativeDirectory}/${entry}`;
55919
- bodies.set(path, strictUtf8(readEntry(resolve30(absolute, entry), path), path));
56893
+ bodies.set(path, strictUtf8(readEntry(resolve31(absolute, entry), path), path));
55920
56894
  }
55921
56895
  }
55922
56896
  const hashes = new Map([...bodies].map(([path, body]) => [path, digestJson({ body })]));
@@ -55925,13 +56899,13 @@ function readSemanticModelSnapshot(root, readEntry) {
55925
56899
  function readRegularFileBytes(absolute, path) {
55926
56900
  if (!lstatSync15(absolute).isFile())
55927
56901
  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));
56902
+ const fd = openSync11(absolute, fsConstants3.O_RDONLY | (fsConstants3.O_NOFOLLOW ?? 0) | (fsConstants3.O_NONBLOCK ?? 0));
55929
56903
  try {
55930
56904
  if (!fstatSync4(fd).isFile())
55931
56905
  throw new FailureError("AC_PRECONDITION_FAILED", "semantic-model-entry-not-regular");
55932
- return readFileSync27(fd);
56906
+ return readFileSync28(fd);
55933
56907
  } finally {
55934
- closeSync9(fd);
56908
+ closeSync10(fd);
55935
56909
  }
55936
56910
  }
55937
56911
  function strictUtf8(bytes, path) {
@@ -56429,10 +57403,10 @@ class ChangeSetAuthorityService {
56429
57403
  return publicFailureEnvelope("practices.waive", "AC_SCHEMA_INVALID", "waiver-input-invalid");
56430
57404
  }
56431
57405
  const path = `.archcontext/waivers/${waiverId}.json`;
56432
- const absolute = resolve31(session.workspace.root, path);
57406
+ const absolute = resolve32(session.workspace.root, path);
56433
57407
  const body = `${JSON.stringify(waiver, null, 2)}
56434
57408
  `;
56435
- const expectedHash = existsSync30(absolute) ? digestJson({ body: readFileSync28(absolute, "utf8") }) : "missing";
57409
+ const expectedHash = existsSync31(absolute) ? digestJson({ body: readFileSync29(absolute, "utf8") }) : "missing";
56436
57410
  const draft = this.changeSetEngine.plan({
56437
57411
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
56438
57412
  base: {
@@ -56868,7 +57842,7 @@ function architectureLedgerWriteAppendsEvents(mode) {
56868
57842
  // packages/local-runtime/runtime-daemon/src/rpc-server.ts
56869
57843
  init_src();
56870
57844
  import { createServer as createServer2 } from "node:http";
56871
- import { closeSync as closeSync10, existsSync as existsSync31, mkdirSync as mkdirSync12 } from "node:fs";
57845
+ import { closeSync as closeSync11, existsSync as existsSync32, mkdirSync as mkdirSync13 } from "node:fs";
56872
57846
  import { dirname as dirname14 } from "node:path";
56873
57847
  import { randomBytes as randomBytes5 } from "node:crypto";
56874
57848
  var DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 30 * 60000;
@@ -56926,7 +57900,7 @@ class ArchctxRuntimeRpcServer {
56926
57900
  if (connection)
56927
57901
  await removePrivateControlFile(connection.connectionPath);
56928
57902
  if (this.lockFd !== undefined) {
56929
- closeSync10(this.lockFd);
57903
+ closeSync11(this.lockFd);
56930
57904
  this.lockFd = undefined;
56931
57905
  await removePrivateControlFile(this.options.lockPath ?? defaultDaemonLockPath(this.options.root ?? process.cwd()));
56932
57906
  }
@@ -56939,8 +57913,8 @@ class ArchctxRuntimeRpcServer {
56939
57913
  const root = this.options.root ?? process.cwd();
56940
57914
  const connectionPath = this.options.connectionPath ?? defaultDaemonConnectionPath(root);
56941
57915
  const lockPath = this.options.lockPath ?? defaultDaemonLockPath(root);
56942
- mkdirSync12(dirname14(connectionPath), { recursive: true });
56943
- mkdirSync12(dirname14(lockPath), { recursive: true });
57916
+ mkdirSync13(dirname14(connectionPath), { recursive: true });
57917
+ mkdirSync13(dirname14(lockPath), { recursive: true });
56944
57918
  this.lockFd = acquireDaemonLock(lockPath, root);
56945
57919
  const token = this.options.token ?? randomBytes5(18).toString("base64url");
56946
57920
  const server = createServer2((request, response) => {
@@ -56991,7 +57965,7 @@ class ArchctxRuntimeRpcServer {
56991
57965
  await removePrivateControlFile(connection.connectionPath);
56992
57966
  } finally {
56993
57967
  if (this.lockFd !== undefined)
56994
- closeSync10(this.lockFd);
57968
+ closeSync11(this.lockFd);
56995
57969
  this.lockFd = undefined;
56996
57970
  }
56997
57971
  if (connection)
@@ -57033,7 +58007,7 @@ class ArchctxRuntimeRpcServer {
57033
58007
  });
57034
58008
  if (this.server && this.connection && (this.inFlightRpcRequests > 0 || this.authorizedRequests !== requestsBeforeRemoval)) {
57035
58009
  try {
57036
- if (!existsSync31(connection.connectionPath))
58010
+ if (!existsSync32(connection.connectionPath))
57037
58011
  createPrivateControlFile(connection.connectionPath, JSON.stringify(connection, null, 2));
57038
58012
  this.idleExitPending = false;
57039
58013
  this.armIdleTimer();
@@ -57246,8 +58220,8 @@ async function readRpcRequestBody(request, limits) {
57246
58220
  }
57247
58221
 
57248
58222
  // 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";
58223
+ import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
58224
+ import { resolve as resolve33 } from "node:path";
57251
58225
  init_src();
57252
58226
 
57253
58227
  // packages/local-runtime/runtime-daemon/src/egress.ts
@@ -57283,6 +58257,8 @@ function effectiveEgressChannels2(env, live) {
57283
58257
  }
57284
58258
  if (live.updateCheckRequested)
57285
58259
  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" });
58260
+ if (live.projectionPublishRequested)
58261
+ 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
58262
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
57287
58263
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
57288
58264
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -57437,7 +58413,7 @@ class ArchctxDaemon {
57437
58413
  const model = loadNativeModelFromArchContext(root);
57438
58414
  const canonical = renderAgentContextProjection({ model, sourceDigest: digestJson({ model }), existingFiles: loadAgentContextProjectionFiles(root, model) });
57439
58415
  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) {
58416
+ if (canonicalBody === undefined || projectionOwnedWriteBody(canonicalBody, readFileSync30(resolve33(root, file.path), "utf8")) !== file.body) {
57441
58417
  throw new Error(`Root contract requires canonical marker-only content: ${file.path}`);
57442
58418
  }
57443
58419
  }
@@ -57696,7 +58672,7 @@ class ArchctxDaemon {
57696
58672
  if (this.auditRunAbortControllers.size > 0)
57697
58673
  return true;
57698
58674
  for (const [repositoryId, session] of this.sessions) {
57699
- if (!existsSync32(session.workspace.root)) {
58675
+ if (!existsSync33(session.workspace.root)) {
57700
58676
  this.sessions.delete(repositoryId);
57701
58677
  continue;
57702
58678
  }
@@ -57900,7 +58876,7 @@ class ArchctxDaemon {
57900
58876
  try {
57901
58877
  projectionDrift = completeTaskProjectionDrift(session.workspace.root);
57902
58878
  projectionFreshness = completeTaskProjectionFreshness(session.workspace.root);
57903
- if (model.valid || !existsSync32(resolve32(session.workspace.root, ".archcontext/manifest.yaml"))) {
58879
+ if (model.valid || !existsSync33(resolve33(session.workspace.root, ".archcontext/manifest.yaml"))) {
57904
58880
  architectureDebt = readCompleteTaskArchitectureDebt(session.workspace.root, practiceEnforcement);
57905
58881
  }
57906
58882
  } catch (error) {
@@ -58389,7 +59365,7 @@ class ArchctxDaemon {
58389
59365
  }
58390
59366
  async restoreRepositorySessions() {
58391
59367
  for (const record of await this.localStore.listRepositorySessions()) {
58392
- if (!record.root || !existsSync32(record.root))
59368
+ if (!record.root || !existsSync33(record.root))
58393
59369
  continue;
58394
59370
  if (repositoryFingerprint(record.root) !== record.repositoryId)
58395
59371
  continue;
@@ -58687,7 +59663,7 @@ function blockedProductionInjections(deps) {
58687
59663
  ].filter((key) => (key in deps));
58688
59664
  }
58689
59665
  function completeTaskProjectionDrift(root) {
58690
- if (!existsSync32(resolve32(root, "docs/architecture/.projection-manifest.json")))
59666
+ if (!existsSync33(resolve33(root, "docs/architecture/.projection-manifest.json")))
58691
59667
  return;
58692
59668
  const profile = loadArchitectureDocumentationProfile(root);
58693
59669
  const loaded = loadArchitectureDocumentationInputs(root, profile);
@@ -58735,9 +59711,9 @@ function completeTaskProjectionFreshness(root) {
58735
59711
  // packages/local-runtime/codegraph-adapter/src/index.ts
58736
59712
  init_src();
58737
59713
  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";
59714
+ 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
59715
  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";
59716
+ 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
59717
  init_src();
58742
59718
  var REQUIRED_CODEGRAPH_PACKAGE2 = "@colbymchenry/codegraph";
58743
59719
  var REQUIRED_CODEGRAPH_VERSION2 = productVersionManifest().runtime.codeGraph.requiredVersion;
@@ -58774,15 +59750,15 @@ function codeGraphCliInvocation2(binary, cwd, pathValue = process.env.PATH ?? ""
58774
59750
  }
58775
59751
  function resolveExecutable2(binary, cwd, pathValue) {
58776
59752
  if (binary.includes("/") || binary.includes("\\")) {
58777
- const path = isAbsolute10(binary) ? binary : join25(cwd, binary);
58778
- return existsSync33(path) ? realpathSync12.native(path) : undefined;
59753
+ const path = isAbsolute10(binary) ? binary : join26(cwd, binary);
59754
+ return existsSync34(path) ? realpathSync13.native(path) : undefined;
58779
59755
  }
58780
59756
  for (const dir of pathValue.split(delimiter2)) {
58781
59757
  if (!dir)
58782
59758
  continue;
58783
- const path = join25(dir, binary);
59759
+ const path = join26(dir, binary);
58784
59760
  if (isExecutablePath2(path))
58785
- return realpathSync12.native(path);
59761
+ return realpathSync13.native(path);
58786
59762
  }
58787
59763
  return;
58788
59764
  }
@@ -58800,12 +59776,12 @@ function isExecutablePath2(path) {
58800
59776
  function resolvePackagedCodeGraphShim2() {
58801
59777
  try {
58802
59778
  const manifestPath = requireFromAdapter2.resolve(`${REQUIRED_CODEGRAPH_PACKAGE2}/package.json`);
58803
- const manifest = JSON.parse(readFileSync30(manifestPath, "utf8"));
59779
+ const manifest = JSON.parse(readFileSync31(manifestPath, "utf8"));
58804
59780
  const relativeBin = manifest.bin?.codegraph;
58805
59781
  if (manifest.version !== REQUIRED_CODEGRAPH_VERSION2 || !relativeBin)
58806
59782
  return;
58807
- const shimPath = resolve33(dirname15(manifestPath), relativeBin);
58808
- return existsSync33(shimPath) ? realpathSync12.native(shimPath) : undefined;
59783
+ const shimPath = resolve34(dirname15(manifestPath), relativeBin);
59784
+ return existsSync34(shimPath) ? realpathSync13.native(shimPath) : undefined;
58809
59785
  } catch {
58810
59786
  return;
58811
59787
  }
@@ -58819,15 +59795,15 @@ function isNodeRuntimeScript2(path) {
58819
59795
  function readFilePrefix2(path, length) {
58820
59796
  let fd;
58821
59797
  try {
58822
- fd = openSync11(path, "r");
59798
+ fd = openSync12(path, "r");
58823
59799
  const buffer = Buffer.alloc(length);
58824
- const bytes = readSync5(fd, buffer, 0, length, 0);
59800
+ const bytes = readSync6(fd, buffer, 0, length, 0);
58825
59801
  return buffer.subarray(0, bytes).toString("utf8");
58826
59802
  } catch {
58827
59803
  return "";
58828
59804
  } finally {
58829
59805
  if (fd !== undefined)
58830
- closeSync11(fd);
59806
+ closeSync12(fd);
58831
59807
  }
58832
59808
  }
58833
59809
  var CODEGRAPH_IMPORT_NODE_OUTPUT_BYTES2 = 4 * 1024;
@@ -58865,7 +59841,7 @@ function sameRealPath2(left, right) {
58865
59841
  if (typeof left !== "string")
58866
59842
  return false;
58867
59843
  try {
58868
- return realpathSync12.native(left) === realpathSync12.native(right);
59844
+ return realpathSync13.native(left) === realpathSync13.native(right);
58869
59845
  } catch {
58870
59846
  return false;
58871
59847
  }
@@ -58955,7 +59931,7 @@ function inspectRepositoryCodeFacts2(root, options = {}) {
58955
59931
  };
58956
59932
  }
58957
59933
  function codeGraphIndexAvailable2(workspaceRoot) {
58958
- return existsSync33(join25(workspaceRoot, ".codegraph"));
59934
+ return existsSync34(join26(workspaceRoot, ".codegraph"));
58959
59935
  }
58960
59936
  var NON_DECLARATION_KINDS2 = new Set(["file", "import", "export", "parameter"]);
58961
59937
  var RUNTIME_BUILTIN_CALL_NAMES2 = new Set([
@@ -59880,6 +60856,12 @@ var AUDIT_RUN_VALUE_FLAGS = ["--task-session-id", "--reason", "--risk", "--uncer
59880
60856
  var AUDIT_RUN_BOOLEAN_FLAGS = ["--no-wait", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
59881
60857
  var AUDIT_APPROVE_VALUE_FLAGS = ["--run-id", "--confirm-public-repo", ...AUDIT_GLOBAL_VALUE_FLAGS];
59882
60858
  var AUDIT_APPROVE_BOOLEAN_FLAGS = ["--resume", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
60859
+ var PROJECTION_PUBLISH_VALUE_FLAGS = ["--base", "--branch", "--interval", "--publisher", "--publisher-command", "--remote"];
60860
+ var PROJECTION_PUBLISH_BOOLEAN_FLAGS = ["--accept-observed-major-change", "--follow"];
60861
+ var PROJECTION_PUBLISH_STATUS_VALUE_FLAGS = ["--base", "--remote"];
60862
+ var PROJECTION_VERIFY_PR_VALUE_FLAGS = ["--base"];
60863
+ var PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS = 300;
60864
+ var PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS = 30;
59883
60865
  var CLI_COMMAND_FLAGS = {
59884
60866
  debt: { value: ["--changed-path"], boolean: [] },
59885
60867
  "agent-context": { value: ["--expected-worktree-digest", "--id", "--task-session-id"], boolean: ["--approved"] },
@@ -59993,7 +60975,10 @@ var CLI_COMMAND_FLAGS = {
59993
60975
  recover: { value: ["--request-json"], boolean: [] },
59994
60976
  readback: { value: ["--request-json"], boolean: [] },
59995
60977
  snapshot: { value: [], boolean: [] },
59996
- freshness: { value: ["--changed-path"], boolean: [] }
60978
+ freshness: { value: ["--changed-path"], boolean: [] },
60979
+ publish: { value: PROJECTION_PUBLISH_VALUE_FLAGS, boolean: PROJECTION_PUBLISH_BOOLEAN_FLAGS },
60980
+ "publish-status": { value: PROJECTION_PUBLISH_STATUS_VALUE_FLAGS, boolean: [] },
60981
+ "verify-pr": { value: PROJECTION_VERIFY_PR_VALUE_FLAGS, boolean: [] }
59997
60982
  }
59998
60983
  },
59999
60984
  recommendations: {
@@ -60128,6 +61113,48 @@ var CLI_PROJECTION_FRESHNESS_HELP = {
60128
61113
  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
61114
  capability: "projection-freshness-v1"
60130
61115
  };
61116
+ var CLI_PROJECTION_PUBLISH_HELP = {
61117
+ 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]",
61118
+ 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.",
61119
+ options: {
61120
+ "--base <branch>": "The branch the projection is computed for (default: main).",
61121
+ "--remote <name>": "The remote to fetch the base from and push the rolling branch to (default: origin).",
61122
+ "--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.",
61123
+ "--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.",
61124
+ "--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?}.",
61125
+ "--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.",
61126
+ "--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.",
61127
+ "--interval <seconds>": `With --follow: seconds between runs (default ${PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS}, minimum ${PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS}).`,
61128
+ "--json": "Accepted for symmetry; output is always JSON.",
61129
+ "--help, -h": "Show help without fetching, pushing or starting a daemon."
61130
+ },
61131
+ results: "published, up-to-date, already-processed, human-action-required, busy (ok) or failed (ok: false with the run as data).",
61132
+ 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).",
61133
+ capability: "projection-publish-v1"
61134
+ };
61135
+ var CLI_PROJECTION_PUBLISH_STATUS_HELP = {
61136
+ usage: "archctx projection publish-status [--base <branch>] [--remote <name>] [--json]",
61137
+ 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.",
61138
+ options: {
61139
+ "--base <branch>": "Default: main.",
61140
+ "--remote <name>": "Default: origin.",
61141
+ "--json": "Accepted for symmetry; output is always JSON.",
61142
+ "--help, -h": "Show help without reading anything."
61143
+ },
61144
+ writes: "none; no fetch, no daemon.",
61145
+ capability: "projection-publish-v1"
61146
+ };
61147
+ var CLI_PROJECTION_VERIFY_PR_HELP = {
61148
+ usage: "archctx projection verify-pr --base <ref> [--json]",
61149
+ 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.",
61150
+ options: {
61151
+ "--base <ref>": "Required. The pull request base, for example origin/main.",
61152
+ "--json": "Accepted for symmetry; output is always JSON.",
61153
+ "--help, -h": "Show help without reading the repository."
61154
+ },
61155
+ 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.",
61156
+ capability: "projection-verify-pr-v1"
61157
+ };
60131
61158
  function refactorFlagValue(args, flag) {
60132
61159
  const index = args.indexOf(flag);
60133
61160
  if (index === -1)
@@ -60264,14 +61291,14 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
60264
61291
  case "status":
60265
61292
  return (await runtime()).runtimeStatus(cwd);
60266
61293
  case "projection":
60267
- return await runProjectionProtocolCommand2(args, cwd, runtime);
61294
+ return await runProjectionProtocolCommand2(args, cwd, runtime, deps);
60268
61295
  case "flows":
60269
61296
  return runFlowsCommand(args, cwd);
60270
61297
  case "repo": {
60271
61298
  const subcommand = args[0] ?? "list";
60272
61299
  if (subcommand === "add") {
60273
61300
  const root = readFlag(args, "--root") ?? cwd;
60274
- if (resolve34(root) !== resolve34(cwd)) {
61301
+ if (resolve35(root) !== resolve35(cwd)) {
60275
61302
  return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
60276
61303
  }
60277
61304
  const daemon = await runtime();
@@ -60615,6 +61642,12 @@ function cliHelpEnvelope(command, args) {
60615
61642
  case "projection":
60616
61643
  if (subcommand === "freshness")
60617
61644
  return okEnvelope("help", CLI_PROJECTION_FRESHNESS_HELP);
61645
+ if (subcommand === "publish")
61646
+ return okEnvelope("help", CLI_PROJECTION_PUBLISH_HELP);
61647
+ if (subcommand === "publish-status")
61648
+ return okEnvelope("help", CLI_PROJECTION_PUBLISH_STATUS_HELP);
61649
+ if (subcommand === "verify-pr")
61650
+ return okEnvelope("help", CLI_PROJECTION_VERIFY_PR_HELP);
60618
61651
  break;
60619
61652
  case "daemon":
60620
61653
  return okEnvelope("daemon", {
@@ -61331,8 +62364,10 @@ async function runAgentContextProjectionCommand2(args, cwd, daemon) {
61331
62364
  expectedWorktreeDigest: readFlag(args, "--expected-worktree-digest")
61332
62365
  });
61333
62366
  }
61334
- async function runProjectionProtocolCommand2(args, cwd, runtime) {
62367
+ async function runProjectionProtocolCommand2(args, cwd, runtime, deps) {
61335
62368
  const action = args[0] ?? "run";
62369
+ if (action === "publish" || action === "publish-status" || action === "verify-pr")
62370
+ return runProjectionWriterCliCommand(action, args.slice(1), cwd, deps);
61336
62371
  if (action === "snapshot") {
61337
62372
  const unexpected = args.slice(1).filter((arg) => arg !== "--json");
61338
62373
  if (unexpected.length > 0)
@@ -61342,7 +62377,7 @@ async function runProjectionProtocolCommand2(args, cwd, runtime) {
61342
62377
  if (action === "freshness")
61343
62378
  return runProjectionFreshnessCliCommand(args.slice(1), cwd);
61344
62379
  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]");
62380
+ return errorEnvelope("projection", "AC_SCHEMA_INVALID", "projection requires run|recover|readback --request-json <request>, snapshot|freshness [--json], or publish|publish-status|verify-pr");
61346
62381
  const raw = readFlag(args, "--request-json");
61347
62382
  if (!raw)
61348
62383
  return errorEnvelope(`projection.${action}`, "AC_SCHEMA_INVALID", `projection ${action} requires --request-json`);
@@ -61379,6 +62414,145 @@ function runProjectionFreshnessCliCommand(args, cwd) {
61379
62414
  }
61380
62415
  return runProjectionFreshnessCommand(cwd, changedPaths.length > 0 ? { changedPaths } : {});
61381
62416
  }
62417
+ async function runProjectionWriterCliCommand(action, args, cwd, deps) {
62418
+ const requestId = `projection.${action}`;
62419
+ const valueFlags = action === "publish" ? PROJECTION_PUBLISH_VALUE_FLAGS : action === "publish-status" ? PROJECTION_PUBLISH_STATUS_VALUE_FLAGS : PROJECTION_VERIFY_PR_VALUE_FLAGS;
62420
+ const stray = projectionWriterStrayToken(args, valueFlags);
62421
+ if (stray !== undefined)
62422
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", `projection ${action} does not accept ${stray}; use --help`, "cli-input-invalid");
62423
+ if (action === "verify-pr") {
62424
+ const base = readFlag(args, "--base");
62425
+ if (!base)
62426
+ return errorEnvelope(requestId, "AC_SCHEMA_INVALID", "projection verify-pr requires --base <ref>", "cli-input-invalid");
62427
+ return runProjectionVerifyPrCommand({ cwd, base });
62428
+ }
62429
+ if (action === "publish-status") {
62430
+ return runProjectionPublishStatusCommand({ cwd, base: readFlag(args, "--base"), remote: readFlag(args, "--remote") });
62431
+ }
62432
+ const parsed = parseProjectionPublishCliInput(args, cwd);
62433
+ if (!parsed.ok)
62434
+ return parsed.envelope;
62435
+ if (parsed.input.follow) {
62436
+ 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");
62437
+ }
62438
+ return runProjectionPublishOnce(parsed.input, cwd, deps);
62439
+ }
62440
+ function projectionWriterStrayToken(args, valueFlags) {
62441
+ for (let index = 0;index < args.length; index += 1) {
62442
+ const token = args[index];
62443
+ if (valueFlags.includes(token) || token === "--format") {
62444
+ index += 1;
62445
+ continue;
62446
+ }
62447
+ if (isCliFlagToken(token))
62448
+ continue;
62449
+ return token;
62450
+ }
62451
+ return;
62452
+ }
62453
+ function parseProjectionPublishCliInput(args, cwd) {
62454
+ const invalid = (message) => ({ ok: false, envelope: errorEnvelope("projection.publish", "AC_SCHEMA_INVALID", message, "cli-input-invalid") });
62455
+ const publisherKind = readFlag(args, "--publisher") ?? "gh";
62456
+ const publisherCommand = readFlag(args, "--publisher-command");
62457
+ const follow = args.includes("--follow");
62458
+ const intervalFlag = readFlag(args, "--interval");
62459
+ if (intervalFlag !== undefined && !follow)
62460
+ return invalid("projection publish --interval requires --follow");
62461
+ const intervalSeconds = intervalFlag === undefined ? PROJECTION_PUBLISH_DEFAULT_INTERVAL_SECONDS : Number(intervalFlag);
62462
+ if (!/^\d+$/.test(intervalFlag ?? "0") || !Number.isSafeInteger(intervalSeconds) || intervalSeconds < PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS) {
62463
+ return invalid(`projection publish --interval must be an integer number of seconds, at least ${PROJECTION_PUBLISH_MIN_INTERVAL_SECONDS}`);
62464
+ }
62465
+ let publisher;
62466
+ if (publisherKind === "gh" || publisherKind === "none") {
62467
+ if (publisherCommand !== undefined)
62468
+ return invalid("projection publish --publisher-command requires --publisher command");
62469
+ publisher = publisherKind === "gh" ? createGhProjectionPublisher() : createNoneProjectionPublisher();
62470
+ } else if (publisherKind === "command") {
62471
+ if (!publisherCommand)
62472
+ return invalid("projection publish --publisher command requires --publisher-command <executable>");
62473
+ const command = resolve35(cwd, publisherCommand);
62474
+ if (!existsSync35(command) || !statSync11(command).isFile())
62475
+ return invalid(`projection publish --publisher-command is not a file: ${command}`);
62476
+ publisher = createCommandProjectionPublisher({ command });
62477
+ } else {
62478
+ return invalid("projection publish --publisher must be gh, command or none");
62479
+ }
62480
+ return {
62481
+ ok: true,
62482
+ input: {
62483
+ remote: readFlag(args, "--remote"),
62484
+ base: readFlag(args, "--base"),
62485
+ branch: readFlag(args, "--branch"),
62486
+ publisher,
62487
+ acceptObservedMajorChange: args.includes("--accept-observed-major-change"),
62488
+ follow,
62489
+ intervalSeconds
62490
+ }
62491
+ };
62492
+ }
62493
+ function runProjectionPublishOnce(input, cwd, deps) {
62494
+ const clients = new Map;
62495
+ return runProjectionPublishCommand({
62496
+ cwd,
62497
+ remote: input.remote,
62498
+ base: input.base,
62499
+ branch: input.branch,
62500
+ publisher: input.publisher,
62501
+ acceptObservedMajorChange: input.acceptObservedMajorChange,
62502
+ apply: async (worktree, request) => {
62503
+ let client = deps.runtimeClient ?? clients.get(worktree);
62504
+ if (!client) {
62505
+ client = await createOrStartRuntimeRpcClient(worktree);
62506
+ clients.set(worktree, client);
62507
+ }
62508
+ return client.projection(worktree, { action: "run", request });
62509
+ }
62510
+ });
62511
+ }
62512
+ async function runProjectionPublishFollowEntry(args, cwd) {
62513
+ const stray = projectionWriterStrayToken(args, PROJECTION_PUBLISH_VALUE_FLAGS);
62514
+ 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") };
62515
+ if (!parsed.ok) {
62516
+ process.stdout.write(`${JSON.stringify(parsed.envelope)}
62517
+ `);
62518
+ return 1;
62519
+ }
62520
+ const controller = new AbortController;
62521
+ const stop = () => controller.abort();
62522
+ process.on("SIGINT", stop);
62523
+ process.on("SIGTERM", stop);
62524
+ try {
62525
+ await runProjectionPublishFollow({
62526
+ runOnce: () => runProjectionPublishOnce(parsed.input, cwd, {}),
62527
+ intervalMs: parsed.input.intervalSeconds * 1000,
62528
+ sleep: (ms) => sleepUnlessAborted(ms, controller.signal),
62529
+ shouldStop: () => controller.signal.aborted,
62530
+ onResult: (envelope) => {
62531
+ process.stdout.write(`${JSON.stringify(envelope)}
62532
+ `);
62533
+ }
62534
+ });
62535
+ } finally {
62536
+ process.off("SIGINT", stop);
62537
+ process.off("SIGTERM", stop);
62538
+ }
62539
+ return 0;
62540
+ }
62541
+ function sleepUnlessAborted(ms, signal) {
62542
+ return new Promise((resolvePromise) => {
62543
+ if (signal.aborted) {
62544
+ resolvePromise();
62545
+ return;
62546
+ }
62547
+ const timer = setTimeout(done, ms);
62548
+ function done() {
62549
+ clearTimeout(timer);
62550
+ signal.removeEventListener("abort", done);
62551
+ resolvePromise();
62552
+ }
62553
+ signal.addEventListener("abort", done, { once: true });
62554
+ });
62555
+ }
61382
62556
  function runFlowsCommand(args, cwd) {
61383
62557
  const formatAt = args.indexOf("--format");
61384
62558
  const unexpected = args.slice(1).filter((arg, index) => arg !== "--json" && (formatAt < 0 || index + 1 !== formatAt && index !== formatAt));
@@ -61742,12 +62916,12 @@ function auditManifestGateRoot(cwd) {
61742
62916
  }
61743
62917
  }
61744
62918
  function auditGithubIssuesEnabled(cwd) {
61745
- const manifestPath = resolve34(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
61746
- if (!existsSync34(manifestPath))
62919
+ const manifestPath = resolve35(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
62920
+ if (!existsSync35(manifestPath))
61747
62921
  return false;
61748
62922
  let raw;
61749
62923
  try {
61750
- raw = readFileSync31(manifestPath, "utf8");
62924
+ raw = readFileSync32(manifestPath, "utf8");
61751
62925
  } catch {
61752
62926
  return false;
61753
62927
  }
@@ -62032,8 +63206,8 @@ async function runGithubCommand(args, cwd, deps) {
62032
63206
  }
62033
63207
  if (subcommand === "disconnect") {
62034
63208
  const connection = await deps.githubConnectionReader?.readVerifiedConnection({ repositoryRoot: cwd });
62035
- const localRecordRemoved = existsSync34(connectionPath);
62036
- rmSync15(connectionPath, { force: true });
63209
+ const localRecordRemoved = existsSync35(connectionPath);
63210
+ rmSync16(connectionPath, { force: true });
62037
63211
  return okEnvelope("github.disconnect", {
62038
63212
  connected: connection !== undefined,
62039
63213
  localRecordRemoved,
@@ -62320,7 +63494,7 @@ async function digestReviewChallenge(challenge) {
62320
63494
  return reviewChallengeV2Digest(challenge);
62321
63495
  }
62322
63496
  function defaultGithubConnectionPath(cwd) {
62323
- return join26(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
63497
+ return join27(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
62324
63498
  }
62325
63499
  function sanitizeGithubConnection(record, connectionPath) {
62326
63500
  return {
@@ -62344,7 +63518,7 @@ async function readReviewChallengeV2Arg(args, cwd, commandName = "github verify-
62344
63518
  if (!inline && !challengePath)
62345
63519
  return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
62346
63520
  try {
62347
- const raw = inline ?? readFileSync31(resolve34(cwd, challengePath), "utf8");
63521
+ const raw = inline ?? readFileSync32(resolve35(cwd, challengePath), "utf8");
62348
63522
  const parsed = JSON.parse(raw);
62349
63523
  const attestation = await Promise.resolve().then(() => (init_src6(), {}));
62350
63524
  const assertReviewChallengeV2 = assertReviewChallengeV22;
@@ -62728,7 +63902,7 @@ function readLatestPackageVersion(env) {
62728
63902
  if (env[LATEST_VERSION_ENV]) {
62729
63903
  return { source: "env", version: env[LATEST_VERSION_ENV] };
62730
63904
  }
62731
- const result = withLocalEgress("npm-update-check", () => spawnSync5("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
63905
+ const result = withLocalEgress("npm-update-check", () => spawnSync6("npm", ["view", RELEASE_PACKAGE_NAME, "version", "--json"], {
62732
63906
  encoding: "utf8",
62733
63907
  timeout: NPM_VIEW_TIMEOUT_MS,
62734
63908
  shell: false
@@ -62822,10 +63996,10 @@ function doctorSqlite(cwd) {
62822
63996
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
62823
63997
  return {
62824
63998
  path,
62825
- exists: existsSync34(path),
63999
+ exists: existsSync35(path),
62826
64000
  migrations: productVersionManifest().runtime.sqliteMigrations,
62827
64001
  legacyPath: paths.legacyLocalStorePath,
62828
- legacyExists: existsSync34(paths.legacyLocalStorePath),
64002
+ legacyExists: existsSync35(paths.legacyLocalStorePath),
62829
64003
  legacyLocalStore
62830
64004
  };
62831
64005
  }
@@ -62846,8 +64020,8 @@ function runtimePathsReport(cwd) {
62846
64020
  ...paths,
62847
64021
  legacyLocalStore: inspectLegacyLocalStoreMigration(cwd),
62848
64022
  runtimeRepositoryId: repositoryFingerprint2(paths.repositoryRoot),
62849
- repositoryTruthDir: join26(paths.repositoryRoot, ".archcontext"),
62850
- codeGraphIndexDir: join26(paths.repositoryRoot, ".codegraph"),
64023
+ repositoryTruthDir: join27(paths.repositoryRoot, ".archcontext"),
64024
+ codeGraphIndexDir: join27(paths.repositoryRoot, ".codegraph"),
62851
64025
  npmGlobalInstallState: "forbidden",
62852
64026
  overrides: {
62853
64027
  stateRootEnv: "ARCHCONTEXT_STATE_DIR",
@@ -62856,7 +64030,7 @@ function runtimePathsReport(cwd) {
62856
64030
  };
62857
64031
  }
62858
64032
  function pathAccess(path) {
62859
- const exists = existsSync34(path);
64033
+ const exists = existsSync35(path);
62860
64034
  return {
62861
64035
  path,
62862
64036
  exists,
@@ -63097,7 +64271,7 @@ function daemonStopStateDir(args, cwd) {
63097
64271
  return { ok: false, envelope: errorEnvelope("daemon.stop", "AC_SCHEMA_INVALID", "daemon stop accepts at most one of --state-dir or --workspace-id") };
63098
64272
  }
63099
64273
  if (stateDir !== undefined)
63100
- return { ok: true, stateDir: resolve34(cwd, stateDir) };
64274
+ return { ok: true, stateDir: resolve35(cwd, stateDir) };
63101
64275
  if (workspaceId === undefined)
63102
64276
  return { ok: true };
63103
64277
  if (!/^ws\.[0-9a-f]{16}$/.test(workspaceId)) {
@@ -63133,9 +64307,9 @@ async function startBackgroundDaemon(args, cwd) {
63133
64307
  const recovery = discovered.recovery;
63134
64308
  const connectionPath = defaultDaemonConnectionPath(cwd);
63135
64309
  const controlDir = dirname16(connectionPath);
63136
- const logPath = join26(controlDir, "archctxd.log");
63137
- mkdirSync13(controlDir, { recursive: true });
63138
- const logFd = openSync12(logPath, "a", 384);
64310
+ const logPath = join27(controlDir, "archctxd.log");
64311
+ mkdirSync14(controlDir, { recursive: true });
64312
+ const logFd = openSync13(logPath, "a", 384);
63139
64313
  try {
63140
64314
  let childExit;
63141
64315
  let childError;
@@ -63175,7 +64349,7 @@ async function startBackgroundDaemon(args, cwd) {
63175
64349
  ...recoveryData(recovery)
63176
64350
  });
63177
64351
  } finally {
63178
- closeSync12(logFd);
64352
+ closeSync13(logFd);
63179
64353
  }
63180
64354
  }
63181
64355
  async function upgradeDaemon(args, cwd) {
@@ -63184,7 +64358,7 @@ async function upgradeDaemon(args, cwd) {
63184
64358
  const record = readRuntimeRpcConnectionFile(cwd);
63185
64359
  const unchanged = () => JSON.stringify(readRuntimeRpcConnectionFile(cwd)) === JSON.stringify(record);
63186
64360
  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)) {
64361
+ if (existsSync35(connectionPath) && (!record || typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0)) {
63188
64362
  return refuse("connection record cannot prove a daemon identity");
63189
64363
  }
63190
64364
  let lockOwner;
@@ -63234,9 +64408,9 @@ async function upgradeDaemon(args, cwd) {
63234
64408
  if (!stopped.ok)
63235
64409
  return refuse("live instance refused the expected identity");
63236
64410
  const deadline = Date.now() + daemonStartTimeoutMs(args);
63237
- while (existsSync34(connectionPath) || existsSync34(lockPath)) {
64411
+ while (existsSync35(connectionPath) || existsSync35(lockPath)) {
63238
64412
  const current = readRuntimeRpcConnectionFile(cwd);
63239
- if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync34(connectionPath)) {
64413
+ if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync35(connectionPath)) {
63240
64414
  return refuse("connection changed or cannot be verified during self-stop");
63241
64415
  }
63242
64416
  const owner = liveDaemonLockOwnerPid(cwd);
@@ -63256,7 +64430,7 @@ async function upgradeDaemon(args, cwd) {
63256
64430
  const started = await startBackgroundDaemon(args, cwd);
63257
64431
  return started.ok ? { ...started, requestId: "daemon.upgrade", data: { ...started.data, upgraded: false, reason: "runtime-compatible" } } : started;
63258
64432
  }
63259
- if (selfStopped ? existsSync34(connectionPath) || existsSync34(lockPath) : !unchanged() || record?.pid !== undefined && isPidAlive(record.pid) || liveDaemonLockOwnerPid(cwd) !== undefined) {
64433
+ if (selfStopped ? existsSync35(connectionPath) || existsSync35(lockPath) : !unchanged() || record?.pid !== undefined && isPidAlive(record.pid) || liveDaemonLockOwnerPid(cwd) !== undefined) {
63260
64434
  return refuse("ownership changed before replacement");
63261
64435
  }
63262
64436
  const recovery = recoverStaleDaemonControlFiles(cwd, { removeUnhealthyConnection: true });
@@ -63422,7 +64596,7 @@ function daemonStartTimeoutMs(args) {
63422
64596
  }
63423
64597
  function daemonStartFailureMessage(cwd, logPath, childPid, childExit, childError) {
63424
64598
  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);
64599
+ const lockOwnerPid = existsSync35(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
63426
64600
  const owner = lockOwnerPid === undefined || lockOwnerPid === childPid ? "" : `; ${orphanedDaemonMessage(lockOwnerPid)}`;
63427
64601
  return `archctxd did not become ready; ${childState}${owner}; log=${logPath}; logTail=${readFileTail(logPath)}`;
63428
64602
  }
@@ -63443,7 +64617,7 @@ function sleep2(ms) {
63443
64617
  }
63444
64618
  function readFileTail(path, maxBytes = 4096) {
63445
64619
  try {
63446
- const content = readFileSync31(path, "utf8");
64620
+ const content = readFileSync32(path, "utf8");
63447
64621
  return content.slice(Math.max(0, content.length - maxBytes)).replace(/\s+/g, " ").trim();
63448
64622
  } catch {
63449
64623
  return "<unavailable>";
@@ -63528,7 +64702,7 @@ function resolveCommandExitCode(result) {
63528
64702
  return 2;
63529
64703
  return 1;
63530
64704
  }
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"];
64705
+ 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
64706
  var CLI_RESOLVE_HELP = {
63533
64707
  command: "archctx resolve (--path <repo-relative path> | --paths-from <file> | --paths-from -)",
63534
64708
  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 +64711,7 @@ var CLI_RESOLVE_HELP = {
63537
64711
  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
64712
  };
63539
64713
  function loadResolveModel(cwd) {
63540
- if (!existsSync34(join26(cwd, ".archcontext/manifest.yaml"))) {
64714
+ if (!existsSync35(join27(cwd, ".archcontext/manifest.yaml"))) {
63541
64715
  throw new NativeContextError("native-context-model-missing", { path: ".archcontext/manifest.yaml" });
63542
64716
  }
63543
64717
  let model;
@@ -63593,7 +64767,7 @@ async function readResolvePathsFrom(args, cwd, deps) {
63593
64767
  return { problem: "resolve --paths-from requires a file path or - for stdin" };
63594
64768
  let content;
63595
64769
  try {
63596
- content = source === "-" ? await (deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8")) : readFileSync31(resolve34(cwd, source), "utf8");
64770
+ content = source === "-" ? await (deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8")) : readFileSync32(resolve35(cwd, source), "utf8");
63597
64771
  } catch (error) {
63598
64772
  return { problem: `resolve --paths-from ${source} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63599
64773
  }
@@ -63681,7 +64855,7 @@ function readRepositoryInputFile(cwd, flag, file) {
63681
64855
  const absolute = assertPathHasNoSymlinkSegments2(cwd, file);
63682
64856
  if (!statSync11(absolute).isFile())
63683
64857
  return { problem: `plan ${flag} ${file} is not a regular file` };
63684
- return { content: readFileSync31(absolute, "utf8") };
64858
+ return { content: readFileSync32(absolute, "utf8") };
63685
64859
  } catch (error) {
63686
64860
  return { problem: `plan ${flag} ${file} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63687
64861
  }
@@ -63699,10 +64873,10 @@ function runHashCommand(args, cwd) {
63699
64873
  } catch (error) {
63700
64874
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
63701
64875
  }
63702
- if (!existsSync34(absolute) || !statSync11(absolute).isFile()) {
64876
+ if (!existsSync35(absolute) || !statSync11(absolute).isFile()) {
63703
64877
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", `hash --path is not an existing file: ${path}`);
63704
64878
  }
63705
- return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync31(absolute, "utf8") }) });
64879
+ return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync32(absolute, "utf8") }) });
63706
64880
  }
63707
64881
  async function readCliPlanOperations(args, cwd, deps) {
63708
64882
  if (args.includes("--operations-file")) {
@@ -63712,7 +64886,7 @@ async function readCliPlanOperations(args, cwd, deps) {
63712
64886
  const file = readFlag(args, "--operations-file");
63713
64887
  if (!file)
63714
64888
  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);
64889
+ const read = file === "-" ? { content: await (deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8")) } : readRepositoryInputFile(cwd, "--operations-file", file);
63716
64890
  if ("problem" in read)
63717
64891
  return read.problem;
63718
64892
  let parsed;
@@ -63774,7 +64948,7 @@ async function readCliPlanBody(args, cwd, deps) {
63774
64948
  return { problem: "plan --body requires a value (use - to read stdin)" };
63775
64949
  if (inline !== "-")
63776
64950
  return inline;
63777
- return deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8");
64951
+ return deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8");
63778
64952
  }
63779
64953
  function readFlag(args, flag) {
63780
64954
  const index = args.indexOf(flag);
@@ -63866,6 +65040,8 @@ if (__require.main == __require.module) {
63866
65040
  await runStdioMcpLoop(stdinLines(), (line) => process.stdout.write(`${line}
63867
65041
  `), (line) => process.stderr.write(`${line}
63868
65042
  `), { runtimeResolver: (root, options) => createOrStartRuntimeRpcClient(root, options?.preserveHealthFailure) });
65043
+ } else if (command === "projection" && args[0] === "publish" && args.includes("--follow") && preflightCliArguments(command, args) === undefined) {
65044
+ process.exitCode = await runProjectionPublishFollowEntry(args.slice(1), process.cwd());
63869
65045
  } else if (command === "daemon" && args[0] === "start" && args.includes("--foreground") && preflightCliArguments(command, args) === undefined) {
63870
65046
  await runForegroundDaemon(process.cwd(), args).catch((error) => {
63871
65047
  const message = error instanceof Error ? error.message : String(error);