archctx 0.8.0 → 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
@@ -96,6 +96,7 @@ var init_schema = __esm(() => {
96
96
  AC_NODE_VERSION_UNSUPPORTED: { code: "AC_NODE_VERSION_UNSUPPORTED", severity: "error", retryable: false, action: "use-supported-node-version" },
97
97
  AC_CODEGRAPH_UNAVAILABLE: { code: "AC_CODEGRAPH_UNAVAILABLE", severity: "error", retryable: true, action: "run-diagnostics" },
98
98
  AC_CODE_FACTS_UNAVAILABLE: { code: "AC_CODE_FACTS_UNAVAILABLE", severity: "error", retryable: true, action: "codegraph-init" },
99
+ AC_CODE_FACTS_NOT_READY: { code: "AC_CODE_FACTS_NOT_READY", severity: "error", retryable: false, action: "codegraph-index" },
99
100
  AC_INDEX_STALE: { code: "AC_INDEX_STALE", severity: "warning", retryable: true, action: "sync-codefacts" },
100
101
  AC_CONTEXT_STALE: { code: "AC_CONTEXT_STALE", severity: "warning", retryable: true, action: "prepare-task-again" },
101
102
  AC_SCHEMA_INVALID: { code: "AC_SCHEMA_INVALID", severity: "error", retryable: false, action: "repair-model" },
@@ -1752,7 +1753,9 @@ var init_projection = __esm(() => {
1752
1753
  "projection-preview-v1",
1753
1754
  "projection-prior-committed-applies-v1",
1754
1755
  "projection-protocol-v2",
1756
+ "projection-publish-v1",
1755
1757
  "projection-snapshot-v1",
1758
+ "projection-verify-pr-v1",
1756
1759
  "recommendation-scan-decision-v1",
1757
1760
  "recommendation-v3",
1758
1761
  "refactor-assessment-v1",
@@ -2031,6 +2034,23 @@ function dataRecord(value) {
2031
2034
  return fields;
2032
2035
  }
2033
2036
  function localFactValue(rule, value) {
2037
+ if (rule === "codeFactsIssues") {
2038
+ if (!Array.isArray(value))
2039
+ throw new Error("Code facts issues must be a list");
2040
+ const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
2041
+ if (!Number.isSafeInteger(length) || length < 1 || length > 32 || Reflect.ownKeys(value).length !== length + 1)
2042
+ throw new Error("Code facts issues exceed their closed budget");
2043
+ return Array.from({ length }, (_, index) => {
2044
+ const entry = Object.getOwnPropertyDescriptor(value, String(index));
2045
+ if (!entry || !("value" in entry))
2046
+ throw new Error("Code facts issues must use own data");
2047
+ const fields = dataRecord(entry.value);
2048
+ const code = fields.code?.value, message = fields.message?.value;
2049
+ if (Object.keys(fields).length !== 2 || typeof code !== "string" || !Object.hasOwn(CODE_FACTS_ISSUE_MESSAGES, code) || message !== CODE_FACTS_ISSUE_MESSAGES[code])
2050
+ throw new Error("Unknown code facts diagnostic");
2051
+ return { code, message };
2052
+ });
2053
+ }
2034
2054
  if (rule === "commandSuggestion" && (value === null || typeof value === "string" && bytes(value) <= 1024 && /^archctx (recommendations |refactor verify )[^\r\n]*$/.test(value)))
2035
2055
  return value;
2036
2056
  if (rule === "publicConfirmationToken" && typeof value === "string" && bytes(value) <= 1024 && /^public:github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+:[a-f0-9]{40}:[A-Za-z0-9_.:-]+$/.test(value))
@@ -2130,7 +2150,7 @@ function nativeFactsFor(policy, input) {
2130
2150
  }
2131
2151
  const item = dataRecord(value);
2132
2152
  if (type === "issues") {
2133
- if (Object.keys(item).length !== 3 || !["entityId", "field", "invariant"].every((key) => Object.hasOwn(item, key)))
2153
+ if (!["entityId", "field", "invariant"].every((key) => Object.hasOwn(item, key)) || Object.keys(item).some((key) => !["entityId", "field", "invariant", "path"].includes(key)))
2134
2154
  throw new Error("Native issue schema is invalid");
2135
2155
  const clean = Object.fromEntries(Object.entries(item).map(([key, data]) => [key, text(data.value)]));
2136
2156
  if (Object.values(clean).some((v) => bytes(v) > 256)) {
@@ -2202,6 +2222,8 @@ function nativeFactsFor(policy, input) {
2202
2222
  }
2203
2223
  result[key] = values;
2204
2224
  }
2225
+ const sourceIssues = Array.isArray(result.issues) ? result.issues : [];
2226
+ const summarizePath = sourceIssues.some((issue) => typeof issue.path === "string");
2205
2227
  const counts = () => {
2206
2228
  if (!Number.isSafeInteger(omitted) || !Number.isSafeInteger(characters))
2207
2229
  throw new Error("Native diagnostic counts exceed schema");
@@ -2211,6 +2233,13 @@ function nativeFactsFor(policy, input) {
2211
2233
  result.omittedDiagnosticCharacters = characters;
2212
2234
  };
2213
2235
  const fits = () => {
2236
+ if (summarizePath) {
2237
+ const path = sourceIssues[0]?.path;
2238
+ if (typeof path === "string" && sourceIssues.every((issue) => issue.path === path))
2239
+ result.path = path;
2240
+ else
2241
+ delete result.path;
2242
+ }
2214
2243
  counts();
2215
2244
  const base = catalogDefaults[policy.code];
2216
2245
  const error = { code: policy.code, message: policy.message, severity: base.severity, retryable: policy.retryable ?? base.retryable, action: policy.action ?? base.action, reasonCode: Object.entries(policies).find(([, p]) => p === policy)?.[0], details: result };
@@ -2325,12 +2354,21 @@ function normalizeFailureResult(result, stage, requestId = "failure") {
2325
2354
  return toEnvelope(requestId, undefined, "failure-metadata");
2326
2355
  }
2327
2356
  }
2328
- var FAILURE_STAGES, policies, FAILURE_POLICIES, catalogDefaults, actions, bytes = (value) => new TextEncoder().encode(value).byteLength, record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), finiteNonnegative = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0, criticalKey = (key) => /id$|digest|receipt|binding|readback|lookupkey/i.test(key), localEnums, nativeEnums, nativeDiagnosticKinds, FailureError;
2357
+ var FAILURE_STAGES, CODE_FACTS_ISSUE_MESSAGES, policies, FAILURE_POLICIES, catalogDefaults, actions, bytes = (value) => new TextEncoder().encode(value).byteLength, record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), finiteNonnegative = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0, criticalKey = (key) => /id$|digest|receipt|binding|readback|lookupkey/i.test(key), localEnums, nativeEnums, nativeDiagnosticKinds, FailureError;
2329
2358
  var init_failure = __esm(() => {
2330
2359
  init_changeset_recovery();
2331
2360
  init_projection_recovery();
2332
2361
  init_schema();
2333
2362
  FAILURE_STAGES = Object.freeze(["debt", "complete", "practice-checkpoint", "cli", "mcp", "hook-checkpoint", "failure-metadata", "public-operation", "native-export", "docs-anchors", "flows-check"]);
2363
+ CODE_FACTS_ISSUE_MESSAGES = Object.freeze({
2364
+ "not-initialized": "CodeGraph repository status is not initialized",
2365
+ "version-mismatch": "CodeGraph repository status version mismatch",
2366
+ "project-path-mismatch": "CodeGraph repository project path mismatch",
2367
+ "index-incomplete": "CodeGraph repository index is not complete and compatible",
2368
+ "extraction-version-mismatch": "CodeGraph repository extraction version mismatch",
2369
+ "worktree-mismatch": "CodeGraph repository worktree mismatch",
2370
+ "pending-source-changes": "CodeGraph repository index has pending or unreported source changes"
2371
+ });
2334
2372
  policies = {
2335
2373
  "git-worktree-required": { code: "AC_REPO_NOT_FOUND", message: "This operation requires a Git worktree.", fields: ["checks"] },
2336
2374
  "model-missing": { code: "AC_MODEL_ADOPTION_REQUIRED", message: "Architecture debt requires an adopted model.", fields: ["checks"] },
@@ -2340,6 +2378,7 @@ var init_failure = __esm(() => {
2340
2378
  "projection-manifest-invalid": { code: "AC_PRECONDITION_FAILED", message: "The committed projection manifest violates its contract." },
2341
2379
  "projection-footprint-uncovered": { code: "AC_PRECONDITION_FAILED", message: "Declared source footprints lack a projection target." },
2342
2380
  "index-missing": { code: "AC_CODE_FACTS_UNAVAILABLE", message: "Required code facts have no available index. Run codegraph init." },
2381
+ "code-facts-not-ready": { code: "AC_CODE_FACTS_NOT_READY", message: "Repository code facts are not ready. Run archctx doctor --json and follow the reported action.", facts: { issues: "codeFactsIssues" } },
2343
2382
  "checkpoint-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Checkpoint arguments violate the declared input schema.", fields: ["issueCount"], action: "correct-command-arguments" },
2344
2383
  "cli-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Command arguments violate the declared input contract.", action: "correct-command-arguments" },
2345
2384
  "complete-input-invalid": { code: "AC_SCHEMA_INVALID", message: "Completion input violates the caller authority contract.", action: "correct-command-arguments" },
@@ -2348,7 +2387,7 @@ var init_failure = __esm(() => {
2348
2387
  "runtime-version-unsupported": { code: "AC_RUNTIME_VERSION_UNSUPPORTED", message: "The connected runtime version is unsupported." },
2349
2388
  "rpc-timeout": { code: "RPC_TIMEOUT", message: "The runtime RPC deadline elapsed without a response; a mutation outcome is unknown.", fields: ["method", "timeoutMs", "elapsedMs", "mutationOutcome"], action: "reconcile-before-retry" },
2350
2389
  "rpc-aborted": { code: "RPC_ABORTED", message: "The runtime RPC caller aborted without a response; a mutation outcome is unknown.", fields: ["method", "timeoutMs", "elapsedMs", "mutationOutcome"], action: "reconcile-before-retry" },
2351
- "failure-unclassified": { code: "AC_UNCLASSIFIED_FAILURE", message: "The operation failed without a classified cause. Inspect local diagnostics before retrying.", action: "inspect-diagnostics" }
2390
+ "failure-unclassified": { code: "AC_UNCLASSIFIED_FAILURE", message: "The operation failed without a classified cause. Run archctx doctor --json before retrying.", action: "inspect-diagnostics" }
2352
2391
  };
2353
2392
  Object.assign(policies, {
2354
2393
  "developer-review-run-not-owned": { code: "AC_PRECONDITION_FAILED", message: "developer-review-run-not-owned", facts: { refusal: "reviewOwnershipRefusal" } },
@@ -2585,7 +2624,7 @@ var init_failure = __esm(() => {
2585
2624
  }
2586
2625
  FAILURE_POLICIES = Object.freeze(policies);
2587
2626
  catalogDefaults = Object.fromEntries(Object.entries(ERROR_CATALOG).map(([code, value]) => [code, Object.freeze({ severity: value.severity, retryable: value.retryable, action: value.action })]));
2588
- actions = new Set([...Object.values(catalogDefaults).map((value) => value.action), "correct-command-arguments", "inspect-diagnostics", "reconcile-before-retry", ...Object.values(policies).flatMap((policy) => policy.action ? [policy.action] : [])]);
2627
+ actions = new Set([...Object.values(catalogDefaults).map((value) => value.action), "correct-command-arguments", "inspect-diagnostics", "reconcile-before-retry", "install-compatible-codegraph", ...Object.values(policies).flatMap((policy) => policy.action ? [policy.action] : [])]);
2589
2628
  localEnums = {
2590
2629
  reviewWorktreeRefusal: ["UNKNOWN", "HEAD_UNAVAILABLE", "HEAD_SHA_MISMATCH", "TREE_OID_MISMATCH", "WORKTREE_NOT_DETACHED", "WORKTREE_NOT_CLEAN"],
2591
2630
  reviewOwnershipRefusal: ["persisted-manifest-missing", "persisted-manifest-identity-mismatch", "run-id", "manifest-path", "lock-path", "persisted-manifest-mismatch", "persisted-lock-mismatch", "run-root-name", "worktree-temp-root", "codegraph-temporary-state-root", "run-root-not-a-directory", "run-root-owner-marker-missing", "run-root-owner-marker-mismatch", "worktree-temporary-root", "worktree-root", "worktree-source-root"],
@@ -2992,9 +3031,13 @@ function nativeAllowedNodeKinds(schemas) {
2992
3031
  throw new Error("canonical-node-kind-enum-unavailable");
2993
3032
  return [...kinds];
2994
3033
  }
2995
- function nativeContextModelIssues(model, schemas) {
3034
+ function nativeContextModelIssues(model, schemas, recordPaths) {
2996
3035
  const issues = [];
2997
- const add = (entityId, field, invariant) => issues.push({ entityId, field, invariant });
3036
+ const add = (record, field, invariant) => {
3037
+ const entityId = typeof record === "string" ? record : typeof record.id === "string" ? record.id : "(undeclared)";
3038
+ const path = typeof record === "string" ? undefined : recordPaths?.get(record);
3039
+ issues.push({ entityId, field, invariant, ...path === undefined ? {} : { path } });
3040
+ };
2998
3041
  const resolveSchema = nativeContextSchemaResolver(schemas);
2999
3042
  const ids = new Set;
3000
3043
  for (const [kind, records] of [["node", model.nodes], ["relation", model.relations], ["flow", model.flows]]) {
@@ -3013,14 +3056,14 @@ function nativeContextModelIssues(model, schemas) {
3013
3056
  continue;
3014
3057
  }
3015
3058
  if (typeof value === "number" && !Number.isFinite(value)) {
3016
- add(id, item.field, "non-json-number");
3059
+ add(record, item.field, "non-json-number");
3017
3060
  jsonInvalid = true;
3018
3061
  break;
3019
3062
  }
3020
3063
  if (value === null || typeof value !== "object")
3021
3064
  continue;
3022
3065
  if (active.has(value)) {
3023
- add(id, item.field, "non-json-cycle");
3066
+ add(record, item.field, "non-json-cycle");
3024
3067
  jsonInvalid = true;
3025
3068
  break;
3026
3069
  }
@@ -3028,7 +3071,7 @@ function nativeContextModelIssues(model, schemas) {
3028
3071
  continue;
3029
3072
  const prototype = Object.getPrototypeOf(value);
3030
3073
  if (Array.isArray(value) ? prototype !== Array.prototype : prototype !== Object.prototype) {
3031
- add(id, item.field, "non-json-prototype");
3074
+ add(record, item.field, "non-json-prototype");
3032
3075
  jsonInvalid = true;
3033
3076
  break;
3034
3077
  }
@@ -3041,36 +3084,35 @@ function nativeContextModelIssues(model, schemas) {
3041
3084
  continue;
3042
3085
  const schema = kind === "node" ? schemas.node : kind === "relation" ? schemas.relation : schemas.flow;
3043
3086
  for (const issue of validateJsonSchema(schema, record, { resolveSchema }).issues)
3044
- add(id, issue.path, issue.message);
3087
+ add(record, issue.path, issue.message);
3045
3088
  if (ids.has(id))
3046
- add(id, "id", "duplicate-entity-id");
3089
+ add(record, "id", "duplicate-entity-id");
3047
3090
  ids.add(id);
3048
3091
  }
3049
3092
  }
3050
3093
  if (issues.length > 0)
3051
3094
  return issues;
3052
3095
  for (const kind of ["nodes", "relations", "flows"]) {
3053
- const expected = new Set(model[kind].map((entity) => entity.id));
3054
- for (const id of expected)
3096
+ const expected = new Map(model[kind].map((entity) => [entity.id, entity]));
3097
+ for (const [id, entity] of expected)
3055
3098
  if (!Object.hasOwn(model.sourcePaths[kind], id))
3056
- add(id, `sourcePaths.${kind}`, "source-path-index-incomplete");
3099
+ add(entity, `sourcePaths.${kind}`, "source-path-index-incomplete");
3057
3100
  for (const [id, path] of Object.entries(model.sourcePaths[kind])) {
3058
3101
  if (!expected.has(id))
3059
3102
  add(id, `sourcePaths.${kind}`, "source-path-index-unknown-entity");
3060
3103
  if (!isNativeContextPath(path))
3061
- add(id, `sourcePaths.${kind}`, "path-unsafe");
3104
+ add(expected.get(id) ?? id, `sourcePaths.${kind}`, "path-unsafe");
3062
3105
  }
3063
3106
  }
3064
3107
  const nodes = new Map(model.nodes.map((node) => [node.id, node]));
3065
3108
  const checked = new Set;
3066
3109
  for (const node of model.nodes) {
3067
- const id = node.id;
3068
3110
  const chain = new Set;
3069
3111
  let cursor = node;
3070
3112
  while (cursor) {
3071
3113
  const current = cursor.id;
3072
3114
  if (chain.has(current)) {
3073
- add(id, "parent", "parent-cycle");
3115
+ add(node, "parent", "parent-cycle");
3074
3116
  break;
3075
3117
  }
3076
3118
  if (checked.has(current))
@@ -3080,7 +3122,7 @@ function nativeContextModelIssues(model, schemas) {
3080
3122
  break;
3081
3123
  const parent = nodes.get(cursor.parent);
3082
3124
  if (!parent) {
3083
- add(current, "parent", "parent-node-missing");
3125
+ add(cursor, "parent", "parent-node-missing");
3084
3126
  break;
3085
3127
  }
3086
3128
  cursor = parent;
@@ -3093,17 +3135,17 @@ function nativeContextModelIssues(model, schemas) {
3093
3135
  const source = node.source;
3094
3136
  for (const path of [...source?.include ?? [], ...source?.exclude ?? []])
3095
3137
  if (!isNativeContextPath(path))
3096
- add(node.id, "source", "path-unsafe");
3138
+ add(node, "source", "path-unsafe");
3097
3139
  for (const entry of source?.entrypoints ?? []) {
3098
3140
  if (!isNativeContextPath(entry.path))
3099
- add(node.id, "source.entrypoints.path", "path-unsafe");
3141
+ add(node, "source.entrypoints.path", "path-unsafe");
3100
3142
  for (const symbol of entry.symbols) {
3101
3143
  for (const sink of symbol.sinks) {
3102
3144
  if (!isNativeContextPath(sink.path))
3103
- add(node.id, "source.entrypoints.symbols.sinks.path", "path-unsafe");
3145
+ add(node, "source.entrypoints.symbols.sinks.path", "path-unsafe");
3104
3146
  const selector = JSON.stringify([entry.id, symbol.name, sink.id]);
3105
3147
  if (selectors.has(selector))
3106
- add(node.id, "source.entrypoints.symbols.sinks.id", "selector-ambiguous");
3148
+ add(node, "source.entrypoints.symbols.sinks.id", "selector-ambiguous");
3107
3149
  selectors.add(selector);
3108
3150
  }
3109
3151
  }
@@ -3112,43 +3154,42 @@ function nativeContextModelIssues(model, schemas) {
3112
3154
  for (const relation of model.relations) {
3113
3155
  for (const field of ["source", "target", "via"])
3114
3156
  if (relation[field] !== undefined && !nodes.has(relation[field]))
3115
- add(relation.id, field, "relation-node-missing");
3157
+ add(relation, field, "relation-node-missing");
3116
3158
  }
3117
3159
  for (const flow of model.flows) {
3118
- const id = flow.id;
3119
3160
  if (nodes.get(flow.capabilityId)?.kind !== "capability")
3120
- add(id, "capabilityId", "flow-capability-missing");
3161
+ add(flow, "capabilityId", "flow-capability-missing");
3121
3162
  if (flow.applicability !== "required")
3122
3163
  continue;
3123
3164
  const participants = flow.participants;
3124
3165
  const participantIds = new Set;
3125
3166
  for (const participant of participants) {
3126
3167
  if (participantIds.has(participant.id))
3127
- add(id, "participants.id", "participant-id-ambiguous");
3168
+ add(flow, "participants.id", "participant-id-ambiguous");
3128
3169
  participantIds.add(participant.id);
3129
3170
  if (!nodes.has(participant.nodeId))
3130
- add(id, "participants.nodeId", "participant-node-missing");
3171
+ add(flow, "participants.nodeId", "participant-node-missing");
3131
3172
  }
3132
3173
  const definitions = new Map;
3133
3174
  const outcomes = flow.outcomes;
3134
3175
  const outcomeIds = new Set;
3135
3176
  for (const outcome of outcomes) {
3136
3177
  if (outcomeIds.has(outcome.id))
3137
- add(id, "outcomes.id", "outcome-id-ambiguous");
3178
+ add(flow, "outcomes.id", "outcome-id-ambiguous");
3138
3179
  outcomeIds.add(outcome.id);
3139
3180
  if (!participantIds.has(outcome.terminal.participant))
3140
- add(id, "outcomes.terminal", "terminal-participant-missing");
3181
+ add(flow, "outcomes.terminal", "terminal-participant-missing");
3141
3182
  }
3142
3183
  for (const step of [...flow.steps, ...outcomes.flatMap((outcome) => outcome.steps)]) {
3143
3184
  if (!participantIds.has(step.from) || !participantIds.has(step.to))
3144
- add(id, "steps", "step-participant-missing");
3185
+ add(flow, "steps", "step-participant-missing");
3145
3186
  const definition = canonicalize(step);
3146
3187
  if (definitions.has(step.id) && definitions.get(step.id) !== definition)
3147
- add(id, "steps.id", "step-definition-ambiguous");
3188
+ add(flow, "steps.id", "step-definition-ambiguous");
3148
3189
  definitions.set(step.id, definition);
3149
3190
  const selector = step.evidence;
3150
3191
  if (!selectors.has(JSON.stringify([selector.entrypointId, selector.sourceSymbol, selector.sinkId])))
3151
- add(id, "steps.evidence", "selector-missing");
3192
+ add(flow, "steps.evidence", "selector-missing");
3152
3193
  }
3153
3194
  }
3154
3195
  return issues;
@@ -3324,6 +3365,10 @@ var init_ports = __esm(() => {
3324
3365
  // packages/contracts/src/practices.ts
3325
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";
3326
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
+
3327
3372
  // packages/contracts/src/projection-manifest.ts
3328
3373
  function architectureDocsProjectionManifestIssues(value) {
3329
3374
  const issues = [];
@@ -3828,7 +3873,7 @@ function compareVersionTriples(left, right) {
3828
3873
  }
3829
3874
  return 0;
3830
3875
  }
3831
- var ARCHCONTEXT_PRODUCT_NAME = "archctx", ARCHCONTEXT_PRODUCT_VERSION = "0.8.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";
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";
3832
3877
 
3833
3878
  // packages/contracts/src/refactor.ts
3834
3879
  function refactorNamespaceIssues(value, label = "product.id") {
@@ -4930,6 +4975,7 @@ var init_src = __esm(() => {
4930
4975
  init_ports();
4931
4976
  init_projection();
4932
4977
  init_projection_recovery();
4978
+ init_projection_writer();
4933
4979
  init_projection_manifest();
4934
4980
  init_refactor();
4935
4981
  init_schema();
@@ -15338,7 +15384,7 @@ var init_src6 = __esm(() => {
15338
15384
 
15339
15385
  // packages/local-runtime/egress-admission/src/index.ts
15340
15386
  var LOCAL_EGRESS_MODE_ENV = "ARCHCONTEXT_EGRESS_MODE";
15341
- 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"];
15342
15388
 
15343
15389
  class LocalEgressPolicyError extends Error {
15344
15390
  }
@@ -15748,10 +15794,10 @@ function sourceIncludeAnchors(patterns) {
15748
15794
  init_src();
15749
15795
 
15750
15796
  // packages/core/architecture-domain/src/index.ts
15751
- init_src();
15752
15797
  import { createHash as createHash2 } from "node:crypto";
15753
15798
  import { readFileSync, realpathSync, statSync } from "node:fs";
15754
15799
  import { resolve } from "node:path";
15800
+ init_src();
15755
15801
  var LANDSCAPE_FILE = ".archcontext/landscape.yaml";
15756
15802
  var ADAPTER_PROTECTED_NATIVE_FIELDS = ["evidence", "verification", "constraint", "intervention"];
15757
15803
  var DEFAULT_IGNORES = new Set([
@@ -15980,7 +16026,7 @@ function parseJsonOrStableYaml(body, path) {
15980
16026
  if (!trimmed)
15981
16027
  throw new Error(`${path}: empty model file`);
15982
16028
  if (trimmed.startsWith("{") || trimmed.startsWith("["))
15983
- return checkedJsonDecode(trimmed, path, "json-document");
16029
+ return checkedJsonOrFlowYaml(trimmed, path, "json-document");
15984
16030
  return new StableYamlParser(body, path).parse();
15985
16031
  }
15986
16032
  var ADR_FILE_PATH = /^docs\/adr\/ADR-\d{4}-.+\.md$/;
@@ -16361,7 +16407,7 @@ function parseScalar(value, path) {
16361
16407
  if (/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(value))
16362
16408
  return Number(value);
16363
16409
  if (/^["[{]/.test(value))
16364
- return checkedJsonDecode(value, path, "json-scalar");
16410
+ return checkedJsonOrFlowYaml(value, path, "json-scalar");
16365
16411
  return value;
16366
16412
  }
16367
16413
 
@@ -16379,6 +16425,54 @@ class DuplicateModelKeyError extends Error {
16379
16425
  this.name = "DuplicateModelKeyError";
16380
16426
  }
16381
16427
  }
16428
+ function checkedJsonOrFlowYaml(source, path, branch) {
16429
+ try {
16430
+ return checkedJsonDecode(source, path, branch);
16431
+ } catch (error) {
16432
+ if (!(error instanceof SyntaxError) || !(source.startsWith("{") || source.startsWith("[")))
16433
+ throw error;
16434
+ }
16435
+ return checkedFlowYamlDecode(source, path);
16436
+ }
16437
+ function checkedFlowYamlDecode(source, path) {
16438
+ const document = $parseDocument(source, { version: "1.2", schema: "core", uniqueKeys: false, prettyErrors: false });
16439
+ const invalid = (reason) => {
16440
+ throw new SyntaxError(`${path}: invalid YAML flow collection (${reason})`);
16441
+ };
16442
+ if (document.errors.length || document.warnings.length)
16443
+ invalid("parse error or warning");
16444
+ if (!$isMap(document.contents) && !$isSeq(document.contents) || !document.contents.flow)
16445
+ invalid("expected flow collection");
16446
+ const decode = (node) => {
16447
+ if (node === null)
16448
+ return null;
16449
+ if (!$isScalar(node) && !$isMap(node) && !$isSeq(node))
16450
+ return invalid("unsupported node or alias");
16451
+ if (node.anchor || node.tag)
16452
+ return invalid("anchor or explicit tag");
16453
+ if ($isScalar(node)) {
16454
+ const value = node.value;
16455
+ if (value === null || typeof value === "string" || typeof value === "boolean")
16456
+ return value;
16457
+ if (typeof value === "number" && Number.isFinite(value))
16458
+ return value;
16459
+ return invalid("non-JSON scalar");
16460
+ }
16461
+ if ($isSeq(node))
16462
+ return node.items.map(decode);
16463
+ const keys = new Set;
16464
+ return Object.fromEntries(node.items.map((pair) => {
16465
+ if (!$isScalar(pair.key))
16466
+ return invalid("non-scalar key");
16467
+ const key = String(decode(pair.key));
16468
+ if (keys.has(key))
16469
+ throw new DuplicateModelKeyError(path, "yaml-flow", key, pair.key.range[0]);
16470
+ keys.add(key);
16471
+ return [key, decode(pair.value)];
16472
+ }));
16473
+ };
16474
+ return decode(document.contents);
16475
+ }
16382
16476
  function checkedJsonDecode(source, path, branch) {
16383
16477
  let offset = 0;
16384
16478
  const whitespace = () => {
@@ -16968,6 +17062,12 @@ function renderAdrReferenceUpdate(body, references) {
16968
17062
  return rendered;
16969
17063
  }
16970
17064
  // packages/core/changeset-engine/src/index.ts
17065
+ function changeSetBaseModelBlockingErrors(result, draft) {
17066
+ const repairedPaths = new Set(draft.operations.filter((operation) => operation.op === "update_entity_fields" || operation.op === "delete_entity").map((operation) => operation.path));
17067
+ const repairedErrors = new Set((result.nodeSchemaErrors ?? []).filter((issue) => repairedPaths.has(issue.path)).map((issue) => issue.error));
17068
+ return baseModelBlockingErrors(result).filter((error) => !repairedErrors.has(error));
17069
+ }
17070
+
16971
17071
  class ChangeSetEngine {
16972
17072
  deps;
16973
17073
  states = new Map;
@@ -17190,10 +17290,16 @@ class ChangeSetEngine {
17190
17290
  }
17191
17291
  async validateModel(root, draft, deps, phase) {
17192
17292
  const result = await deps.modelStore.validateModel({ root, repositoryId: draft.reason.taskSessionId, headSha: draft.base.headSha });
17193
- const blocked = phase === "before" ? baseModelBlockingErrors(result).length > 0 : !result.valid;
17293
+ const errors = phase === "before" ? changeSetBaseModelBlockingErrors(result, draft) : result.errors;
17294
+ const blocked = phase === "before" ? errors.length > 0 : !result.valid;
17194
17295
  if (blocked) {
17195
- const errors = phase === "before" ? baseModelBlockingErrors(result) : result.errors;
17196
- throw new Error(`ChangeSet model validation failed ${phase} apply: ${errors.join("; ") || "unknown validation error"}`);
17296
+ const path = errors[0]?.match(/^(\.archcontext\/.+?): /)?.[1];
17297
+ const error = new FailureError("AC_SCHEMA_INVALID", "native-context-model-invalid", {
17298
+ invariant: `model-validation-${phase}-apply`,
17299
+ ...path ? { path } : {}
17300
+ });
17301
+ error.message = `ChangeSet model validation failed ${phase} apply: ${errors.join("; ") || "unknown validation error"}`;
17302
+ throw error;
17197
17303
  }
17198
17304
  }
17199
17305
  requireDeps() {
@@ -20916,6 +21022,16 @@ ${wrapped}`;
20916
21022
  }
20917
21023
  return `${existing.slice(0, region.start)}${wrapped}${existing.slice(region.end)}`;
20918
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
+ }
20919
21035
  function findAgentContextRegion(body, nodeId) {
20920
21036
  const startPattern = new RegExp(`<!-- BEGIN ARCHCONTEXT AGENT CONTEXT id="${escapeRegExp(nodeId)}"[^>]*-->`);
20921
21037
  const startMatch = startPattern.exec(body);
@@ -22897,12 +23013,9 @@ function isGitWorktreeError(error) {
22897
23013
  return error instanceof Error && /git|Command failed/.test(error.message);
22898
23014
  }
22899
23015
 
22900
- // packages/local-runtime/runtime-daemon/src/producer-failure.ts
22901
- init_src();
22902
-
22903
23016
  // packages/local-runtime/egress-admission/src/index.ts
22904
23017
  var LOCAL_EGRESS_MODE_ENV2 = "ARCHCONTEXT_EGRESS_MODE";
22905
- 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"];
22906
23019
 
22907
23020
  class LocalEgressPolicyError2 extends Error {
22908
23021
  }
@@ -22931,6 +23044,7 @@ function withLocalEgress2(channel, operation, env = process.env) {
22931
23044
  }
22932
23045
 
22933
23046
  // packages/local-runtime/codegraph-adapter/src/index.ts
23047
+ init_src();
22934
23048
  import { execFileSync as execFileSync2 } from "node:child_process";
22935
23049
  import { createHash as createHash5 } from "node:crypto";
22936
23050
  import { accessSync, closeSync as closeSync3, constants as fsConstants2, existsSync as existsSync6, openSync as openSync3, readFileSync as readFileSync7, readSync, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
@@ -23932,10 +24046,15 @@ class CodeGraphCliProvider {
23932
24046
  }
23933
24047
  async indexAll(workspaceRoot) {
23934
24048
  this.workspaceRoot = workspaceRoot;
23935
- if (!existsSync6(join3(workspaceRoot, ".codegraph"))) {
23936
- throw new Error(`CodeGraph index missing at ${workspaceRoot}; run codegraph init before using archctxd`);
23937
- }
24049
+ this.assertCanSync(workspaceRoot);
23938
24050
  this.run(["sync", workspaceRoot]);
24051
+ this.assertReady(workspaceRoot);
24052
+ }
24053
+ assertReady(workspaceRoot) {
24054
+ assertRepositoryCodeFactsReady(workspaceRoot, this.binary === DEFAULT_CODEGRAPH_BINARY ? {} : { binary: this.binary });
24055
+ }
24056
+ assertCanSync(workspaceRoot) {
24057
+ this.assertReady(workspaceRoot);
23939
24058
  }
23940
24059
  async buildContext(task, options) {
23941
24060
  const query = scopedTaskQuery(task, options.changedPaths ?? []);
@@ -23998,7 +24117,7 @@ class CodeGraphCliProvider {
23998
24117
  }
23999
24118
  run(args) {
24000
24119
  disableCodeGraphTelemetryByDefault();
24001
- const invocation = codeGraphCliInvocation(this.binary, this.workspaceRoot);
24120
+ const invocation = this.binary === DEFAULT_CODEGRAPH_BINARY ? packagedCodeGraphCliInvocation() : codeGraphCliInvocation(this.binary, this.workspaceRoot);
24002
24121
  try {
24003
24122
  return execFileSync2(invocation.command, [...invocation.argsPrefix, ...args], {
24004
24123
  cwd: this.workspaceRoot,
@@ -24129,8 +24248,14 @@ class CodeGraphAdapter {
24129
24248
  this.provider = provider;
24130
24249
  disableCodeGraphTelemetryByDefault();
24131
24250
  }
24251
+ assertCanSync(workspaceRoot) {
24252
+ this.assertCompatible();
24253
+ this.provider.assertCanSync?.(workspaceRoot);
24254
+ }
24132
24255
  async ensureReady(workspace) {
24133
24256
  this.assertCompatible();
24257
+ if (this.#snapshot)
24258
+ this.provider.assertReady?.(workspace.root);
24134
24259
  this.#snapshot ??= await this.sync({ workspace });
24135
24260
  return this.#snapshot;
24136
24261
  }
@@ -24594,7 +24719,7 @@ function readProjectionHeadSha(root) {
24594
24719
  function projectionCodeGraphStatusIssues(status, root, phase) {
24595
24720
  const issue = (code, text) => ({
24596
24721
  code,
24597
- message: `CodeGraph ${phase} ${text}`
24722
+ message: phase === "repository" ? CODE_FACTS_ISSUE_MESSAGES[code] : `CodeGraph ${phase} ${text}`
24598
24723
  });
24599
24724
  if (!status.initialized)
24600
24725
  return [issue("not-initialized", "status is not initialized")];
@@ -24703,7 +24828,7 @@ function inspectRepositoryCodeFacts(root, options = {}) {
24703
24828
  message: (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim().slice(0, 400)
24704
24829
  };
24705
24830
  }
24706
- const versionIssues = codeGraphVersion === REQUIRED_CODEGRAPH_VERSION ? [] : [{ code: "version-mismatch", message: `CodeGraph ${REQUIRED_CODEGRAPH_VERSION} required, got actual binary ${codeGraphVersion || "unknown"}` }];
24831
+ const versionIssues = codeGraphVersion === REQUIRED_CODEGRAPH_VERSION ? [] : [{ code: "version-mismatch", message: CODE_FACTS_ISSUE_MESSAGES["version-mismatch"] }];
24707
24832
  if (!indexPresent) {
24708
24833
  return { schemaVersion, state: "index-missing", indexPresent: false, requiredVersion: REQUIRED_CODEGRAPH_VERSION, codeGraphVersion, issues: versionIssues };
24709
24834
  }
@@ -24733,12 +24858,25 @@ function inspectRepositoryCodeFacts(root, options = {}) {
24733
24858
  current: status.index?.currentExtractionVersion ?? null
24734
24859
  },
24735
24860
  indexState: status.index?.state ?? null,
24736
- reindexRecommended: status.index?.reindexRecommended ?? null,
24861
+ reindexRecommended: issues.some((issue) => issue.code !== "pending-source-changes"),
24737
24862
  pendingRefs: status.index?.pendingRefs ?? null,
24738
24863
  pendingChanges: pending ? { added: pending.added, modified: pending.modified, removed: pending.removed } : null,
24739
24864
  issues
24740
24865
  };
24741
24866
  }
24867
+ function assertRepositoryCodeFactsReady(root, options = {}) {
24868
+ const facts = inspectRepositoryCodeFacts(root, options);
24869
+ if (facts.state === "index-missing")
24870
+ throw new ProjectionCodeFactsUnavailableError("index-missing");
24871
+ if (facts.state === "unavailable")
24872
+ throw new FailureError("AC_CODE_FACTS_UNAVAILABLE", "code-facts-unavailable", {}, { action: "inspect-diagnostics", retryable: false });
24873
+ if (facts.state === "not-ready") {
24874
+ if (facts.issues.every((issue) => issue.code === "pending-source-changes"))
24875
+ return;
24876
+ const action = facts.codeGraphVersion !== REQUIRED_CODEGRAPH_VERSION ? "install-compatible-codegraph" : "codegraph-index";
24877
+ throw new FailureError("AC_CODE_FACTS_NOT_READY", "code-facts-not-ready", { issues: facts.issues }, { action });
24878
+ }
24879
+ }
24742
24880
  function codeGraphIndexAvailable(workspaceRoot) {
24743
24881
  return existsSync6(join3(workspaceRoot, ".codegraph"));
24744
24882
  }
@@ -25061,6 +25199,7 @@ function parseExploreSymbols(output, maxSymbols) {
25061
25199
  }
25062
25200
 
25063
25201
  // packages/local-runtime/runtime-daemon/src/producer-failure.ts
25202
+ init_src();
25064
25203
  function selectedProducerData(error, key) {
25065
25204
  const descriptor = Object.getOwnPropertyDescriptor(error, key);
25066
25205
  if (!descriptor || !("value" in descriptor))
@@ -26151,12 +26290,6 @@ function isPresent2(value) {
26151
26290
  init_src();
26152
26291
  import { existsSync as existsSync7, lstatSync as lstatSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync4 } from "node:fs";
26153
26292
  import { dirname as dirname5, resolve as resolve11 } from "node:path";
26154
-
26155
- // packages/local-runtime/model-store-yaml/src/native-context.ts
26156
- init_src();
26157
- import { createHash as createHash6 } from "node:crypto";
26158
- import { constants, closeSync as closeSync4, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync4, readSync as readSync2, realpathSync as realpathSync4 } from "node:fs";
26159
- import { join as join4, resolve as resolve10 } from "node:path";
26160
26293
  // schemas/repo/architecture-node.schema.json
26161
26294
  var architecture_node_schema_default = {
26162
26295
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -26245,6 +26378,12 @@ var architecture_node_schema_default = {
26245
26378
  extensions: { type: "object", additionalProperties: true }
26246
26379
  }
26247
26380
  };
26381
+
26382
+ // packages/local-runtime/model-store-yaml/src/native-context.ts
26383
+ init_src();
26384
+ import { createHash as createHash6 } from "node:crypto";
26385
+ import { constants, closeSync as closeSync4, fstatSync as fstatSync2, lstatSync as lstatSync4, openSync as openSync4, readSync as readSync2, realpathSync as realpathSync4 } from "node:fs";
26386
+ import { join as join4, resolve as resolve10 } from "node:path";
26248
26387
  // schemas/repo/architecture-relation.schema.json
26249
26388
  var architecture_relation_schema_default = {
26250
26389
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -26751,6 +26890,7 @@ function capture(source, snapshot) {
26751
26890
  if (typeof budget !== "number" || !Number.isSafeInteger(budget) || budget <= 0)
26752
26891
  throw new NativeContextError("native-context-budget-invalid", { path: MANIFEST, field: "runtime.contextBudgetBytes" });
26753
26892
  const model = { nodes: [], relations: [], flows: [], sourcePaths: { nodes: {}, relations: {}, flows: {} } };
26893
+ const recordPaths = new Map;
26754
26894
  for (const spec of specs)
26755
26895
  if (spec.role !== "manifest") {
26756
26896
  const value = record5(parseInput(source, spec.path));
@@ -26758,10 +26898,11 @@ function capture(source, snapshot) {
26758
26898
  throw new NativeContextError("native-context-model-invalid", { path: spec.path, invariant: "entity-object-required" });
26759
26899
  const kind = spec.role === "node" ? "nodes" : spec.role === "relation" ? "relations" : "flows";
26760
26900
  model[kind].push(value);
26901
+ recordPaths.set(value, spec.path);
26761
26902
  if (typeof value.id === "string")
26762
26903
  model.sourcePaths[kind][value.id] = spec.path;
26763
26904
  }
26764
- const issues = nativeContextModelIssues(model, SCHEMAS);
26905
+ const issues = nativeContextModelIssues(model, SCHEMAS, recordPaths);
26765
26906
  if (issues.length > 0)
26766
26907
  throw new NativeContextError(issues.some((issue) => issue.invariant === "path-unsafe") ? "native-context-path-unsafe" : "native-context-model-invalid", { issues: issues.slice(0, 32), omittedDiagnosticCount: Math.max(0, issues.length - 32) });
26767
26908
  for (const kind of MODEL_KINDS)
@@ -27100,6 +27241,7 @@ class YamlModelStore {
27100
27241
  }
27101
27242
  async validateModel(workspace) {
27102
27243
  const errors = [];
27244
+ const nodeSchemaErrors = [];
27103
27245
  for (const required of [".archcontext/manifest.yaml", ".archcontext/product.yaml"]) {
27104
27246
  try {
27105
27247
  readFileSync8(resolve11(workspace.root, required), "utf8");
@@ -27109,11 +27251,25 @@ class YamlModelStore {
27109
27251
  }
27110
27252
  const files = listModelFiles(workspace.root);
27111
27253
  for (const file of files) {
27254
+ if (file.path.startsWith(".archcontext/model/") && /\.ya?ml$/.test(file.path)) {
27255
+ try {
27256
+ const value = parseJsonOrStableYaml(file.body, file.path);
27257
+ if (file.path.startsWith(".archcontext/model/nodes/")) {
27258
+ const { issues } = validateJsonSchema(architecture_node_schema_default, value);
27259
+ for (const issue of issues) {
27260
+ const error = `${file.path}: ${issue.path}: ${issue.message}`;
27261
+ errors.push(error);
27262
+ nodeSchemaErrors.push({ path: file.path, error });
27263
+ }
27264
+ continue;
27265
+ }
27266
+ } catch (error) {
27267
+ errors.push(`${file.path}: invalid model YAML: ${error instanceof Error ? error.message : String(error)}`);
27268
+ continue;
27269
+ }
27270
+ }
27112
27271
  if (!file.schemaVersion.startsWith("archcontext."))
27113
27272
  errors.push(`${file.path}: missing schemaVersion`);
27114
- if (file.path.startsWith(".archcontext/model/nodes/") && file.schemaVersion !== "archcontext.node/v2") {
27115
- errors.push(`${file.path}: expected archcontext.node/v2, got ${file.schemaVersion || "missing"}`);
27116
- }
27117
27273
  if (file.path.startsWith(".archcontext/model/flows/") && file.schemaVersion !== "archcontext.flow/v1") {
27118
27274
  errors.push(`${file.path}: expected archcontext.flow/v1, got ${file.schemaVersion || "missing"}`);
27119
27275
  }
@@ -27129,6 +27285,7 @@ class YamlModelStore {
27129
27285
  valid: errors.length === 0,
27130
27286
  errors,
27131
27287
  ...referenceErrors.length > 0 ? { referenceErrors } : {},
27288
+ ...nodeSchemaErrors.length > 0 ? { nodeSchemaErrors } : {},
27132
27289
  ...warnings.length > 0 ? { warnings } : {},
27133
27290
  modelDigest
27134
27291
  };
@@ -33740,6 +33897,19 @@ async function runAgentContextProjectionCommand(input, cwd, daemon) {
33740
33897
  throw publicOperationFailure(error);
33741
33898
  }
33742
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
+ }
33743
33913
  function buildAgentContextProjection(root) {
33744
33914
  const model = loadNativeModelFromArchContext(root);
33745
33915
  const sourceDigest = digestJson({ model });
@@ -34042,7 +34212,7 @@ function readArchitectureDebtEvidence(root, input) {
34042
34212
  capabilityByNodeId
34043
34213
  } };
34044
34214
  }
34045
- function runArchitectureDebtCommand(cwd, input = {}) {
34215
+ function runArchitectureDebtCommand(cwd, input = {}, codeFactsOptions = {}) {
34046
34216
  let root;
34047
34217
  try {
34048
34218
  root = findRepositoryRoot(cwd);
@@ -34050,6 +34220,8 @@ function runArchitectureDebtCommand(cwd, input = {}) {
34050
34220
  return failureEnvelope("debt", knownFailure("AC_REPO_NOT_FOUND", "git-worktree-required", { checks: ["ownership", "anchors"] }));
34051
34221
  }
34052
34222
  try {
34223
+ if (codeGraphIndexAvailable(root))
34224
+ assertRepositoryCodeFactsReady(root, codeFactsOptions);
34053
34225
  return okEnvelope("debt", readArchitectureDebt(root, input));
34054
34226
  } catch (error) {
34055
34227
  return selectedProducerFailureEnvelope("debt", error, "debt");
@@ -34424,6 +34596,8 @@ function effectiveEgressChannels(env, live) {
34424
34596
  }
34425
34597
  if (live.updateCheckRequested)
34426
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" });
34427
34601
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
34428
34602
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
34429
34603
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -34458,9 +34632,9 @@ function localEgressStatus(env = process.env, live = {}) {
34458
34632
  }
34459
34633
 
34460
34634
  // packages/surfaces/cli/src/main.ts
34461
- import { spawn as spawn2, spawnSync as spawnSync5 } from "child_process";
34462
- 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";
34463
- 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";
34464
34638
  import { fileURLToPath as fileURLToPath2 } from "url";
34465
34639
 
34466
34640
  // packages/contracts/src/index.ts
@@ -34475,6 +34649,7 @@ init_ownership_dependencies();
34475
34649
  init_ports();
34476
34650
  init_projection();
34477
34651
  init_projection_recovery();
34652
+ init_projection_writer();
34478
34653
  init_projection_manifest();
34479
34654
  init_refactor();
34480
34655
  init_schema();
@@ -34596,10 +34771,10 @@ function assertAllowedArchContextPath2(root, relativePath, scope) {
34596
34771
  }
34597
34772
 
34598
34773
  // packages/core/architecture-domain/src/index.ts
34599
- init_src();
34600
34774
  import { createHash as createHash9 } from "node:crypto";
34601
34775
  import { readFileSync as readFileSync14, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
34602
34776
  import { resolve as resolve18 } from "node:path";
34777
+ init_src();
34603
34778
  var DEFAULT_IGNORES2 = new Set([
34604
34779
  ".git",
34605
34780
  ".codegraph",
@@ -34818,7 +34993,10 @@ var WINDOWS_CREATE_TIMEOUT_MS = 60000;
34818
34993
  var WINDOWS_READ_TIMEOUT_MS = 1e4;
34819
34994
  var REMOVE_HANDLE_RELEASE_MARGIN_MS = 2000;
34820
34995
  var REMOVE_RETRY_DELAY_MS = 25;
34996
+ var WINDOWS_CONTROL_FILE_PHASES = ["script", "input", "parsed", "opened", "verified", "done"];
34997
+ var WINDOWS_CONTROL_FILE_PHASE_MARKER = /^archctx-acl-phase:([a-z]+)\r?$/;
34821
34998
  var WINDOWS_CONTROL_FILE_SCRIPT = String.raw`
34999
+ [Console]::Error.WriteLine('archctx-acl-phase:script')
34822
35000
  $ErrorActionPreference = 'Stop'
34823
35001
  # A pwsh -> Node/Bun -> powershell.exe launch inherits incompatible PS7 module paths.
34824
35002
  $env:PSModulePath = "$PSHOME\Modules"
@@ -34840,7 +35018,10 @@ function Test-PrivateAcl($stream, $sid) {
34840
35018
  $rule.PropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::None)
34841
35019
  }
34842
35020
  try {
34843
- $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
35021
+ $text = [Console]::In.ReadToEnd()
35022
+ [Console]::Error.WriteLine('archctx-acl-phase:input')
35023
+ $request = $text | ConvertFrom-Json
35024
+ [Console]::Error.WriteLine('archctx-acl-phase:parsed')
34844
35025
  $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
34845
35026
  if ($request.operation -eq 'create') {
34846
35027
  $acl = New-Object System.Security.AccessControl.FileSecurity
@@ -34850,18 +35031,24 @@ try {
34850
35031
  $acl.AddAccessRule($rule)
34851
35032
  $stream = New-Object System.IO.FileStream($request.path, [System.IO.FileMode]::CreateNew, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.IO.FileShare]::None, 4096, [System.IO.FileOptions]::None, $acl)
34852
35033
  $created = $true
35034
+ [Console]::Error.WriteLine('archctx-acl-phase:opened')
34853
35035
  if (-not (Test-PrivateAcl $stream $sid)) { throw 'private ACL readback failed' }
35036
+ [Console]::Error.WriteLine('archctx-acl-phase:verified')
34854
35037
  $bytes = [System.Text.Encoding]::UTF8.GetBytes([string]$request.body)
34855
35038
  $stream.Write($bytes, 0, $bytes.Length)
34856
35039
  $stream.Flush($true)
35040
+ [Console]::Error.WriteLine('archctx-acl-phase:done')
34857
35041
  $result = @{ ok = $true }
34858
35042
  } elseif ($request.operation -eq 'read') {
34859
35043
  # Deny concurrent write/delete, validate the opened file, then read that same handle.
34860
35044
  $stream = New-Object System.IO.FileStream($request.path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
35045
+ [Console]::Error.WriteLine('archctx-acl-phase:opened')
34861
35046
  if (-not (Test-PrivateAcl $stream $sid)) { throw 'private ACL readback failed' }
35047
+ [Console]::Error.WriteLine('archctx-acl-phase:verified')
34862
35048
  if ($stream.Length -gt 1048576) { throw 'control file exceeds read budget' }
34863
35049
  $reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8, $true, 4096, $true)
34864
35050
  try { $body = $reader.ReadToEnd() } finally { $reader.Dispose() }
35051
+ [Console]::Error.WriteLine('archctx-acl-phase:done')
34865
35052
  $result = @{ ok = $true; body = $body }
34866
35053
  } else { throw 'unsupported operation' }
34867
35054
  [Console]::Out.Write(($result | ConvertTo-Json -Compress))
@@ -34903,8 +35090,9 @@ function windowsControlFile(operation, path, body) {
34903
35090
  const code = ["ETIMEDOUT", "ENOENT", "EACCES", "EPERM", "ENOBUFS"].includes(String(failure?.code)) ? String(failure?.code) : "UNKNOWN";
34904
35091
  const signal = ["SIGTERM", "SIGKILL", "SIGABRT", "SIGINT"].includes(String(failure?.signal)) ? String(failure?.signal) : null;
34905
35092
  const status = typeof failure?.status === "number" && Number.isInteger(failure.status) ? failure.status : null;
35093
+ const phase = lastWindowsControlFilePhase(failure?.stderr);
34906
35094
  const elapsedMs = Math.round(performance.now() - startedAt);
34907
- throw new Error(`Windows native ACL operation failed: ${JSON.stringify({ operation, code, status, signal, elapsedMs })}`);
35095
+ throw new Error(`Windows native ACL operation failed: ${JSON.stringify({ operation, code, status, signal, phase, elapsedMs })}`);
34908
35096
  }
34909
35097
  const result = JSON.parse(output);
34910
35098
  if (!result || typeof result !== "object" || !("ok" in result))
@@ -34919,6 +35107,18 @@ function windowsControlFile(operation, path, body) {
34919
35107
  return result.body;
34920
35108
  }
34921
35109
  }
35110
+ function lastWindowsControlFilePhase(stderr) {
35111
+ let phase = "none";
35112
+ if (typeof stderr !== "string")
35113
+ return phase;
35114
+ for (const line of stderr.split(`
35115
+ `)) {
35116
+ const name = WINDOWS_CONTROL_FILE_PHASE_MARKER.exec(line)?.[1];
35117
+ if (name && WINDOWS_CONTROL_FILE_PHASES.includes(name))
35118
+ phase = name;
35119
+ }
35120
+ return phase;
35121
+ }
34922
35122
  function createPrivateControlFile(path, body) {
34923
35123
  if (process.platform === "win32") {
34924
35124
  windowsControlFile("create", path, body);
@@ -37988,7 +38188,7 @@ function readArchitectureDebtEvidence2(root, input) {
37988
38188
  capabilityByNodeId
37989
38189
  } };
37990
38190
  }
37991
- function runArchitectureDebtCommand2(cwd, input = {}) {
38191
+ function runArchitectureDebtCommand2(cwd, input = {}, codeFactsOptions = {}) {
37992
38192
  let root;
37993
38193
  try {
37994
38194
  root = findRepositoryRoot(cwd);
@@ -37996,6 +38196,8 @@ function runArchitectureDebtCommand2(cwd, input = {}) {
37996
38196
  return failureEnvelope("debt", knownFailure("AC_REPO_NOT_FOUND", "git-worktree-required", { checks: ["ownership", "anchors"] }));
37997
38197
  }
37998
38198
  try {
38199
+ if (codeGraphIndexAvailable(root))
38200
+ assertRepositoryCodeFactsReady(root, codeFactsOptions);
37999
38201
  return okEnvelope("debt", readArchitectureDebt2(root, input));
38000
38202
  } catch (error) {
38001
38203
  return selectedProducerFailureEnvelope("debt", error, "debt");
@@ -42988,7 +43190,13 @@ class PracticeCheckpointService {
42988
43190
  }
42989
43191
  async prepareUnchecked(root, task, maxBytes = 12288, maxItems = 12, taskSessionId = "task_runtime") {
42990
43192
  this.assertRunning();
43193
+ if (this.codeFacts.assertCanSync) {
43194
+ root = findRepositoryRoot(root);
43195
+ this.codeFacts.assertCanSync(root);
43196
+ }
42991
43197
  const session = await this.openSession(root);
43198
+ if (this.codeFacts.assertCanSync)
43199
+ await this.codeFacts.ensureReady(session.workspace);
42992
43200
  const result = await prepareTask({
42993
43201
  workspace: session.workspace,
42994
43202
  task,
@@ -54151,6 +54359,947 @@ function runtimeProjectionOwnedOutputDigest(projection) {
54151
54359
  files: projection.files.map((file) => ({ path: file.path, body: file.body })).sort((left, right) => left.path.localeCompare(right.path))
54152
54360
  });
54153
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
+ }
54154
55303
  // packages/local-runtime/runtime-daemon/src/capability-dependencies.ts
54155
55304
  init_src();
54156
55305
  var CODEGRAPH_BINARY2 = "codegraph";
@@ -54274,8 +55423,8 @@ function runFlowSelectorCheckCommand(cwd, options = {}) {
54274
55423
  }
54275
55424
  }
54276
55425
  // packages/local-runtime/runtime-daemon/src/recommendations.ts
54277
- import { existsSync as existsSync29 } from "node:fs";
54278
- import { join as join23 } from "node:path";
55426
+ import { existsSync as existsSync30 } from "node:fs";
55427
+ import { join as join24 } from "node:path";
54279
55428
 
54280
55429
  // packages/local-runtime/runtime-daemon/src/refactor-verify.ts
54281
55430
  init_src();
@@ -54877,7 +56026,7 @@ class RecommendationsService {
54877
56026
  });
54878
56027
  }
54879
56028
  readGitOnlyDecision(repositoryRoot, recommendationId, surface) {
54880
- if (!existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
56029
+ if (!existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY)))
54881
56030
  return;
54882
56031
  let decisions;
54883
56032
  try {
@@ -55547,7 +56696,7 @@ function lifecycleEnvelope(input) {
55547
56696
  });
55548
56697
  }
55549
56698
  function refactorDecisionsInPlay(repositoryRoot, records) {
55550
- return records.some(isRefactorRecommendation) || existsSync29(join23(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
56699
+ return records.some(isRefactorRecommendation) || existsSync30(join24(repositoryRoot, REFACTOR_DECISION_DIRECTORY));
55551
56700
  }
55552
56701
  function ledgerAuditDecisionRefused(surface, record, recommendations, decisions) {
55553
56702
  let head = record.recommendationId;
@@ -55668,15 +56817,15 @@ function recommendationActorKind(input) {
55668
56817
  // packages/local-runtime/runtime-daemon/src/changeset-authority.ts
55669
56818
  init_src();
55670
56819
  init_src();
55671
- import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
55672
- 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";
55673
56822
  init_src();
55674
56823
 
55675
56824
  // packages/local-runtime/runtime-daemon/src/committed-change-acceptance.ts
55676
56825
  init_src();
55677
56826
  import { execFileSync as execFileSync10 } from "node:child_process";
55678
- 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";
55679
- 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";
55680
56829
  init_src();
55681
56830
  var SEMANTIC_MODEL_DIRECTORIES = ["nodes", "relations", "flows"];
55682
56831
  var SEMANTIC_MODEL_PATH = /^\.archcontext\/model\/(nodes|relations|flows)\/[^/]+\.ya?ml$/;
@@ -55741,7 +56890,7 @@ function readSemanticModelSnapshot(root, readEntry) {
55741
56890
  }
55742
56891
  for (const entry of entries.filter((name) => /\.ya?ml$/.test(name)).sort()) {
55743
56892
  const path = `${relativeDirectory}/${entry}`;
55744
- bodies.set(path, strictUtf8(readEntry(resolve30(absolute, entry), path), path));
56893
+ bodies.set(path, strictUtf8(readEntry(resolve31(absolute, entry), path), path));
55745
56894
  }
55746
56895
  }
55747
56896
  const hashes = new Map([...bodies].map(([path, body]) => [path, digestJson({ body })]));
@@ -55750,13 +56899,13 @@ function readSemanticModelSnapshot(root, readEntry) {
55750
56899
  function readRegularFileBytes(absolute, path) {
55751
56900
  if (!lstatSync15(absolute).isFile())
55752
56901
  throw new FailureError("AC_PRECONDITION_FAILED", "semantic-model-entry-not-regular");
55753
- 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));
55754
56903
  try {
55755
56904
  if (!fstatSync4(fd).isFile())
55756
56905
  throw new FailureError("AC_PRECONDITION_FAILED", "semantic-model-entry-not-regular");
55757
- return readFileSync27(fd);
56906
+ return readFileSync28(fd);
55758
56907
  } finally {
55759
- closeSync9(fd);
56908
+ closeSync10(fd);
55760
56909
  }
55761
56910
  }
55762
56911
  function strictUtf8(bytes, path) {
@@ -56254,10 +57403,10 @@ class ChangeSetAuthorityService {
56254
57403
  return publicFailureEnvelope("practices.waive", "AC_SCHEMA_INVALID", "waiver-input-invalid");
56255
57404
  }
56256
57405
  const path = `.archcontext/waivers/${waiverId}.json`;
56257
- const absolute = resolve31(session.workspace.root, path);
57406
+ const absolute = resolve32(session.workspace.root, path);
56258
57407
  const body = `${JSON.stringify(waiver, null, 2)}
56259
57408
  `;
56260
- const expectedHash = existsSync30(absolute) ? digestJson({ body: readFileSync28(absolute, "utf8") }) : "missing";
57409
+ const expectedHash = existsSync31(absolute) ? digestJson({ body: readFileSync29(absolute, "utf8") }) : "missing";
56261
57410
  const draft = this.changeSetEngine.plan({
56262
57411
  id: input.id ?? `changeset.practice-waiver-${waiverId.replace(/[^A-Za-z0-9_-]/g, "-")}`,
56263
57412
  base: {
@@ -56409,7 +57558,7 @@ class ChangeSetAuthorityService {
56409
57558
  if (draft.base.worktreeDigest !== current)
56410
57559
  throw new FailureError("AC_PRECONDITION_FAILED", "apply-worktree-changed", { expectedWorktreeDigest: draft.base.worktreeDigest, actualWorktreeDigest: current });
56411
57560
  const currentModel = await this.readModelStore.validateModel(session.workspace);
56412
- const baseErrors = baseModelBlockingErrors(currentModel);
57561
+ const baseErrors = changeSetBaseModelBlockingErrors(currentModel, draft);
56413
57562
  if (baseErrors.length > 0) {
56414
57563
  throw new FailureError("AC_PRECONDITION_FAILED", "apply-model-invalid", { issueCount: baseErrors.length });
56415
57564
  }
@@ -56693,7 +57842,7 @@ function architectureLedgerWriteAppendsEvents(mode) {
56693
57842
  // packages/local-runtime/runtime-daemon/src/rpc-server.ts
56694
57843
  init_src();
56695
57844
  import { createServer as createServer2 } from "node:http";
56696
- 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";
56697
57846
  import { dirname as dirname14 } from "node:path";
56698
57847
  import { randomBytes as randomBytes5 } from "node:crypto";
56699
57848
  var DEFAULT_DAEMON_IDLE_TIMEOUT_MS = 30 * 60000;
@@ -56751,7 +57900,7 @@ class ArchctxRuntimeRpcServer {
56751
57900
  if (connection)
56752
57901
  await removePrivateControlFile(connection.connectionPath);
56753
57902
  if (this.lockFd !== undefined) {
56754
- closeSync10(this.lockFd);
57903
+ closeSync11(this.lockFd);
56755
57904
  this.lockFd = undefined;
56756
57905
  await removePrivateControlFile(this.options.lockPath ?? defaultDaemonLockPath(this.options.root ?? process.cwd()));
56757
57906
  }
@@ -56764,8 +57913,8 @@ class ArchctxRuntimeRpcServer {
56764
57913
  const root = this.options.root ?? process.cwd();
56765
57914
  const connectionPath = this.options.connectionPath ?? defaultDaemonConnectionPath(root);
56766
57915
  const lockPath = this.options.lockPath ?? defaultDaemonLockPath(root);
56767
- mkdirSync12(dirname14(connectionPath), { recursive: true });
56768
- mkdirSync12(dirname14(lockPath), { recursive: true });
57916
+ mkdirSync13(dirname14(connectionPath), { recursive: true });
57917
+ mkdirSync13(dirname14(lockPath), { recursive: true });
56769
57918
  this.lockFd = acquireDaemonLock(lockPath, root);
56770
57919
  const token = this.options.token ?? randomBytes5(18).toString("base64url");
56771
57920
  const server = createServer2((request, response) => {
@@ -56816,7 +57965,7 @@ class ArchctxRuntimeRpcServer {
56816
57965
  await removePrivateControlFile(connection.connectionPath);
56817
57966
  } finally {
56818
57967
  if (this.lockFd !== undefined)
56819
- closeSync10(this.lockFd);
57968
+ closeSync11(this.lockFd);
56820
57969
  this.lockFd = undefined;
56821
57970
  }
56822
57971
  if (connection)
@@ -56858,7 +58007,7 @@ class ArchctxRuntimeRpcServer {
56858
58007
  });
56859
58008
  if (this.server && this.connection && (this.inFlightRpcRequests > 0 || this.authorizedRequests !== requestsBeforeRemoval)) {
56860
58009
  try {
56861
- if (!existsSync31(connection.connectionPath))
58010
+ if (!existsSync32(connection.connectionPath))
56862
58011
  createPrivateControlFile(connection.connectionPath, JSON.stringify(connection, null, 2));
56863
58012
  this.idleExitPending = false;
56864
58013
  this.armIdleTimer();
@@ -57071,8 +58220,8 @@ async function readRpcRequestBody(request, limits) {
57071
58220
  }
57072
58221
 
57073
58222
  // packages/local-runtime/runtime-daemon/src/index.ts
57074
- import { existsSync as existsSync32, readFileSync as readFileSync29 } from "node:fs";
57075
- 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";
57076
58225
  init_src();
57077
58226
 
57078
58227
  // packages/local-runtime/runtime-daemon/src/egress.ts
@@ -57108,6 +58257,8 @@ function effectiveEgressChannels2(env, live) {
57108
58257
  }
57109
58258
  if (live.updateCheckRequested)
57110
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" });
57111
58262
  if (env[CODEGRAPH_TELEMETRY_ENV] !== undefined && env[CODEGRAPH_TELEMETRY_ENV] !== CODEGRAPH_TELEMETRY_DISABLED_VALUE)
57112
58263
  channels.push({ channel: "codegraph-telemetry", destination: "CodeGraph configured telemetry endpoint", trigger: "CodeGraph execution with telemetry enabled", data: "third-party telemetry", status: "enabled" });
57113
58264
  return channels.map((channel) => localEgressAdmission2(channel.channel, env).allowed ? channel : { ...channel, status: "blocked-by-policy" });
@@ -57262,7 +58413,7 @@ class ArchctxDaemon {
57262
58413
  const model = loadNativeModelFromArchContext(root);
57263
58414
  const canonical = renderAgentContextProjection({ model, sourceDigest: digestJson({ model }), existingFiles: loadAgentContextProjectionFiles(root, model) });
57264
58415
  const canonicalBody = canonical.files.find((candidate) => candidate.path === file.path)?.body;
57265
- 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) {
57266
58417
  throw new Error(`Root contract requires canonical marker-only content: ${file.path}`);
57267
58418
  }
57268
58419
  }
@@ -57521,7 +58672,7 @@ class ArchctxDaemon {
57521
58672
  if (this.auditRunAbortControllers.size > 0)
57522
58673
  return true;
57523
58674
  for (const [repositoryId, session] of this.sessions) {
57524
- if (!existsSync32(session.workspace.root)) {
58675
+ if (!existsSync33(session.workspace.root)) {
57525
58676
  this.sessions.delete(repositoryId);
57526
58677
  continue;
57527
58678
  }
@@ -57684,6 +58835,10 @@ class ArchctxDaemon {
57684
58835
  async completeTaskUnchecked(root, input) {
57685
58836
  this.assertRunning();
57686
58837
  assertNoCallerProvidedAttestationFields(input, "complete-task");
58838
+ if (this.codeFacts.assertCanSync) {
58839
+ root = findRepositoryRoot(root);
58840
+ this.codeFacts.assertCanSync(root);
58841
+ }
57687
58842
  const session = await this.openSession(root);
57688
58843
  let currentHeadSha = input.headSha;
57689
58844
  try {
@@ -57721,7 +58876,7 @@ class ArchctxDaemon {
57721
58876
  try {
57722
58877
  projectionDrift = completeTaskProjectionDrift(session.workspace.root);
57723
58878
  projectionFreshness = completeTaskProjectionFreshness(session.workspace.root);
57724
- if (model.valid || !existsSync32(resolve32(session.workspace.root, ".archcontext/manifest.yaml"))) {
58879
+ if (model.valid || !existsSync33(resolve33(session.workspace.root, ".archcontext/manifest.yaml"))) {
57725
58880
  architectureDebt = readCompleteTaskArchitectureDebt(session.workspace.root, practiceEnforcement);
57726
58881
  }
57727
58882
  } catch (error) {
@@ -58210,7 +59365,7 @@ class ArchctxDaemon {
58210
59365
  }
58211
59366
  async restoreRepositorySessions() {
58212
59367
  for (const record of await this.localStore.listRepositorySessions()) {
58213
- if (!record.root || !existsSync32(record.root))
59368
+ if (!record.root || !existsSync33(record.root))
58214
59369
  continue;
58215
59370
  if (repositoryFingerprint(record.root) !== record.repositoryId)
58216
59371
  continue;
@@ -58508,7 +59663,7 @@ function blockedProductionInjections(deps) {
58508
59663
  ].filter((key) => (key in deps));
58509
59664
  }
58510
59665
  function completeTaskProjectionDrift(root) {
58511
- if (!existsSync32(resolve32(root, "docs/architecture/.projection-manifest.json")))
59666
+ if (!existsSync33(resolve33(root, "docs/architecture/.projection-manifest.json")))
58512
59667
  return;
58513
59668
  const profile = loadArchitectureDocumentationProfile(root);
58514
59669
  const loaded = loadArchitectureDocumentationInputs(root, profile);
@@ -58554,10 +59709,11 @@ function completeTaskProjectionFreshness(root) {
58554
59709
  }
58555
59710
 
58556
59711
  // packages/local-runtime/codegraph-adapter/src/index.ts
59712
+ init_src();
58557
59713
  import { execFileSync as execFileSync11 } from "node:child_process";
58558
- 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";
58559
59715
  import { createRequire as createRequire5 } from "node:module";
58560
- 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";
58561
59717
  init_src();
58562
59718
  var REQUIRED_CODEGRAPH_PACKAGE2 = "@colbymchenry/codegraph";
58563
59719
  var REQUIRED_CODEGRAPH_VERSION2 = productVersionManifest().runtime.codeGraph.requiredVersion;
@@ -58594,15 +59750,15 @@ function codeGraphCliInvocation2(binary, cwd, pathValue = process.env.PATH ?? ""
58594
59750
  }
58595
59751
  function resolveExecutable2(binary, cwd, pathValue) {
58596
59752
  if (binary.includes("/") || binary.includes("\\")) {
58597
- const path = isAbsolute10(binary) ? binary : join25(cwd, binary);
58598
- return existsSync33(path) ? realpathSync12.native(path) : undefined;
59753
+ const path = isAbsolute10(binary) ? binary : join26(cwd, binary);
59754
+ return existsSync34(path) ? realpathSync13.native(path) : undefined;
58599
59755
  }
58600
59756
  for (const dir of pathValue.split(delimiter2)) {
58601
59757
  if (!dir)
58602
59758
  continue;
58603
- const path = join25(dir, binary);
59759
+ const path = join26(dir, binary);
58604
59760
  if (isExecutablePath2(path))
58605
- return realpathSync12.native(path);
59761
+ return realpathSync13.native(path);
58606
59762
  }
58607
59763
  return;
58608
59764
  }
@@ -58620,12 +59776,12 @@ function isExecutablePath2(path) {
58620
59776
  function resolvePackagedCodeGraphShim2() {
58621
59777
  try {
58622
59778
  const manifestPath = requireFromAdapter2.resolve(`${REQUIRED_CODEGRAPH_PACKAGE2}/package.json`);
58623
- const manifest = JSON.parse(readFileSync30(manifestPath, "utf8"));
59779
+ const manifest = JSON.parse(readFileSync31(manifestPath, "utf8"));
58624
59780
  const relativeBin = manifest.bin?.codegraph;
58625
59781
  if (manifest.version !== REQUIRED_CODEGRAPH_VERSION2 || !relativeBin)
58626
59782
  return;
58627
- const shimPath = resolve33(dirname15(manifestPath), relativeBin);
58628
- return existsSync33(shimPath) ? realpathSync12.native(shimPath) : undefined;
59783
+ const shimPath = resolve34(dirname15(manifestPath), relativeBin);
59784
+ return existsSync34(shimPath) ? realpathSync13.native(shimPath) : undefined;
58629
59785
  } catch {
58630
59786
  return;
58631
59787
  }
@@ -58639,15 +59795,15 @@ function isNodeRuntimeScript2(path) {
58639
59795
  function readFilePrefix2(path, length) {
58640
59796
  let fd;
58641
59797
  try {
58642
- fd = openSync11(path, "r");
59798
+ fd = openSync12(path, "r");
58643
59799
  const buffer = Buffer.alloc(length);
58644
- const bytes = readSync5(fd, buffer, 0, length, 0);
59800
+ const bytes = readSync6(fd, buffer, 0, length, 0);
58645
59801
  return buffer.subarray(0, bytes).toString("utf8");
58646
59802
  } catch {
58647
59803
  return "";
58648
59804
  } finally {
58649
59805
  if (fd !== undefined)
58650
- closeSync11(fd);
59806
+ closeSync12(fd);
58651
59807
  }
58652
59808
  }
58653
59809
  var CODEGRAPH_IMPORT_NODE_OUTPUT_BYTES2 = 4 * 1024;
@@ -58662,7 +59818,7 @@ function readProjectionCodeGraphStatus2(invocation, root, timeout, envOverrides)
58662
59818
  function projectionCodeGraphStatusIssues2(status, root, phase) {
58663
59819
  const issue = (code, text) => ({
58664
59820
  code,
58665
- message: `CodeGraph ${phase} ${text}`
59821
+ message: phase === "repository" ? CODE_FACTS_ISSUE_MESSAGES[code] : `CodeGraph ${phase} ${text}`
58666
59822
  });
58667
59823
  if (!status.initialized)
58668
59824
  return [issue("not-initialized", "status is not initialized")];
@@ -58685,7 +59841,7 @@ function sameRealPath2(left, right) {
58685
59841
  if (typeof left !== "string")
58686
59842
  return false;
58687
59843
  try {
58688
- return realpathSync12.native(left) === realpathSync12.native(right);
59844
+ return realpathSync13.native(left) === realpathSync13.native(right);
58689
59845
  } catch {
58690
59846
  return false;
58691
59847
  }
@@ -58738,7 +59894,7 @@ function inspectRepositoryCodeFacts2(root, options = {}) {
58738
59894
  message: (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim().slice(0, 400)
58739
59895
  };
58740
59896
  }
58741
- const versionIssues = codeGraphVersion === REQUIRED_CODEGRAPH_VERSION2 ? [] : [{ code: "version-mismatch", message: `CodeGraph ${REQUIRED_CODEGRAPH_VERSION2} required, got actual binary ${codeGraphVersion || "unknown"}` }];
59897
+ const versionIssues = codeGraphVersion === REQUIRED_CODEGRAPH_VERSION2 ? [] : [{ code: "version-mismatch", message: CODE_FACTS_ISSUE_MESSAGES["version-mismatch"] }];
58742
59898
  if (!indexPresent) {
58743
59899
  return { schemaVersion, state: "index-missing", indexPresent: false, requiredVersion: REQUIRED_CODEGRAPH_VERSION2, codeGraphVersion, issues: versionIssues };
58744
59900
  }
@@ -58768,14 +59924,14 @@ function inspectRepositoryCodeFacts2(root, options = {}) {
58768
59924
  current: status.index?.currentExtractionVersion ?? null
58769
59925
  },
58770
59926
  indexState: status.index?.state ?? null,
58771
- reindexRecommended: status.index?.reindexRecommended ?? null,
59927
+ reindexRecommended: issues.some((issue) => issue.code !== "pending-source-changes"),
58772
59928
  pendingRefs: status.index?.pendingRefs ?? null,
58773
59929
  pendingChanges: pending ? { added: pending.added, modified: pending.modified, removed: pending.removed } : null,
58774
59930
  issues
58775
59931
  };
58776
59932
  }
58777
59933
  function codeGraphIndexAvailable2(workspaceRoot) {
58778
- return existsSync33(join25(workspaceRoot, ".codegraph"));
59934
+ return existsSync34(join26(workspaceRoot, ".codegraph"));
58779
59935
  }
58780
59936
  var NON_DECLARATION_KINDS2 = new Set(["file", "import", "export", "parameter"]);
58781
59937
  var RUNTIME_BUILTIN_CALL_NAMES2 = new Set([
@@ -59571,6 +60727,95 @@ function isExternalDocumentationResourceUri(uri) {
59571
60727
  function architectureBookResourceInput(uri) {
59572
60728
  return ARCHITECTURE_BOOK_RESOURCES.find((resource) => resource.uri === uri)?.input;
59573
60729
  }
60730
+ // schemas/repo/architecture-node.schema.json
60731
+ var architecture_node_schema_default2 = {
60732
+ $schema: "https://json-schema.org/draft/2020-12/schema",
60733
+ $id: "https://archctx.repoharness.com/schemas/repo/architecture-node.schema.json",
60734
+ title: "ArchitectureNode",
60735
+ type: "object",
60736
+ additionalProperties: false,
60737
+ required: ["schemaVersion", "id", "kind", "name", "status", "summary"],
60738
+ properties: {
60739
+ schemaVersion: { const: "archcontext.node/v2" },
60740
+ id: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
60741
+ kind: {
60742
+ type: "string",
60743
+ enum: ["capability", "module", "component", "interface", "event", "datastore", "external-system", "deployment"]
60744
+ },
60745
+ name: { type: "string" },
60746
+ status: { type: "string", enum: ["active", "planned", "deprecated", "removed"] },
60747
+ parent: { type: "string", pattern: "^([a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)*::)?[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$" },
60748
+ summary: { type: "string" },
60749
+ responsibilities: { type: "array", items: { type: "string" }, minItems: 1 },
60750
+ source: {
60751
+ type: "object",
60752
+ additionalProperties: false,
60753
+ properties: {
60754
+ include: { type: "array", items: { type: "string" } },
60755
+ exclude: { type: "array", items: { type: "string" } },
60756
+ entrypoints: {
60757
+ type: "array",
60758
+ items: {
60759
+ type: "object",
60760
+ additionalProperties: false,
60761
+ required: ["id", "path", "symbols"],
60762
+ properties: {
60763
+ id: { type: "string", pattern: "^[a-z][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+$" },
60764
+ path: { type: "string", minLength: 1 },
60765
+ symbols: {
60766
+ type: "array",
60767
+ minItems: 1,
60768
+ items: {
60769
+ type: "object",
60770
+ additionalProperties: false,
60771
+ required: ["name", "sinks"],
60772
+ properties: {
60773
+ name: { type: "string", minLength: 1 },
60774
+ sinks: {
60775
+ type: "array",
60776
+ minItems: 1,
60777
+ items: {
60778
+ type: "object",
60779
+ additionalProperties: false,
60780
+ required: ["id", "path", "symbol"],
60781
+ properties: {
60782
+ id: { type: "string", pattern: "^[a-z][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+$" },
60783
+ path: { type: "string", minLength: 1 },
60784
+ symbol: { type: "string", minLength: 1 }
60785
+ }
60786
+ }
60787
+ }
60788
+ }
60789
+ }
60790
+ }
60791
+ }
60792
+ }
60793
+ }
60794
+ }
60795
+ },
60796
+ ownership: {
60797
+ type: "object",
60798
+ additionalProperties: false,
60799
+ properties: {
60800
+ lifecycle: { type: "array", items: { type: "string" } },
60801
+ data: { type: "array", items: { type: "string" } }
60802
+ }
60803
+ },
60804
+ interfaces: {
60805
+ type: "object",
60806
+ additionalProperties: false,
60807
+ properties: {
60808
+ provides: { type: "array", items: { type: "string" } },
60809
+ consumes: { type: "array", items: { type: "string" } }
60810
+ }
60811
+ },
60812
+ criticality: { type: "string", enum: ["low", "medium", "high", "critical"] },
60813
+ riskDomains: { type: "array", items: { type: "string" } },
60814
+ notes: { type: "array", items: { type: "string" } },
60815
+ extensions: { type: "object", additionalProperties: true }
60816
+ }
60817
+ };
60818
+
59574
60819
  // packages/surfaces/cli/src/main.ts
59575
60820
  var [, , command, ...args] = process.argv;
59576
60821
  var CLI_ENTRY = fileURLToPath2(import.meta.url);
@@ -59611,6 +60856,12 @@ var AUDIT_RUN_VALUE_FLAGS = ["--task-session-id", "--reason", "--risk", "--uncer
59611
60856
  var AUDIT_RUN_BOOLEAN_FLAGS = ["--no-wait", ...AUDIT_GLOBAL_BOOLEAN_FLAGS];
59612
60857
  var AUDIT_APPROVE_VALUE_FLAGS = ["--run-id", "--confirm-public-repo", ...AUDIT_GLOBAL_VALUE_FLAGS];
59613
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;
59614
60865
  var CLI_COMMAND_FLAGS = {
59615
60866
  debt: { value: ["--changed-path"], boolean: [] },
59616
60867
  "agent-context": { value: ["--expected-worktree-digest", "--id", "--task-session-id"], boolean: ["--approved"] },
@@ -59724,7 +60975,10 @@ var CLI_COMMAND_FLAGS = {
59724
60975
  recover: { value: ["--request-json"], boolean: [] },
59725
60976
  readback: { value: ["--request-json"], boolean: [] },
59726
60977
  snapshot: { value: [], boolean: [] },
59727
- 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: [] }
59728
60982
  }
59729
60983
  },
59730
60984
  recommendations: {
@@ -59859,6 +61113,48 @@ var CLI_PROJECTION_FRESHNESS_HELP = {
59859
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).",
59860
61114
  capability: "projection-freshness-v1"
59861
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
+ };
59862
61158
  function refactorFlagValue(args, flag) {
59863
61159
  const index = args.indexOf(flag);
59864
61160
  if (index === -1)
@@ -59995,14 +61291,14 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
59995
61291
  case "status":
59996
61292
  return (await runtime()).runtimeStatus(cwd);
59997
61293
  case "projection":
59998
- return await runProjectionProtocolCommand2(args, cwd, runtime);
61294
+ return await runProjectionProtocolCommand2(args, cwd, runtime, deps);
59999
61295
  case "flows":
60000
61296
  return runFlowsCommand(args, cwd);
60001
61297
  case "repo": {
60002
61298
  const subcommand = args[0] ?? "list";
60003
61299
  if (subcommand === "add") {
60004
61300
  const root = readFlag(args, "--root") ?? cwd;
60005
- if (resolve34(root) !== resolve34(cwd)) {
61301
+ if (resolve35(root) !== resolve35(cwd)) {
60006
61302
  return errorEnvelope("repo.add", "AC_CAPABILITY_UNSUPPORTED", "Multi-repo architecture context is outside the Local Core MVP; run archctx inside one Git repository.");
60007
61303
  }
60008
61304
  const daemon = await runtime();
@@ -60244,7 +61540,7 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
60244
61540
  const read = await readResolvePathsFrom(args, cwd, deps);
60245
61541
  if ("problem" in read)
60246
61542
  return errorEnvelope("resolve", "AC_SCHEMA_INVALID", read.problem);
60247
- const model = loadNativeModelFromArchContext(cwd);
61543
+ const model = loadResolveModel(cwd);
60248
61544
  const outcomes = resolveArchitectureOwnersForPaths(model.nodes, read.paths);
60249
61545
  return okEnvelope("resolve", { results: outcomes.map((outcome, index) => resolveResultData(read.paths[index], outcome)) });
60250
61546
  }
@@ -60254,7 +61550,7 @@ async function runCliUnchecked(command = "help", args = [], cwd, deps = {}) {
60254
61550
  if (!isRepoRelativePosixPath(path)) {
60255
61551
  return errorEnvelope("resolve", "AC_SCHEMA_INVALID", "resolve --path must be a repository-relative POSIX path");
60256
61552
  }
60257
- const model = loadNativeModelFromArchContext(cwd);
61553
+ const model = loadResolveModel(cwd);
60258
61554
  return okEnvelope("resolve", resolveResultData(path, resolveArchitectureOwnerForPath(model.nodes, path)));
60259
61555
  }
60260
61556
  case "help":
@@ -60346,6 +61642,12 @@ function cliHelpEnvelope(command, args) {
60346
61642
  case "projection":
60347
61643
  if (subcommand === "freshness")
60348
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);
60349
61651
  break;
60350
61652
  case "daemon":
60351
61653
  return okEnvelope("daemon", {
@@ -61062,8 +62364,10 @@ async function runAgentContextProjectionCommand2(args, cwd, daemon) {
61062
62364
  expectedWorktreeDigest: readFlag(args, "--expected-worktree-digest")
61063
62365
  });
61064
62366
  }
61065
- async function runProjectionProtocolCommand2(args, cwd, runtime) {
62367
+ async function runProjectionProtocolCommand2(args, cwd, runtime, deps) {
61066
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);
61067
62371
  if (action === "snapshot") {
61068
62372
  const unexpected = args.slice(1).filter((arg) => arg !== "--json");
61069
62373
  if (unexpected.length > 0)
@@ -61073,7 +62377,7 @@ async function runProjectionProtocolCommand2(args, cwd, runtime) {
61073
62377
  if (action === "freshness")
61074
62378
  return runProjectionFreshnessCliCommand(args.slice(1), cwd);
61075
62379
  if (!["run", "recover", "readback"].includes(action))
61076
- 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");
61077
62381
  const raw = readFlag(args, "--request-json");
61078
62382
  if (!raw)
61079
62383
  return errorEnvelope(`projection.${action}`, "AC_SCHEMA_INVALID", `projection ${action} requires --request-json`);
@@ -61110,6 +62414,145 @@ function runProjectionFreshnessCliCommand(args, cwd) {
61110
62414
  }
61111
62415
  return runProjectionFreshnessCommand(cwd, changedPaths.length > 0 ? { changedPaths } : {});
61112
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
+ }
61113
62556
  function runFlowsCommand(args, cwd) {
61114
62557
  const formatAt = args.indexOf("--format");
61115
62558
  const unexpected = args.slice(1).filter((arg, index) => arg !== "--json" && (formatAt < 0 || index + 1 !== formatAt && index !== formatAt));
@@ -61473,12 +62916,12 @@ function auditManifestGateRoot(cwd) {
61473
62916
  }
61474
62917
  }
61475
62918
  function auditGithubIssuesEnabled(cwd) {
61476
- const manifestPath = resolve34(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
61477
- if (!existsSync34(manifestPath))
62919
+ const manifestPath = resolve35(auditManifestGateRoot(cwd), ".archcontext/manifest.yaml");
62920
+ if (!existsSync35(manifestPath))
61478
62921
  return false;
61479
62922
  let raw;
61480
62923
  try {
61481
- raw = readFileSync31(manifestPath, "utf8");
62924
+ raw = readFileSync32(manifestPath, "utf8");
61482
62925
  } catch {
61483
62926
  return false;
61484
62927
  }
@@ -61763,8 +63206,8 @@ async function runGithubCommand(args, cwd, deps) {
61763
63206
  }
61764
63207
  if (subcommand === "disconnect") {
61765
63208
  const connection = await deps.githubConnectionReader?.readVerifiedConnection({ repositoryRoot: cwd });
61766
- const localRecordRemoved = existsSync34(connectionPath);
61767
- rmSync15(connectionPath, { force: true });
63209
+ const localRecordRemoved = existsSync35(connectionPath);
63210
+ rmSync16(connectionPath, { force: true });
61768
63211
  return okEnvelope("github.disconnect", {
61769
63212
  connected: connection !== undefined,
61770
63213
  localRecordRemoved,
@@ -62051,7 +63494,7 @@ async function digestReviewChallenge(challenge) {
62051
63494
  return reviewChallengeV2Digest(challenge);
62052
63495
  }
62053
63496
  function defaultGithubConnectionPath(cwd) {
62054
- return join26(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
63497
+ return join27(dirname16(defaultDaemonConnectionPath(cwd)), "github-connection.json");
62055
63498
  }
62056
63499
  function sanitizeGithubConnection(record, connectionPath) {
62057
63500
  return {
@@ -62075,7 +63518,7 @@ async function readReviewChallengeV2Arg(args, cwd, commandName = "github verify-
62075
63518
  if (!inline && !challengePath)
62076
63519
  return { ok: false, message: `${commandName} requires --challenge-json or --challenge-path` };
62077
63520
  try {
62078
- const raw = inline ?? readFileSync31(resolve34(cwd, challengePath), "utf8");
63521
+ const raw = inline ?? readFileSync32(resolve35(cwd, challengePath), "utf8");
62079
63522
  const parsed = JSON.parse(raw);
62080
63523
  const attestation = await Promise.resolve().then(() => (init_src6(), {}));
62081
63524
  const assertReviewChallengeV2 = assertReviewChallengeV22;
@@ -62459,7 +63902,7 @@ function readLatestPackageVersion(env) {
62459
63902
  if (env[LATEST_VERSION_ENV]) {
62460
63903
  return { source: "env", version: env[LATEST_VERSION_ENV] };
62461
63904
  }
62462
- 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"], {
62463
63906
  encoding: "utf8",
62464
63907
  timeout: NPM_VIEW_TIMEOUT_MS,
62465
63908
  shell: false
@@ -62553,10 +63996,10 @@ function doctorSqlite(cwd) {
62553
63996
  const legacyLocalStore = inspectLegacyLocalStoreMigration(cwd);
62554
63997
  return {
62555
63998
  path,
62556
- exists: existsSync34(path),
63999
+ exists: existsSync35(path),
62557
64000
  migrations: productVersionManifest().runtime.sqliteMigrations,
62558
64001
  legacyPath: paths.legacyLocalStorePath,
62559
- legacyExists: existsSync34(paths.legacyLocalStorePath),
64002
+ legacyExists: existsSync35(paths.legacyLocalStorePath),
62560
64003
  legacyLocalStore
62561
64004
  };
62562
64005
  }
@@ -62577,8 +64020,8 @@ function runtimePathsReport(cwd) {
62577
64020
  ...paths,
62578
64021
  legacyLocalStore: inspectLegacyLocalStoreMigration(cwd),
62579
64022
  runtimeRepositoryId: repositoryFingerprint2(paths.repositoryRoot),
62580
- repositoryTruthDir: join26(paths.repositoryRoot, ".archcontext"),
62581
- codeGraphIndexDir: join26(paths.repositoryRoot, ".codegraph"),
64023
+ repositoryTruthDir: join27(paths.repositoryRoot, ".archcontext"),
64024
+ codeGraphIndexDir: join27(paths.repositoryRoot, ".codegraph"),
62582
64025
  npmGlobalInstallState: "forbidden",
62583
64026
  overrides: {
62584
64027
  stateRootEnv: "ARCHCONTEXT_STATE_DIR",
@@ -62587,7 +64030,7 @@ function runtimePathsReport(cwd) {
62587
64030
  };
62588
64031
  }
62589
64032
  function pathAccess(path) {
62590
- const exists = existsSync34(path);
64033
+ const exists = existsSync35(path);
62591
64034
  return {
62592
64035
  path,
62593
64036
  exists,
@@ -62828,7 +64271,7 @@ function daemonStopStateDir(args, cwd) {
62828
64271
  return { ok: false, envelope: errorEnvelope("daemon.stop", "AC_SCHEMA_INVALID", "daemon stop accepts at most one of --state-dir or --workspace-id") };
62829
64272
  }
62830
64273
  if (stateDir !== undefined)
62831
- return { ok: true, stateDir: resolve34(cwd, stateDir) };
64274
+ return { ok: true, stateDir: resolve35(cwd, stateDir) };
62832
64275
  if (workspaceId === undefined)
62833
64276
  return { ok: true };
62834
64277
  if (!/^ws\.[0-9a-f]{16}$/.test(workspaceId)) {
@@ -62864,9 +64307,9 @@ async function startBackgroundDaemon(args, cwd) {
62864
64307
  const recovery = discovered.recovery;
62865
64308
  const connectionPath = defaultDaemonConnectionPath(cwd);
62866
64309
  const controlDir = dirname16(connectionPath);
62867
- const logPath = join26(controlDir, "archctxd.log");
62868
- mkdirSync13(controlDir, { recursive: true });
62869
- 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);
62870
64313
  try {
62871
64314
  let childExit;
62872
64315
  let childError;
@@ -62906,7 +64349,7 @@ async function startBackgroundDaemon(args, cwd) {
62906
64349
  ...recoveryData(recovery)
62907
64350
  });
62908
64351
  } finally {
62909
- closeSync12(logFd);
64352
+ closeSync13(logFd);
62910
64353
  }
62911
64354
  }
62912
64355
  async function upgradeDaemon(args, cwd) {
@@ -62915,7 +64358,7 @@ async function upgradeDaemon(args, cwd) {
62915
64358
  const record = readRuntimeRpcConnectionFile(cwd);
62916
64359
  const unchanged = () => JSON.stringify(readRuntimeRpcConnectionFile(cwd)) === JSON.stringify(record);
62917
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");
62918
- 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)) {
62919
64362
  return refuse("connection record cannot prove a daemon identity");
62920
64363
  }
62921
64364
  let lockOwner;
@@ -62965,9 +64408,9 @@ async function upgradeDaemon(args, cwd) {
62965
64408
  if (!stopped.ok)
62966
64409
  return refuse("live instance refused the expected identity");
62967
64410
  const deadline = Date.now() + daemonStartTimeoutMs(args);
62968
- while (existsSync34(connectionPath) || existsSync34(lockPath)) {
64411
+ while (existsSync35(connectionPath) || existsSync35(lockPath)) {
62969
64412
  const current = readRuntimeRpcConnectionFile(cwd);
62970
- if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync34(connectionPath)) {
64413
+ if (current ? JSON.stringify(current) !== JSON.stringify(record) : existsSync35(connectionPath)) {
62971
64414
  return refuse("connection changed or cannot be verified during self-stop");
62972
64415
  }
62973
64416
  const owner = liveDaemonLockOwnerPid(cwd);
@@ -62987,7 +64430,7 @@ async function upgradeDaemon(args, cwd) {
62987
64430
  const started = await startBackgroundDaemon(args, cwd);
62988
64431
  return started.ok ? { ...started, requestId: "daemon.upgrade", data: { ...started.data, upgraded: false, reason: "runtime-compatible" } } : started;
62989
64432
  }
62990
- 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) {
62991
64434
  return refuse("ownership changed before replacement");
62992
64435
  }
62993
64436
  const recovery = recoverStaleDaemonControlFiles(cwd, { removeUnhealthyConnection: true });
@@ -63153,7 +64596,7 @@ function daemonStartTimeoutMs(args) {
63153
64596
  }
63154
64597
  function daemonStartFailureMessage(cwd, logPath, childPid, childExit, childError) {
63155
64598
  const childState = childError ? `childError=${childError.message}` : childExit ? `childExit=${childExit.code ?? "null"}/${childExit.signal ?? "none"}` : "childState=still-running";
63156
- const lockOwnerPid = existsSync34(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
64599
+ const lockOwnerPid = existsSync35(defaultDaemonConnectionPath(cwd)) ? undefined : liveDaemonLockOwnerPid(cwd);
63157
64600
  const owner = lockOwnerPid === undefined || lockOwnerPid === childPid ? "" : `; ${orphanedDaemonMessage(lockOwnerPid)}`;
63158
64601
  return `archctxd did not become ready; ${childState}${owner}; log=${logPath}; logTail=${readFileTail(logPath)}`;
63159
64602
  }
@@ -63174,7 +64617,7 @@ function sleep2(ms) {
63174
64617
  }
63175
64618
  function readFileTail(path, maxBytes = 4096) {
63176
64619
  try {
63177
- const content = readFileSync31(path, "utf8");
64620
+ const content = readFileSync32(path, "utf8");
63178
64621
  return content.slice(Math.max(0, content.length - maxBytes)).replace(/\s+/g, " ").trim();
63179
64622
  } catch {
63180
64623
  return "<unavailable>";
@@ -63259,7 +64702,7 @@ function resolveCommandExitCode(result) {
63259
64702
  return 2;
63260
64703
  return 1;
63261
64704
  }
63262
- 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"];
63263
64706
  var CLI_RESOLVE_HELP = {
63264
64707
  command: "archctx resolve (--path <repo-relative path> | --paths-from <file> | --paths-from -)",
63265
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.",
@@ -63267,6 +64710,37 @@ var CLI_RESOLVE_HELP = {
63267
64710
  result: "Each result carries path, matched, ambiguous and owningCapabilityId. A match adds stableId, kind, name, source and extensions of the most specific node. An ambiguous result adds candidates. owningCapabilityId is the nearest kind: capability node on the owner's parent chain (the owner itself when it is a capability), or null. For an ambiguous result it is set only when all candidates share one owning capability.",
63268
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."
63269
64712
  };
64713
+ function loadResolveModel(cwd) {
64714
+ if (!existsSync35(join27(cwd, ".archcontext/manifest.yaml"))) {
64715
+ throw new NativeContextError("native-context-model-missing", { path: ".archcontext/manifest.yaml" });
64716
+ }
64717
+ let model;
64718
+ try {
64719
+ model = loadNativeModelFromArchContext(cwd);
64720
+ } catch (error) {
64721
+ const cause = error;
64722
+ if (typeof cause?.code === "string") {
64723
+ throw new NativeContextError("native-context-input-unreadable", {
64724
+ category: "open-or-read",
64725
+ ...typeof cause.path === "string" ? { path: cause.path } : {}
64726
+ });
64727
+ }
64728
+ throw new NativeContextError("native-context-model-invalid", { invariant: "model-parse" });
64729
+ }
64730
+ if (model.nodes.length === 0) {
64731
+ throw new NativeContextError("native-context-model-missing", { category: "no-native-nodes" });
64732
+ }
64733
+ for (const node of model.nodes) {
64734
+ const { issues } = validateJsonSchema(architecture_node_schema_default2, node);
64735
+ if (issues.length > 0) {
64736
+ throw new NativeContextError("native-context-model-invalid", {
64737
+ issues: issues.slice(0, 32).map((issue) => ({ entityId: typeof node.id === "string" ? node.id : "(undeclared)", field: issue.path, invariant: issue.message })),
64738
+ omittedDiagnosticCount: Math.max(0, issues.length - 32)
64739
+ });
64740
+ }
64741
+ }
64742
+ return model;
64743
+ }
63270
64744
  function resolveResultData(path, outcome) {
63271
64745
  if (outcome.status === "matched") {
63272
64746
  const node = outcome.node;
@@ -63293,7 +64767,7 @@ async function readResolvePathsFrom(args, cwd, deps) {
63293
64767
  return { problem: "resolve --paths-from requires a file path or - for stdin" };
63294
64768
  let content;
63295
64769
  try {
63296
- 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");
63297
64771
  } catch (error) {
63298
64772
  return { problem: `resolve --paths-from ${source} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63299
64773
  }
@@ -63381,7 +64855,7 @@ function readRepositoryInputFile(cwd, flag, file) {
63381
64855
  const absolute = assertPathHasNoSymlinkSegments2(cwd, file);
63382
64856
  if (!statSync11(absolute).isFile())
63383
64857
  return { problem: `plan ${flag} ${file} is not a regular file` };
63384
- return { content: readFileSync31(absolute, "utf8") };
64858
+ return { content: readFileSync32(absolute, "utf8") };
63385
64859
  } catch (error) {
63386
64860
  return { problem: `plan ${flag} ${file} is not readable: ${error instanceof Error ? error.message : String(error)}` };
63387
64861
  }
@@ -63399,10 +64873,10 @@ function runHashCommand(args, cwd) {
63399
64873
  } catch (error) {
63400
64874
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", error instanceof Error ? error.message : String(error));
63401
64875
  }
63402
- if (!existsSync34(absolute) || !statSync11(absolute).isFile()) {
64876
+ if (!existsSync35(absolute) || !statSync11(absolute).isFile()) {
63403
64877
  return errorEnvelope("hash", "AC_SCHEMA_INVALID", `hash --path is not an existing file: ${path}`);
63404
64878
  }
63405
- return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync31(absolute, "utf8") }) });
64879
+ return okEnvelope("hash", { path, hash: digestJson({ body: readFileSync32(absolute, "utf8") }) });
63406
64880
  }
63407
64881
  async function readCliPlanOperations(args, cwd, deps) {
63408
64882
  if (args.includes("--operations-file")) {
@@ -63412,7 +64886,7 @@ async function readCliPlanOperations(args, cwd, deps) {
63412
64886
  const file = readFlag(args, "--operations-file");
63413
64887
  if (!file)
63414
64888
  return "plan --operations-file requires a path";
63415
- 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);
63416
64890
  if ("problem" in read)
63417
64891
  return read.problem;
63418
64892
  let parsed;
@@ -63474,7 +64948,7 @@ async function readCliPlanBody(args, cwd, deps) {
63474
64948
  return { problem: "plan --body requires a value (use - to read stdin)" };
63475
64949
  if (inline !== "-")
63476
64950
  return inline;
63477
- return deps.readStdin ? deps.readStdin() : readFileSync31(0, "utf8");
64951
+ return deps.readStdin ? deps.readStdin() : readFileSync32(0, "utf8");
63478
64952
  }
63479
64953
  function readFlag(args, flag) {
63480
64954
  const index = args.indexOf(flag);
@@ -63566,6 +65040,8 @@ if (__require.main == __require.module) {
63566
65040
  await runStdioMcpLoop(stdinLines(), (line) => process.stdout.write(`${line}
63567
65041
  `), (line) => process.stderr.write(`${line}
63568
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());
63569
65045
  } else if (command === "daemon" && args[0] === "start" && args.includes("--foreground") && preflightCliArguments(command, args) === undefined) {
63570
65046
  await runForegroundDaemon(process.cwd(), args).catch((error) => {
63571
65047
  const message = error instanceof Error ? error.message : String(error);
@@ -63579,6 +65055,8 @@ if (__require.main == __require.module) {
63579
65055
  `);
63580
65056
  if (result.ok === false)
63581
65057
  process.exitCode = 1;
65058
+ if (command === "validate" && result.ok && result.data?.valid === false)
65059
+ process.exitCode = 1;
63582
65060
  if (command === "resolve")
63583
65061
  process.exitCode = resolveCommandExitCode(result);
63584
65062
  }