@event4u/agent-config 14.14.0 → 14.15.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.
Files changed (72) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +63 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/pr/create.md +16 -1
  7. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  8. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  9. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  10. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  11. package/dist/cli/registry.js +1 -0
  12. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  13. package/dist/cli-delegate/cmd_conformance.js +513 -17
  14. package/dist/cli-delegate/cmd_doctor.js +1 -1
  15. package/dist/discovery/deprecation-report.md +1 -1
  16. package/dist/discovery/discovery-manifest.json +5 -5
  17. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  18. package/dist/discovery/discovery-manifest.summary.md +1 -1
  19. package/dist/discovery/dormancy-report.md +1 -1
  20. package/dist/discovery/orphan-report.md +1 -1
  21. package/dist/discovery/packs.json +2 -2
  22. package/dist/discovery/trust-report.md +1 -1
  23. package/dist/discovery/workspaces.json +2 -2
  24. package/dist/hooks/dispatch.js +1043 -390
  25. package/dist/mcp/registry-manifest.json +2 -2
  26. package/dist/mcp/server.json +2 -2
  27. package/dist/mcp/server.mjs +703 -115
  28. package/package.json +1 -1
  29. package/src/config/assurance-capability-registry.json +1 -1
  30. package/src/config/evaluator-budgets.json +4 -4
  31. package/src/config/gate-coverage.yml +62 -2
  32. package/src/config/gate-reachability-exemptions.json +1 -0
  33. package/src/config/gate-violation-baselines.json +4 -3
  34. package/src/config/rule-enforcement-baseline.json +0 -1
  35. package/src/config/web-launch-readiness.json +4 -2
  36. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  37. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  38. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  39. package/src/scripts/_dispatch.bash +11 -0
  40. package/src/scripts/_lib/branch_convergence.ts +186 -0
  41. package/src/scripts/_lib/context_observation.ts +132 -0
  42. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  43. package/src/scripts/_lib/experiment_binding.ts +95 -0
  44. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  45. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  46. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  47. package/src/scripts/_lib/release_highlights.ts +75 -10
  48. package/src/scripts/_lib/repeated_failure.ts +19 -2
  49. package/src/scripts/_lib/runtime_journal.ts +11 -4
  50. package/src/scripts/check_agent_artifact_location.ts +337 -0
  51. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  52. package/src/scripts/check_enforcement_coverage.ts +40 -1
  53. package/src/scripts/check_release_highlights.ts +17 -1
  54. package/src/scripts/check_web_launch_readiness.ts +310 -20
  55. package/src/scripts/evolution_lab.ts +53 -1
  56. package/src/scripts/generate_capability_matrix.ts +16 -9
  57. package/src/scripts/git_authorization_hook.ts +476 -6
  58. package/src/scripts/grade_target_readiness.ts +221 -2
  59. package/src/scripts/hook_effect_doctor.ts +47 -4
  60. package/src/scripts/hooks/block_unauthorized_git.ts +238 -9
  61. package/src/scripts/hooks/merge_impact.ts +207 -0
  62. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  63. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  64. package/src/scripts/install-hooks.sh +18 -0
  65. package/src/scripts/lint_code_comments.ts +106 -1
  66. package/src/scripts/release.ts +5 -17
  67. package/src/scripts/release_drill.ts +76 -1
  68. package/src/scripts/release_env.ts +31 -0
  69. package/src/scripts/release_publication.ts +125 -1
  70. package/src/scripts/roadmap_context.ts +15 -0
  71. package/src/scripts/skill_scout.ts +633 -0
  72. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -112,17 +112,17 @@ var require_visit = __commonJS({
112
112
  visit.BREAK = BREAK;
113
113
  visit.SKIP = SKIP;
114
114
  visit.REMOVE = REMOVE;
115
- function visit_(key, node, visitor, path105) {
116
- const ctrl = callVisitor(key, node, visitor, path105);
115
+ function visit_(key, node, visitor, path106) {
116
+ const ctrl = callVisitor(key, node, visitor, path106);
117
117
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
118
- replaceNode(key, path105, ctrl);
119
- return visit_(key, ctrl, visitor, path105);
118
+ replaceNode(key, path106, ctrl);
119
+ return visit_(key, ctrl, visitor, path106);
120
120
  }
121
121
  if (typeof ctrl !== "symbol") {
122
122
  if (identity.isCollection(node)) {
123
- path105 = Object.freeze(path105.concat(node));
123
+ path106 = Object.freeze(path106.concat(node));
124
124
  for (let i = 0; i < node.items.length; ++i) {
125
- const ci = visit_(i, node.items[i], visitor, path105);
125
+ const ci = visit_(i, node.items[i], visitor, path106);
126
126
  if (typeof ci === "number")
127
127
  i = ci - 1;
128
128
  else if (ci === BREAK)
@@ -133,13 +133,13 @@ var require_visit = __commonJS({
133
133
  }
134
134
  }
135
135
  } else if (identity.isPair(node)) {
136
- path105 = Object.freeze(path105.concat(node));
137
- const ck = visit_("key", node.key, visitor, path105);
136
+ path106 = Object.freeze(path106.concat(node));
137
+ const ck = visit_("key", node.key, visitor, path106);
138
138
  if (ck === BREAK)
139
139
  return BREAK;
140
140
  else if (ck === REMOVE)
141
141
  node.key = null;
142
- const cv = visit_("value", node.value, visitor, path105);
142
+ const cv = visit_("value", node.value, visitor, path106);
143
143
  if (cv === BREAK)
144
144
  return BREAK;
145
145
  else if (cv === REMOVE)
@@ -160,17 +160,17 @@ var require_visit = __commonJS({
160
160
  visitAsync.BREAK = BREAK;
161
161
  visitAsync.SKIP = SKIP;
162
162
  visitAsync.REMOVE = REMOVE;
163
- async function visitAsync_(key, node, visitor, path105) {
164
- const ctrl = await callVisitor(key, node, visitor, path105);
163
+ async function visitAsync_(key, node, visitor, path106) {
164
+ const ctrl = await callVisitor(key, node, visitor, path106);
165
165
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
166
- replaceNode(key, path105, ctrl);
167
- return visitAsync_(key, ctrl, visitor, path105);
166
+ replaceNode(key, path106, ctrl);
167
+ return visitAsync_(key, ctrl, visitor, path106);
168
168
  }
169
169
  if (typeof ctrl !== "symbol") {
170
170
  if (identity.isCollection(node)) {
171
- path105 = Object.freeze(path105.concat(node));
171
+ path106 = Object.freeze(path106.concat(node));
172
172
  for (let i = 0; i < node.items.length; ++i) {
173
- const ci = await visitAsync_(i, node.items[i], visitor, path105);
173
+ const ci = await visitAsync_(i, node.items[i], visitor, path106);
174
174
  if (typeof ci === "number")
175
175
  i = ci - 1;
176
176
  else if (ci === BREAK)
@@ -181,13 +181,13 @@ var require_visit = __commonJS({
181
181
  }
182
182
  }
183
183
  } else if (identity.isPair(node)) {
184
- path105 = Object.freeze(path105.concat(node));
185
- const ck = await visitAsync_("key", node.key, visitor, path105);
184
+ path106 = Object.freeze(path106.concat(node));
185
+ const ck = await visitAsync_("key", node.key, visitor, path106);
186
186
  if (ck === BREAK)
187
187
  return BREAK;
188
188
  else if (ck === REMOVE)
189
189
  node.key = null;
190
- const cv = await visitAsync_("value", node.value, visitor, path105);
190
+ const cv = await visitAsync_("value", node.value, visitor, path106);
191
191
  if (cv === BREAK)
192
192
  return BREAK;
193
193
  else if (cv === REMOVE)
@@ -214,23 +214,23 @@ var require_visit = __commonJS({
214
214
  }
215
215
  return visitor;
216
216
  }
217
- function callVisitor(key, node, visitor, path105) {
217
+ function callVisitor(key, node, visitor, path106) {
218
218
  if (typeof visitor === "function")
219
- return visitor(key, node, path105);
219
+ return visitor(key, node, path106);
220
220
  if (identity.isMap(node))
221
- return visitor.Map?.(key, node, path105);
221
+ return visitor.Map?.(key, node, path106);
222
222
  if (identity.isSeq(node))
223
- return visitor.Seq?.(key, node, path105);
223
+ return visitor.Seq?.(key, node, path106);
224
224
  if (identity.isPair(node))
225
- return visitor.Pair?.(key, node, path105);
225
+ return visitor.Pair?.(key, node, path106);
226
226
  if (identity.isScalar(node))
227
- return visitor.Scalar?.(key, node, path105);
227
+ return visitor.Scalar?.(key, node, path106);
228
228
  if (identity.isAlias(node))
229
- return visitor.Alias?.(key, node, path105);
229
+ return visitor.Alias?.(key, node, path106);
230
230
  return void 0;
231
231
  }
232
- function replaceNode(key, path105, node) {
233
- const parent = path105[path105.length - 1];
232
+ function replaceNode(key, path106, node) {
233
+ const parent = path106[path106.length - 1];
234
234
  if (identity.isCollection(parent)) {
235
235
  parent.items[key] = node;
236
236
  } else if (identity.isPair(parent)) {
@@ -840,10 +840,10 @@ var require_Collection = __commonJS({
840
840
  var createNode = require_createNode();
841
841
  var identity = require_identity();
842
842
  var Node = require_Node();
843
- function collectionFromPath(schema, path105, value) {
843
+ function collectionFromPath(schema, path106, value) {
844
844
  let v = value;
845
- for (let i = path105.length - 1; i >= 0; --i) {
846
- const k = path105[i];
845
+ for (let i = path106.length - 1; i >= 0; --i) {
846
+ const k = path106[i];
847
847
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
848
848
  const a = [];
849
849
  a[k] = v;
@@ -862,7 +862,7 @@ var require_Collection = __commonJS({
862
862
  sourceObjects: /* @__PURE__ */ new Map()
863
863
  });
864
864
  }
865
- var isEmptyPath = (path105) => path105 == null || typeof path105 === "object" && !!path105[Symbol.iterator]().next().done;
865
+ var isEmptyPath = (path106) => path106 == null || typeof path106 === "object" && !!path106[Symbol.iterator]().next().done;
866
866
  var Collection = class extends Node.NodeBase {
867
867
  constructor(type, schema) {
868
868
  super(type);
@@ -892,11 +892,11 @@ var require_Collection = __commonJS({
892
892
  * be a Pair instance or a `{ key, value }` object, which may not have a key
893
893
  * that already exists in the map.
894
894
  */
895
- addIn(path105, value) {
896
- if (isEmptyPath(path105))
895
+ addIn(path106, value) {
896
+ if (isEmptyPath(path106))
897
897
  this.add(value);
898
898
  else {
899
- const [key, ...rest] = path105;
899
+ const [key, ...rest] = path106;
900
900
  const node = this.get(key, true);
901
901
  if (identity.isCollection(node))
902
902
  node.addIn(rest, value);
@@ -910,8 +910,8 @@ var require_Collection = __commonJS({
910
910
  * Removes a value from the collection.
911
911
  * @returns `true` if the item was found and removed.
912
912
  */
913
- deleteIn(path105) {
914
- const [key, ...rest] = path105;
913
+ deleteIn(path106) {
914
+ const [key, ...rest] = path106;
915
915
  if (rest.length === 0)
916
916
  return this.delete(key);
917
917
  const node = this.get(key, true);
@@ -925,8 +925,8 @@ var require_Collection = __commonJS({
925
925
  * scalar values from their surrounding node; to disable set `keepScalar` to
926
926
  * `true` (collections are always returned intact).
927
927
  */
928
- getIn(path105, keepScalar) {
929
- const [key, ...rest] = path105;
928
+ getIn(path106, keepScalar) {
929
+ const [key, ...rest] = path106;
930
930
  const node = this.get(key, true);
931
931
  if (rest.length === 0)
932
932
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -944,8 +944,8 @@ var require_Collection = __commonJS({
944
944
  /**
945
945
  * Checks if the collection includes a value with the key `key`.
946
946
  */
947
- hasIn(path105) {
948
- const [key, ...rest] = path105;
947
+ hasIn(path106) {
948
+ const [key, ...rest] = path106;
949
949
  if (rest.length === 0)
950
950
  return this.has(key);
951
951
  const node = this.get(key, true);
@@ -955,8 +955,8 @@ var require_Collection = __commonJS({
955
955
  * Sets a value in this collection. For `!!set`, `value` needs to be a
956
956
  * boolean to add/remove the item from the set.
957
957
  */
958
- setIn(path105, value) {
959
- const [key, ...rest] = path105;
958
+ setIn(path106, value) {
959
+ const [key, ...rest] = path106;
960
960
  if (rest.length === 0) {
961
961
  this.set(key, value);
962
962
  } else {
@@ -3471,9 +3471,9 @@ var require_Document = __commonJS({
3471
3471
  this.contents.add(value);
3472
3472
  }
3473
3473
  /** Adds a value to the document. */
3474
- addIn(path105, value) {
3474
+ addIn(path106, value) {
3475
3475
  if (assertCollection(this.contents))
3476
- this.contents.addIn(path105, value);
3476
+ this.contents.addIn(path106, value);
3477
3477
  }
3478
3478
  /**
3479
3479
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -3548,14 +3548,14 @@ var require_Document = __commonJS({
3548
3548
  * Removes a value from the document.
3549
3549
  * @returns `true` if the item was found and removed.
3550
3550
  */
3551
- deleteIn(path105) {
3552
- if (Collection.isEmptyPath(path105)) {
3551
+ deleteIn(path106) {
3552
+ if (Collection.isEmptyPath(path106)) {
3553
3553
  if (this.contents == null)
3554
3554
  return false;
3555
3555
  this.contents = null;
3556
3556
  return true;
3557
3557
  }
3558
- return assertCollection(this.contents) ? this.contents.deleteIn(path105) : false;
3558
+ return assertCollection(this.contents) ? this.contents.deleteIn(path106) : false;
3559
3559
  }
3560
3560
  /**
3561
3561
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -3570,10 +3570,10 @@ var require_Document = __commonJS({
3570
3570
  * scalar values from their surrounding node; to disable set `keepScalar` to
3571
3571
  * `true` (collections are always returned intact).
3572
3572
  */
3573
- getIn(path105, keepScalar) {
3574
- if (Collection.isEmptyPath(path105))
3573
+ getIn(path106, keepScalar) {
3574
+ if (Collection.isEmptyPath(path106))
3575
3575
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
3576
- return identity.isCollection(this.contents) ? this.contents.getIn(path105, keepScalar) : void 0;
3576
+ return identity.isCollection(this.contents) ? this.contents.getIn(path106, keepScalar) : void 0;
3577
3577
  }
3578
3578
  /**
3579
3579
  * Checks if the document includes a value with the key `key`.
@@ -3584,10 +3584,10 @@ var require_Document = __commonJS({
3584
3584
  /**
3585
3585
  * Checks if the document includes a value at `path`.
3586
3586
  */
3587
- hasIn(path105) {
3588
- if (Collection.isEmptyPath(path105))
3587
+ hasIn(path106) {
3588
+ if (Collection.isEmptyPath(path106))
3589
3589
  return this.contents !== void 0;
3590
- return identity.isCollection(this.contents) ? this.contents.hasIn(path105) : false;
3590
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path106) : false;
3591
3591
  }
3592
3592
  /**
3593
3593
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -3604,13 +3604,13 @@ var require_Document = __commonJS({
3604
3604
  * Sets a value in this document. For `!!set`, `value` needs to be a
3605
3605
  * boolean to add/remove the item from the set.
3606
3606
  */
3607
- setIn(path105, value) {
3608
- if (Collection.isEmptyPath(path105)) {
3607
+ setIn(path106, value) {
3608
+ if (Collection.isEmptyPath(path106)) {
3609
3609
  this.contents = value;
3610
3610
  } else if (this.contents == null) {
3611
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path105), value);
3611
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path106), value);
3612
3612
  } else if (assertCollection(this.contents)) {
3613
- this.contents.setIn(path105, value);
3613
+ this.contents.setIn(path106, value);
3614
3614
  }
3615
3615
  }
3616
3616
  /**
@@ -5570,9 +5570,9 @@ var require_cst_visit = __commonJS({
5570
5570
  visit.BREAK = BREAK;
5571
5571
  visit.SKIP = SKIP;
5572
5572
  visit.REMOVE = REMOVE;
5573
- visit.itemAtPath = (cst, path105) => {
5573
+ visit.itemAtPath = (cst, path106) => {
5574
5574
  let item = cst;
5575
- for (const [field, index] of path105) {
5575
+ for (const [field, index] of path106) {
5576
5576
  const tok = item?.[field];
5577
5577
  if (tok && "items" in tok) {
5578
5578
  item = tok.items[index];
@@ -5581,23 +5581,23 @@ var require_cst_visit = __commonJS({
5581
5581
  }
5582
5582
  return item;
5583
5583
  };
5584
- visit.parentCollection = (cst, path105) => {
5585
- const parent = visit.itemAtPath(cst, path105.slice(0, -1));
5586
- const field = path105[path105.length - 1][0];
5584
+ visit.parentCollection = (cst, path106) => {
5585
+ const parent = visit.itemAtPath(cst, path106.slice(0, -1));
5586
+ const field = path106[path106.length - 1][0];
5587
5587
  const coll = parent?.[field];
5588
5588
  if (coll && "items" in coll)
5589
5589
  return coll;
5590
5590
  throw new Error("Parent collection not found");
5591
5591
  };
5592
- function _visit(path105, item, visitor) {
5593
- let ctrl = visitor(item, path105);
5592
+ function _visit(path106, item, visitor) {
5593
+ let ctrl = visitor(item, path106);
5594
5594
  if (typeof ctrl === "symbol")
5595
5595
  return ctrl;
5596
5596
  for (const field of ["key", "value"]) {
5597
5597
  const token = item[field];
5598
5598
  if (token && "items" in token) {
5599
5599
  for (let i = 0; i < token.items.length; ++i) {
5600
- const ci = _visit(Object.freeze(path105.concat([[field, i]])), token.items[i], visitor);
5600
+ const ci = _visit(Object.freeze(path106.concat([[field, i]])), token.items[i], visitor);
5601
5601
  if (typeof ci === "number")
5602
5602
  i = ci - 1;
5603
5603
  else if (ci === BREAK)
@@ -5608,10 +5608,10 @@ var require_cst_visit = __commonJS({
5608
5608
  }
5609
5609
  }
5610
5610
  if (typeof ctrl === "function" && field === "key")
5611
- ctrl = ctrl(item, path105);
5611
+ ctrl = ctrl(item, path106);
5612
5612
  }
5613
5613
  }
5614
- return typeof ctrl === "function" ? ctrl(item, path105) : ctrl;
5614
+ return typeof ctrl === "function" ? ctrl(item, path106) : ctrl;
5615
5615
  }
5616
5616
  exports.visit = visit;
5617
5617
  }
@@ -6913,14 +6913,14 @@ var require_parser = __commonJS({
6913
6913
  case "scalar":
6914
6914
  case "single-quoted-scalar":
6915
6915
  case "double-quoted-scalar": {
6916
- const fs102 = this.flowScalar(this.type);
6916
+ const fs103 = this.flowScalar(this.type);
6917
6917
  if (atNextItem || it.value) {
6918
- map.items.push({ start, key: fs102, sep: [] });
6918
+ map.items.push({ start, key: fs103, sep: [] });
6919
6919
  this.onKeyLine = true;
6920
6920
  } else if (it.sep) {
6921
- this.stack.push(fs102);
6921
+ this.stack.push(fs103);
6922
6922
  } else {
6923
- Object.assign(it, { key: fs102, sep: [] });
6923
+ Object.assign(it, { key: fs103, sep: [] });
6924
6924
  this.onKeyLine = true;
6925
6925
  }
6926
6926
  return;
@@ -7048,13 +7048,13 @@ var require_parser = __commonJS({
7048
7048
  case "scalar":
7049
7049
  case "single-quoted-scalar":
7050
7050
  case "double-quoted-scalar": {
7051
- const fs102 = this.flowScalar(this.type);
7051
+ const fs103 = this.flowScalar(this.type);
7052
7052
  if (!it || it.value)
7053
- fc.items.push({ start: [], key: fs102, sep: [] });
7053
+ fc.items.push({ start: [], key: fs103, sep: [] });
7054
7054
  else if (it.sep)
7055
- this.stack.push(fs102);
7055
+ this.stack.push(fs103);
7056
7056
  else
7057
- Object.assign(it, { key: fs102, sep: [] });
7057
+ Object.assign(it, { key: fs103, sep: [] });
7058
7058
  return;
7059
7059
  }
7060
7060
  case "flow-map-end":
@@ -7363,14 +7363,14 @@ var require_dist = __commonJS({
7363
7363
  });
7364
7364
 
7365
7365
  // src/scripts/hooks/dispatch_entry.ts
7366
- import * as path104 from "node:path";
7366
+ import * as path105 from "node:path";
7367
7367
  import { fileURLToPath as fileURLToPath67 } from "node:url";
7368
7368
 
7369
7369
  // src/scripts/hooks/dispatch_hook.ts
7370
7370
  var import_yaml6 = __toESM(require_dist(), 1);
7371
7371
  import { spawnSync as spawnSync10 } from "node:child_process";
7372
- import fs101 from "node:fs";
7373
- import path103 from "node:path";
7372
+ import fs102 from "node:fs";
7373
+ import path104 from "node:path";
7374
7374
  import * as tty2 from "node:tty";
7375
7375
  import { fileURLToPath as fileURLToPath66, pathToFileURL as pathToFileURL59 } from "node:url";
7376
7376
 
@@ -24686,8 +24686,8 @@ function lockfile_path(env) {
24686
24686
  }
24687
24687
  return write_target("installed.lock", { env: env ?? null });
24688
24688
  }
24689
- function read_lockfile(path105) {
24690
- const target = path105 ?? lockfile_path();
24689
+ function read_lockfile(path106) {
24690
+ const target = path106 ?? lockfile_path();
24691
24691
  let text;
24692
24692
  try {
24693
24693
  text = fs51.readFileSync(target, { encoding: "utf-8" });
@@ -25850,12 +25850,39 @@ var ALL_OPS = [
25850
25850
  "pr-merge-auto",
25851
25851
  "tag",
25852
25852
  "release",
25853
- "publish"
25853
+ "publish",
25854
+ "unpublish",
25855
+ "deprecate",
25856
+ "release-asset",
25857
+ "protection",
25858
+ "workflow-toggle",
25859
+ "repo-lifecycle",
25860
+ "review-approve",
25861
+ "force-push",
25862
+ "worktree-remove",
25863
+ "clean-ignored",
25864
+ "tag-force",
25865
+ "rebase",
25866
+ "reset-hard",
25867
+ "clean",
25868
+ "stash-drop",
25869
+ "branch-delete",
25870
+ "close"
25854
25871
  ];
25855
25872
  var PHRASES = [
25856
25873
  { op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
25857
25874
  { op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
25858
- { op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
25875
+ // A CREATION VERB IS REQUIRED, as for `pr-create` above and for the same
25876
+ // reason. The bare noun could not tell an authorization from a mention, and
25877
+ // the mention is the dangerous half: "den branch nicht löschen" names the
25878
+ // object precisely in order to forbid acting on it, yet authorized branch
25879
+ // creation — the negation trails a bare-noun match, so neither the
25880
+ // look-behind nor the span check in `negatedBefore` can reach it. Widened
25881
+ // 2026-09-03 alongside the destructive-coverage work that measured it.
25882
+ {
25883
+ op: "branch",
25884
+ re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|anleg(e|en)?|(er|be)?(ö|oe)ffne|open|create|new|neuer?|starte?|checkout|wechsl?e|switch)\b[^.\n]{0,30}\b(branch|feature-branch|zweig)\b|\b(branch|feature-branch|zweig)\b[^.\n]{0,25}\b(erstellen|anlegen|aufmachen|(ö|oe)ffnen|starten)\b/i
25885
+ },
25859
25886
  // A creation verb is required — "schau dir den PR an" is not "open a PR".
25860
25887
  {
25861
25888
  op: "pr-create",
@@ -25892,7 +25919,111 @@ var PHRASES = [
25892
25919
  op: "release",
25893
25920
  re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
25894
25921
  },
25895
- { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
25922
+ { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i },
25923
+ // ── Authorization vocabulary for the ops added 2026-09-03.
25924
+ //
25925
+ // NON-NEGOTIABLE, and the reason it ships in the same change as the new BLOCK
25926
+ // ops: a blocked operation with no phrase that authorizes it is a dead end.
25927
+ // The guard refuses, the user says "doch, mach das", the classifier records
25928
+ // nothing, and the refusal repeats forever. Every op the guard can block must
25929
+ // have a sentence that unblocks it, or the user's override is a claim rather
25930
+ // than a mechanism.
25931
+ //
25932
+ // Tight on purpose. Under-matching costs one confirmation; over-matching
25933
+ // authorizes something the user did not ask for, so each pattern demands the
25934
+ // object noun rather than a bare verb wherever the verb is ordinary German.
25935
+ //
25936
+ // `unpublish` before nothing in particular — `\bpublish\b` does not match
25937
+ // inside "unpublish" (no word boundary between `n` and `p`), so the two never
25938
+ // collide.
25939
+ // TWO SHAPES EVERY GERMAN PATTERN BELOW MUST CARRY, both learned from tests
25940
+ // that went red rather than from inspection:
25941
+ //
25942
+ // 1. THE IMPERATIVE DROPS ITS `-e`. "entfern die worktree", "archivier das
25943
+ // repo", "deaktivier den workflow" are what people type; `entfern(e|en)`
25944
+ // matches none of them. Every verb ending is therefore `(e|en)?`.
25945
+ // 2. WORD ORDER GOES BOTH WAYS. "archivier das repo" and "das repo
25946
+ // archivieren" are one instruction, and a pattern anchored on the noun
25947
+ // first sees only the second.
25948
+ //
25949
+ // The object noun stays REQUIRED in every pattern that has one. That is what
25950
+ // keeps the loosened verb endings from over-authorizing: "lösch" alone
25951
+ // authorizes nothing, "lösch den branch" authorizes a branch deletion.
25952
+ { op: "unpublish", re: /\bunpublish(e|en)?\b/i },
25953
+ {
25954
+ op: "deprecate",
25955
+ re: /\bdeprecate(d|n)?\b|\bals\s+veraltet\s+markier(e|en)?\b/i
25956
+ },
25957
+ {
25958
+ op: "release-asset",
25959
+ re: /\brelease[-\s]?asset\b|\b(clobber|(ü|ue)berschreib(e|en)?|ersetz(e|en)?|replace|austausch(e|en)?)\b[^.\n]{0,25}\b(asset|artefakt|artifact|tarball|binary)\b|\b(asset|artefakt|artifact)\b[^.\n]{0,25}\b((ü|ue)berschreiben|ersetzen|austauschen|replace)\b/i
25960
+ },
25961
+ // A VERB IS REQUIRED, like `tag` and `release` above and for the same reason.
25962
+ // A bare noun cannot tell an authorization from a mention, and here the
25963
+ // mention is the dangerous half: "die branch-protection nicht anfassen" names
25964
+ // the object precisely in order to forbid touching it. Measured — this was the
25965
+ // one row of the negation corpus that neither the look-behind nor the
25966
+ // span check could reach, because the negation trails a bare-noun match.
25967
+ {
25968
+ op: "protection",
25969
+ re: /\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|deaktivier(e|en)?|nimm|weg|remove|delete|disable|drop|lift)\b[^.\n]{0,30}\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b|\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b[^.\n]{0,30}\b(entfernen|l(ö|oe)schen|deaktivieren|abschalten|remove|delete|disable|drop|lift|weg)\b/i
25970
+ },
25971
+ {
25972
+ op: "workflow-toggle",
25973
+ re: /\bworkflow\b[^.\n]{0,25}\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b|\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b[^.\n]{0,25}\bworkflow\b/i
25974
+ },
25975
+ {
25976
+ op: "repo-lifecycle",
25977
+ re: /\b(repo|repository)\b[^.\n]{0,30}\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b|\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b[^.\n]{0,25}\b(repo|repository)\b|\b(repo|repository)\b[^.\n]{0,30}\bauf\s+(privat|public|öffentlich|private)\b/i
25978
+ },
25979
+ {
25980
+ op: "review-approve",
25981
+ re: /\b(approve|genehmig(e|en)?|freigeb(e|en)?|abnehm(e|en)?)\b[^.\n]{0,30}\b(review|pr|pull[-\s]request)\b|\b(review|pr|pull[-\s]request)\b[^.\n]{0,20}\b(approve|approven|genehmigen|freigeben)\b/i
25982
+ },
25983
+ {
25984
+ op: "force-push",
25985
+ re: /\bforce[-\s]?push(e|en|st)?\b|--force(-with-lease)?\b|\bmit\s+gewalt\s+push(e|en)?\b/i
25986
+ },
25987
+ {
25988
+ op: "worktree-remove",
25989
+ re: /\bworktree\b[^.\n]{0,25}\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete|weg(werfen|r(ä|ae)umen))\b|\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete)\b[^.\n]{0,25}\bworktree\b/i
25990
+ },
25991
+ // The `-x` flag is the whole distinction: without it `git clean` spares
25992
+ // ignored files, with it `.env` and local certificates go too.
25993
+ {
25994
+ op: "clean-ignored",
25995
+ re: /\bgit\s+clean\b[^.\n]{0,20}-[A-Za-z]*x|\b(ignorierte|ignored)\b[^.\n]{0,25}\b(dateien|files)\b[^.\n]{0,25}\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|remove|delete)\b/i
25996
+ },
25997
+ {
25998
+ op: "tag-force",
25999
+ re: /\btag\b[^.\n]{0,25}\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?|umh(ä|ae)ng(e|en)?|force|move)\b|\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?)\b[^.\n]{0,20}\btag\b/i
26000
+ },
26001
+ { op: "rebase", re: /\brebase(n|st|d)?\b/i },
26002
+ {
26003
+ op: "reset-hard",
26004
+ re: /\breset\s+--hard\b|\bhard\s+reset\b|\bhart\s+zur(ü|ue)cksetz(e|en)?\b/i
26005
+ },
26006
+ { op: "clean", re: /\bgit\s+clean\b/i },
26007
+ {
26008
+ op: "stash-drop",
26009
+ re: /\bstash\b[^.\n]{0,25}\b(drop|clear|verwerf(e|en)?|l(ö|oe)sch(e|en)?)\b|\b(verwerf(e|en)?|l(ö|oe)sch(e|en)?|drop)\b[^.\n]{0,20}\bstash\b/i
26010
+ },
26011
+ {
26012
+ op: "branch-delete",
26013
+ re: /\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|delete|remove)\b[^.\n]{0,25}\b(branch|branches|zweig)\b|\b(branch|branches|zweig)\b[^.\n]{0,25}\b(l(ö|oe)schen|entfernen|delete|remove)\b/i
26014
+ },
26015
+ {
26016
+ op: "close",
26017
+ re: /\b(schlie(ß|ss)(e|en)?|close)\b[^.\n]{0,30}\b(pr|pull[-\s]request|issue|ticket)\b|\b(pr|pull[-\s]request|issue|ticket)\b[^.\n]{0,20}\b(schlie(ß|ss)en|close)\b/i
26018
+ },
26019
+ // Enabling auto-merge is its own op on the guard side, so it needs its own
26020
+ // sentence. Without one it is the dead end this block exists to prevent: the
26021
+ // plain merge phrase authorizes `pr-merge` and never `pr-merge-auto`, so the
26022
+ // refusal would have repeated forever.
26023
+ {
26024
+ op: "pr-merge-auto",
26025
+ re: /\bauto[-\s]?merge\b|\bmerge\b[^.\n]{0,20}\bautomatisch\b|\bautomatisch\b[^.\n]{0,20}\bmerg(e|en)\b/i
26026
+ }
25896
26027
  ];
25897
26028
  var OUTPUT_LINE = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|!\s|\?\?\s|\s*\^|[-+]{3}\s|@@\s|\$\s|>\s|\d+\s+(pass|fail)|npm ERR!|Error:|Traceback|at\s+\S+:\d+)/i;
25898
26029
  function isInterrogative(prose) {
@@ -25953,6 +26084,67 @@ function splitFences(prompt) {
25953
26084
  function _commandLines(fence) {
25954
26085
  return fence.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l)).map((l) => l.replace(/^[$>#]\s*/, "")).filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
25955
26086
  }
26087
+ function extractMergeTargets(prompt) {
26088
+ const out = /* @__PURE__ */ new Set();
26089
+ const re = /(?:#|\bpr[- ]?|\bpull[- ]request\s+#?)(\d{1,7})\b/giu;
26090
+ let m;
26091
+ while ((m = re.exec(prompt)) !== null) {
26092
+ const n = Number.parseInt(m[1], 10);
26093
+ if (Number.isSafeInteger(n) && n > 0) {
26094
+ out.add(n);
26095
+ }
26096
+ }
26097
+ return [...out].sort((a, b) => a - b);
26098
+ }
26099
+ var REVOKE_RE = /\b(stop|stopp|halt|abbrechen|abbruch|cancel|revoke|widerruf(en)?|zur(ü|ue)ck(nehmen|ziehen)|hold\s+(everything|on|the\s+merge)|warte|wait)\b|(?:\b(nicht|kein(e|en)?|no|not|dont|don't|never|niemals|nie)\b[^.!?\n]{0,30}\b(merg(e|en|ing)|zusammenf(ü|ue)hren)\b)/iu;
26100
+ function isRevocation(prompt) {
26101
+ return REVOKE_RE.test(prompt);
26102
+ }
26103
+ function _grantId(session_id, at, targets) {
26104
+ return `${sessionSlug(session_id) || "nosession"}:${at}:${targets.join(",")}`;
26105
+ }
26106
+ function foldGrants(prior, prompt, authorized, session_id, now) {
26107
+ if (isRevocation(prompt)) {
26108
+ return [];
26109
+ }
26110
+ const carried = prior.filter((g) => g.consumed.length < g.targets.length);
26111
+ if (!authorized.includes("pr-merge")) {
26112
+ return carried;
26113
+ }
26114
+ const targets = extractMergeTargets(prompt);
26115
+ if (targets.length === 0) {
26116
+ return carried;
26117
+ }
26118
+ const at = now.toISOString();
26119
+ const already = new Set(carried.flatMap((g) => g.targets));
26120
+ const fresh = targets.filter((t) => !already.has(t));
26121
+ if (fresh.length === 0) {
26122
+ return carried;
26123
+ }
26124
+ return [
26125
+ ...carried,
26126
+ {
26127
+ id: _grantId(session_id, at, fresh),
26128
+ op: "pr-merge",
26129
+ targets: fresh,
26130
+ consumed: [],
26131
+ granted_at: at,
26132
+ evidence: prompt.trim().slice(0, 120)
26133
+ }
26134
+ ];
26135
+ }
26136
+ var NEGATION_WORD = /\b(nicht|nichts|kein(e|en|em|er|es)?|niemals|nie|no|not|dont|don't|never|without|ohne)\b/i;
26137
+ function negatedBefore(text, index, matched = "") {
26138
+ let start = 0;
26139
+ for (const mark of [".", "!", "?", "\n"]) {
26140
+ const at = text.lastIndexOf(mark, index - 1);
26141
+ if (at + 1 > start) {
26142
+ start = at + 1;
26143
+ }
26144
+ }
26145
+ const before = text.slice(Math.max(start, index - 30), index);
26146
+ return NEGATION_WORD.test(before) || NEGATION_WORD.test(matched);
26147
+ }
25956
26148
  function classifyAuthorization(prompt) {
25957
26149
  const { prose, fences } = splitFences(prompt);
25958
26150
  const authorized = /* @__PURE__ */ new Set();
@@ -25960,7 +26152,7 @@ function classifyAuthorization(prompt) {
25960
26152
  const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
25961
26153
  for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
25962
26154
  const m = re.exec(instruction);
25963
- if (m) {
26155
+ if (m && !negatedBefore(instruction, m.index, m[0])) {
25964
26156
  authorized.add(op);
25965
26157
  evidence[op] = `prose: "${m[0]}"`;
25966
26158
  }
@@ -25982,6 +26174,68 @@ function classifyAuthorization(prompt) {
25982
26174
  }
25983
26175
  return { authorized: [...authorized], evidence };
25984
26176
  }
26177
+ function readGrants(consumer_root, session_id) {
26178
+ try {
26179
+ const raw = fs55.readFileSync(path55.join(consumer_root, ledgerFileFor(session_id)), "utf8");
26180
+ const decoded = JSON.parse(raw);
26181
+ if (!_isObject8(decoded) || !Array.isArray(decoded["grants"])) {
26182
+ return [];
26183
+ }
26184
+ const owner = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
26185
+ if (session_id && owner && owner !== session_id) {
26186
+ return [];
26187
+ }
26188
+ return decoded["grants"].flatMap((g) => {
26189
+ if (!_isObject8(g)) return [];
26190
+ const op = g["op"];
26191
+ if (typeof op !== "string" || !ALL_OPS.includes(op)) return [];
26192
+ const targets = Array.isArray(g["targets"]) ? g["targets"] : [];
26193
+ const consumed = Array.isArray(g["consumed"]) ? g["consumed"] : [];
26194
+ return [
26195
+ {
26196
+ id: String(g["id"] ?? ""),
26197
+ op,
26198
+ targets: targets.filter((t) => typeof t === "number"),
26199
+ consumed: consumed.filter((t) => typeof t === "number"),
26200
+ granted_at: String(g["granted_at"] ?? ""),
26201
+ evidence: String(g["evidence"] ?? "")
26202
+ }
26203
+ ];
26204
+ });
26205
+ } catch {
26206
+ return [];
26207
+ }
26208
+ }
26209
+ function consumeGrantTarget(consumer_root, session_id, target) {
26210
+ const file = path55.join(consumer_root, ledgerFileFor(session_id));
26211
+ let decoded;
26212
+ try {
26213
+ decoded = JSON.parse(fs55.readFileSync(file, "utf8"));
26214
+ } catch {
26215
+ return;
26216
+ }
26217
+ if (!_isObject8(decoded) || !Array.isArray(decoded["grants"])) {
26218
+ return;
26219
+ }
26220
+ let touched = false;
26221
+ for (const g of decoded["grants"]) {
26222
+ if (!_isObject8(g) || !Array.isArray(g["targets"]) || !Array.isArray(g["consumed"])) {
26223
+ continue;
26224
+ }
26225
+ const targets = g["targets"];
26226
+ const consumed = g["consumed"];
26227
+ if (targets.includes(target) && !consumed.includes(target)) {
26228
+ consumed.push(target);
26229
+ touched = true;
26230
+ }
26231
+ }
26232
+ if (touched) {
26233
+ try {
26234
+ atomic_write_json(file, decoded);
26235
+ } catch {
26236
+ }
26237
+ }
26238
+ }
25985
26239
  function takePending(consumer_root, session_id, now) {
25986
26240
  const file = path55.join(consumer_root, pendingFileFor(session_id));
25987
26241
  let raw;
@@ -26050,12 +26304,24 @@ function run10(stdin_text, options) {
26050
26304
  authorized.push(pending.op);
26051
26305
  evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
26052
26306
  }
26307
+ const now = /* @__PURE__ */ new Date();
26053
26308
  const ledger = {
26054
26309
  session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
26055
- detected_at: (/* @__PURE__ */ new Date()).toISOString(),
26310
+ detected_at: now.toISOString(),
26056
26311
  authorized,
26057
26312
  evidence,
26058
- prompt_chars: prompt.length
26313
+ prompt_chars: prompt.length,
26314
+ // Grants survive this write; `authorized` does not. That asymmetry IS the
26315
+ // decision. A bare operation name stays one-shot exactly as `commit-policy`
26316
+ // requires, and only an authorization that also froze its objects earns a
26317
+ // life longer than the turn. See ADR-252.
26318
+ grants: foldGrants(
26319
+ readGrants(options.consumer_root, session),
26320
+ prompt,
26321
+ authorized,
26322
+ session,
26323
+ now
26324
+ )
26059
26325
  };
26060
26326
  try {
26061
26327
  atomic_write_json(path55.join(options.consumer_root, ledgerFileFor(session)), ledger);
@@ -26104,6 +26370,95 @@ if (_isCliEntry24()) {
26104
26370
  import fs56 from "node:fs";
26105
26371
  import path56 from "node:path";
26106
26372
  import { fileURLToPath as fileURLToPath34, pathToFileURL as pathToFileURL27 } from "node:url";
26373
+
26374
+ // src/scripts/hooks/merge_impact.ts
26375
+ import { execFileSync as execFileSync3 } from "node:child_process";
26376
+ var DESTRUCTIVE_MARKERS = [
26377
+ // Schema removal. Matched on ADDED lines only — a migration that *removes* a
26378
+ // dropColumn call is the opposite of destructive, and matching the raw patch
26379
+ // would score it the same way.
26380
+ { label: "migration drops a column or table", re: /^\+.*\b(dropColumn|dropTable|dropIfExists|removeColumn|drop_column|drop_table)\b/im },
26381
+ { label: "raw DROP or TRUNCATE in a migration", re: /^\+.*\b(DROP\s+(TABLE|COLUMN|INDEX|CONSTRAINT)|TRUNCATE\s+TABLE)\b/im },
26382
+ // The author said so themselves. Conventional Commits marks a breaking change
26383
+ // with a `!` before the colon or a body trailer.
26384
+ { label: "author marked a BREAKING CHANGE", re: /^\+.*BREAKING[ -]CHANGE/m },
26385
+ // A removed export is the shape most likely to break an importer.
26386
+ { label: "an exported symbol was removed", re: /^-\s*export\s+(default\s+|const\s+|function\s+|class\s+|interface\s+|type\s+|async\s+)/m },
26387
+ { label: "a public PHP or Python symbol was removed", re: /^-\s*(public\s+function|def\s+[a-z_]+\s*\()/m },
26388
+ // Route removal breaks callers that never imported anything.
26389
+ { label: "a route or endpoint was removed", re: /^-.*\b(Route::(get|post|put|patch|delete)|@(Get|Post|Put|Patch|Delete)Mapping|app\.(get|post|put|patch|delete)\s*\()/m },
26390
+ { label: "a whole file was deleted", re: /^deleted file mode /m }
26391
+ ];
26392
+ var MAJOR_BUMP = /^-\s*"version"\s*:\s*"(\d+)\.[^"]*"[\s\S]{0,200}?^\+\s*"version"\s*:\s*"(\d+)\./m;
26393
+ function classifyDiff(patch) {
26394
+ if (patch.trim() === "") {
26395
+ return { verdict: "undecidable", markers: [], reason: "empty diff" };
26396
+ }
26397
+ const markers = [];
26398
+ for (const { label, re } of DESTRUCTIVE_MARKERS) {
26399
+ if (re.test(patch)) {
26400
+ markers.push(label);
26401
+ }
26402
+ }
26403
+ const bump = MAJOR_BUMP.exec(patch);
26404
+ if (bump && bump[1] !== bump[2]) {
26405
+ markers.push(`major version bump ${bump[1]}.x to ${bump[2]}.x`);
26406
+ }
26407
+ const filesChanged = (patch.match(/^diff --git /gm) ?? []).length;
26408
+ if (markers.length > 0) {
26409
+ return { verdict: "destructive", markers, filesChanged };
26410
+ }
26411
+ return { verdict: "additive", markers: [], filesChanged };
26412
+ }
26413
+ function fetchPatch(pr, opts = {}) {
26414
+ const run13 = opts.exec ?? execFileSync3;
26415
+ const cwd = opts.cwd ?? process.cwd();
26416
+ const capture = (file, args) => run13(file, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
26417
+ for (const ref of [`refs/pull/${pr}/head`, `pull/${pr}/head`]) {
26418
+ try {
26419
+ const base = capture("git", ["merge-base", "HEAD", ref]).trim();
26420
+ if (base) {
26421
+ const patch = capture("git", ["diff", `${base}...${ref}`]);
26422
+ if (patch.trim() !== "") {
26423
+ return { patch, source: "local" };
26424
+ }
26425
+ }
26426
+ } catch {
26427
+ }
26428
+ }
26429
+ if (opts.allowNetwork === false) {
26430
+ return { patch: "", source: "none", reason: "no local ref and network reads disabled" };
26431
+ }
26432
+ try {
26433
+ const patch = capture("gh", ["pr", "diff", String(pr), "--patch"]);
26434
+ if (patch.trim() !== "") {
26435
+ return { patch, source: "gh" };
26436
+ }
26437
+ return { patch: "", source: "none", reason: "gh returned an empty diff" };
26438
+ } catch {
26439
+ return { patch: "", source: "none", reason: "no local ref, and gh pr diff failed" };
26440
+ }
26441
+ }
26442
+ function analyseMergeImpact(pr, opts = {}) {
26443
+ const got = fetchPatch(pr, opts);
26444
+ if (got.source === "none") {
26445
+ return { verdict: "undecidable", markers: [], ...got.reason ? { reason: got.reason } : {} };
26446
+ }
26447
+ return classifyDiff(got.patch);
26448
+ }
26449
+ function describeImpact(pr, impact) {
26450
+ const say = `Say "merge #${pr}" to authorize it.`;
26451
+ switch (impact.verdict) {
26452
+ case "additive":
26453
+ return `Impact scan: PR #${pr} touches ${impact.filesChanged ?? 0} file(s) and trips no destructive marker \u2014 no schema drop, no removed export or route, no major bump, no BREAKING CHANGE. Additive as far as the diff can show, which is not the same as safe to merge: the base has moved since review and merging may trigger a deploy. ${say}`;
26454
+ case "destructive":
26455
+ return `Impact scan: PR #${pr} is NOT additive \u2014 ${impact.markers.join("; ")}. Read the diff before authorizing. ${say}`;
26456
+ default:
26457
+ return `Impact scan: could not read the diff for PR #${pr} (${impact.reason ?? "unknown"}), so nothing is known about what it changes. ${say}`;
26458
+ }
26459
+ }
26460
+
26461
+ // src/scripts/hooks/block_unauthorized_git.ts
26107
26462
  var EXIT_ALLOW11 = 0;
26108
26463
  var EXIT_BLOCK = 1;
26109
26464
  function _isObject9(v) {
@@ -26116,13 +26471,52 @@ var BLOCK_OPS = /* @__PURE__ */ new Set([
26116
26471
  "pr-merge",
26117
26472
  // Enabling auto-merge commits the outcome to a condition the agent does not
26118
26473
  // control, so it is blocked on the same terms as the merge it schedules.
26119
- "pr-merge-auto"
26474
+ "pr-merge-auto",
26475
+ // ── Added 2026-09-03. Every one is already never-autonomous in the
26476
+ // `non-destructive-by-default` prose; the measurement that drove it is in the
26477
+ // `GitOp` union's own note — 17 of 25 borderline operations classified as
26478
+ // NOTHING, including three worse than anything the guard already caught.
26479
+ /** The undo that is more destructive than the act: lockfiles break. */
26480
+ "unpublish",
26481
+ /** Metadata only, and every install on earth starts warning. */
26482
+ "deprecate",
26483
+ /** Replaces bytes consumers already hold a checksum for. */
26484
+ "release-asset",
26485
+ /** Deletes the guard rather than the data — everything after is unguarded. */
26486
+ "protection",
26487
+ /** No data lost; the gate that protects everything downstream is off. */
26488
+ "workflow-toggle",
26489
+ /** Reach outside the repository, and mostly one-way. */
26490
+ "repo-lifecycle",
26491
+ /** No code moves, and an armed auto-merge is released. */
26492
+ "review-approve",
26493
+ /** Discards commits that landed after the last fetch, on a shared ref. */
26494
+ "force-push",
26495
+ /** Destroys uncommitted work belonging to a session this one cannot see. */
26496
+ "worktree-remove",
26497
+ /** `-x` is the flag that takes `.env` and local certificates. */
26498
+ "clean-ignored"
26120
26499
  ]);
26121
26500
  var WARN_OPS = /* @__PURE__ */ new Set([
26122
26501
  "commit",
26123
26502
  "push",
26124
26503
  "pr-create",
26125
- "branch"
26504
+ "branch",
26505
+ // ── Added 2026-09-03, deliberately at warn rather than block.
26506
+ //
26507
+ // Each is destructive in some reading, and each is also routine enough that
26508
+ // blocking it would be the friction that gets the whole guard routed around —
26509
+ // the failure mode this estate has already recorded twice. `reset-hard` is the
26510
+ // sharpest case and stays here for a decidable reason: the Hard Floor's
26511
+ // qualifier is "past unpushed work", and the command text cannot say whether
26512
+ // the work was pushed.
26513
+ "tag-force",
26514
+ "rebase",
26515
+ "reset-hard",
26516
+ "clean",
26517
+ "stash-drop",
26518
+ "branch-delete",
26519
+ "close"
26126
26520
  ]);
26127
26521
  var G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
26128
26522
  var P = "^(?:\\S*\\/)?";
@@ -26168,6 +26562,72 @@ var COMMAND_OPS = [
26168
26562
  op: "pr-create",
26169
26563
  re: new RegExp(`${P}gh\\s+pr\\s+create\\b|${ghApiWrite("\\/pulls\\b").source}`, "i")
26170
26564
  },
26565
+ // ── Added 2026-09-03. Placement inside this table is as load-bearing as the
26566
+ // patterns: the loop takes the MOST SEVERE match per segment, but within one
26567
+ // op the first pattern wins, so anything more specific than a plain `git push`
26568
+ // has to sit above it.
26569
+ // `npm unpublish` is NOT reached by the publish pattern — `\bpublish\b` needs
26570
+ // a word boundary that `unpublish` does not provide between `n` and `p`. That
26571
+ // is why it classified as nothing for as long as it did.
26572
+ { op: "unpublish", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}unpublish\\b`, "i") },
26573
+ { op: "deprecate", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}deprecate\\b`, "i") },
26574
+ {
26575
+ op: "release-asset",
26576
+ re: new RegExp(`${P}gh\\s+release\\s+(upload|edit|delete)\\b`, "i")
26577
+ },
26578
+ {
26579
+ op: "protection",
26580
+ re: new RegExp(
26581
+ `${P}gh\\s+api\\b(?=[^\\n]*(?:protection|rulesets?)\\b)(?=[^\\n]*(?:-X|--method)\\s+(?:DELETE|PUT|PATCH|POST)\\b)|${P}gh\\s+ruleset\\s+delete\\b`,
26582
+ "i"
26583
+ )
26584
+ },
26585
+ {
26586
+ op: "workflow-toggle",
26587
+ re: new RegExp(`${P}gh\\s+workflow\\s+(disable|enable)\\b`, "i")
26588
+ },
26589
+ {
26590
+ op: "repo-lifecycle",
26591
+ re: new RegExp(
26592
+ `${P}gh\\s+repo\\s+(archive|delete|unarchive)\\b|${P}gh\\s+repo\\s+edit\\b(?=[^\\n]*--visibility\\b)`,
26593
+ "i"
26594
+ )
26595
+ },
26596
+ {
26597
+ op: "review-approve",
26598
+ re: new RegExp(`${P}gh\\s+pr\\s+review\\b(?=[^\\n]*--approve\\b)`, "i")
26599
+ },
26600
+ // Above `push`, and both spellings: `--force` and `--force-with-lease`. The
26601
+ // "safe force" is in the same class here because what it is safe against is a
26602
+ // stale local view, not a collaborator who pushed after your fetch.
26603
+ {
26604
+ op: "force-push",
26605
+ re: new RegExp(`${P}git\\s+${G}push\\b[^\\n;|&]*(--force\\b|--force-with-lease\\b|\\s-f\\b)`, "i")
26606
+ },
26607
+ {
26608
+ op: "worktree-remove",
26609
+ re: new RegExp(`${P}git\\s+worktree\\s+remove\\b[^\\n;|&]*(--force\\b|\\s-f\\b)`, "i")
26610
+ },
26611
+ // `-x` (or `-X`) anywhere in the flag cluster is what reaches ignored files.
26612
+ {
26613
+ op: "clean-ignored",
26614
+ re: new RegExp(`${P}git\\s+clean\\b[^\\n;|&]*-[A-Za-z]*x`, "i")
26615
+ },
26616
+ { op: "tag-force", re: new RegExp(`${P}git\\s+tag\\b[^\\n;|&]*(-f\\b|--force\\b)`, "i") },
26617
+ { op: "rebase", re: new RegExp(`${P}git\\s+${G}rebase\\b`, "i") },
26618
+ { op: "reset-hard", re: new RegExp(`${P}git\\s+${G}reset\\b[^\\n;|&]*--hard\\b`, "i") },
26619
+ { op: "clean", re: new RegExp(`${P}git\\s+clean\\b`, "i") },
26620
+ { op: "stash-drop", re: new RegExp(`${P}git\\s+stash\\s+(drop|clear)\\b`, "i") },
26621
+ // Remote deletion and the local `-D` are one op: both remove a ref, and the
26622
+ // remote form additionally closes any open PR pointing at it.
26623
+ {
26624
+ op: "branch-delete",
26625
+ re: new RegExp(
26626
+ `${P}git\\s+branch\\b[^\\n;|&]*(-D\\b|--delete\\b)|${P}git\\s+${G}push\\b[^\\n;|&]*(--delete\\b|\\s:\\S)`,
26627
+ "i"
26628
+ )
26629
+ },
26630
+ { op: "close", re: new RegExp(`${P}gh\\s+(pr|issue)\\s+close\\b`, "i") },
26171
26631
  { op: "push", re: new RegExp(`${P}git\\s+${G}push\\b`, "i") },
26172
26632
  { op: "commit", re: new RegExp(`${P}git\\s+${G}commit\\b`, "i") },
26173
26633
  { op: "branch", re: new RegExp(`${P}git\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
@@ -26360,8 +26820,25 @@ function commandOp(command) {
26360
26820
  }
26361
26821
  return found;
26362
26822
  }
26823
+ function mergeTargetOf(command) {
26824
+ const gh = /\bgh\s+pr\s+merge\s+(?:-{1,2}\S+\s+)*?(\d{1,7})\b/i.exec(command);
26825
+ if (gh) {
26826
+ return Number.parseInt(gh[1], 10);
26827
+ }
26828
+ const api = /\/pulls\/(\d{1,7})\/merge\b/i.exec(command);
26829
+ return api ? Number.parseInt(api[1], 10) : null;
26830
+ }
26831
+ function grantCovers(grants, op, target) {
26832
+ if (op !== "pr-merge" || target === null) {
26833
+ return null;
26834
+ }
26835
+ return grants.find(
26836
+ (g) => g.op === "pr-merge" && g.targets.includes(target) && !g.consumed.includes(target)
26837
+ ) ?? null;
26838
+ }
26363
26839
  var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
26364
26840
  function _loadLedger(consumer_root, session_id, now) {
26841
+ const grants = readGrants(consumer_root, session_id);
26365
26842
  const candidates = [ledgerFileFor(session_id)];
26366
26843
  if (!candidates.includes(STATE_FILE7)) {
26367
26844
  candidates.push(STATE_FILE7);
@@ -26369,10 +26846,10 @@ function _loadLedger(consumer_root, session_id, now) {
26369
26846
  for (const rel of candidates) {
26370
26847
  const found = _readLedgerFile(path56.join(consumer_root, rel), session_id, now);
26371
26848
  if (found !== null) {
26372
- return found;
26849
+ return { ...found, grants };
26373
26850
  }
26374
26851
  }
26375
- return { authorized: /* @__PURE__ */ new Set(), present: false };
26852
+ return { authorized: /* @__PURE__ */ new Set(), present: false, grants };
26376
26853
  }
26377
26854
  function _readLedgerFile(file, session_id, now) {
26378
26855
  try {
@@ -26396,7 +26873,7 @@ function _readLedgerFile(file, session_id, now) {
26396
26873
  }
26397
26874
  return null;
26398
26875
  }
26399
- function decide(command, ledger) {
26876
+ function decide(command, ledger, impact) {
26400
26877
  if (command === null) {
26401
26878
  return { exit: EXIT_ALLOW11, stdout: "", stderr: "" };
26402
26879
  }
@@ -26404,9 +26881,14 @@ function decide(command, ledger) {
26404
26881
  if (op === null || ledger.authorized.has(op)) {
26405
26882
  return { exit: EXIT_ALLOW11, stdout: "", stderr: "" };
26406
26883
  }
26884
+ const target = mergeTargetOf(command);
26885
+ if (grantCovers(ledger.grants ?? [], op, target) && target !== null) {
26886
+ return { exit: EXIT_ALLOW11, stdout: "", stderr: "", consumedTarget: target };
26887
+ }
26407
26888
  const shown = command.length > 120 ? `${command.slice(0, 117)}\u2026` : command;
26408
26889
  if (BLOCK_OPS.has(op)) {
26409
- const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}. Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
26890
+ const scan = op === "pr-merge" && target !== null && impact ? ` ${impact}` : "";
26891
+ const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}.${scan} Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
26410
26892
  return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
26411
26893
  `, blockedOp: op };
26412
26894
  }
@@ -26434,10 +26916,24 @@ function run11(stdin_text, options) {
26434
26916
  }
26435
26917
  }
26436
26918
  const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
26437
- const decision = decide(
26438
- extractCommand(envelope),
26439
- _loadLedger(options.consumer_root, session_id, Date.now())
26440
- );
26919
+ const command = extractCommand(envelope);
26920
+ const ledger = _loadLedger(options.consumer_root, session_id, Date.now());
26921
+ let impact;
26922
+ const stage1 = command === null ? null : commandOp(command);
26923
+ const stage2Target = command === null ? null : mergeTargetOf(command);
26924
+ if (stage1 === "pr-merge" && stage2Target !== null && !ledger.authorized.has("pr-merge") && !grantCovers(ledger.grants ?? [], "pr-merge", stage2Target)) {
26925
+ try {
26926
+ impact = describeImpact(
26927
+ stage2Target,
26928
+ analyseMergeImpact(stage2Target, { cwd: options.consumer_root })
26929
+ );
26930
+ } catch {
26931
+ }
26932
+ }
26933
+ const decision = decide(command, ledger, impact);
26934
+ if (decision.consumedTarget !== void 0) {
26935
+ consumeGrantTarget(options.consumer_root, session_id, decision.consumedTarget);
26936
+ }
26441
26937
  if (decision.blockedOp) {
26442
26938
  try {
26443
26939
  atomic_write_json(path56.join(options.consumer_root, pendingFileFor(session_id)), {
@@ -29177,7 +29673,7 @@ import path67 from "node:path";
29177
29673
  import { fileURLToPath as fileURLToPath43, pathToFileURL as pathToFileURL36 } from "node:url";
29178
29674
 
29179
29675
  // src/scripts/code_graph/detect.ts
29180
- import { execFileSync as execFileSync3 } from "node:child_process";
29676
+ import { execFileSync as execFileSync4 } from "node:child_process";
29181
29677
  import * as fs66 from "node:fs";
29182
29678
  import * as path66 from "node:path";
29183
29679
 
@@ -29228,7 +29724,7 @@ function validateGraph(g) {
29228
29724
  var CONSUMER_CANDIDATES = ["graph.json", "code-graph.json", ".code-graph/graph.json"];
29229
29725
  function gitLastCommitEpoch(root) {
29230
29726
  try {
29231
- const out = execFileSync3("git", ["-C", root, "log", "-1", "--format=%ct"], {
29727
+ const out = execFileSync4("git", ["-C", root, "log", "-1", "--format=%ct"], {
29232
29728
  env: hardenedSpawnEnv(),
29233
29729
  encoding: "utf-8",
29234
29730
  stdio: ["ignore", "pipe", "ignore"]
@@ -29241,7 +29737,7 @@ function gitLastCommitEpoch(root) {
29241
29737
  }
29242
29738
  function commitsBehind(root, sha) {
29243
29739
  try {
29244
- const out = execFileSync3("git", ["-C", root, "rev-list", "--count", `${sha}..HEAD`], {
29740
+ const out = execFileSync4("git", ["-C", root, "rev-list", "--count", `${sha}..HEAD`], {
29245
29741
  env: hardenedSpawnEnv(),
29246
29742
  encoding: "utf-8",
29247
29743
  stdio: ["ignore", "pipe", "ignore"]
@@ -33893,6 +34389,61 @@ function changedFiles(root, base) {
33893
34389
  const dirty = m.status === 0 ? m.stdout.split("\n") : [];
33894
34390
  return [.../* @__PURE__ */ new Set([...tracked, ...untracked, ...dirty])].filter((f) => f.trim().length > 0);
33895
34391
  }
34392
+ function addedLineMap(root, base) {
34393
+ const out = /* @__PURE__ */ new Map();
34394
+ const u = spawnSync9("git", ["ls-files", "--others", "--exclude-standard"], {
34395
+ cwd: root,
34396
+ encoding: "utf-8"
34397
+ });
34398
+ if (u.status === 0) {
34399
+ for (const f of u.stdout.split("\n")) {
34400
+ if (f.trim()) out.set(f.trim(), "all");
34401
+ }
34402
+ }
34403
+ for (const argv of [
34404
+ ["diff", "--unified=0", "--diff-filter=ACMR", `${base}...HEAD`],
34405
+ ["diff", "--unified=0", "--diff-filter=ACMR"]
34406
+ ]) {
34407
+ const r = spawnSync9("git", argv, { cwd: root, encoding: "utf-8" });
34408
+ if (r.status !== 0) continue;
34409
+ mergeAddedLines(out, parseAddedLines(r.stdout));
34410
+ }
34411
+ return out;
34412
+ }
34413
+ function parseAddedLines(diffText) {
34414
+ const out = /* @__PURE__ */ new Map();
34415
+ let file = null;
34416
+ for (const line of diffText.split("\n")) {
34417
+ if (line.startsWith("+++ ")) {
34418
+ const p = line.slice(4).trim();
34419
+ file = p === "/dev/null" ? null : p.replace(/^b\//, "");
34420
+ continue;
34421
+ }
34422
+ if (!file || !line.startsWith("@@")) continue;
34423
+ const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
34424
+ if (!m) continue;
34425
+ const start = Number.parseInt(m[1], 10);
34426
+ const count = m[2] === void 0 ? 1 : Number.parseInt(m[2], 10);
34427
+ const set = out.get(file) ?? /* @__PURE__ */ new Set();
34428
+ for (let n = start; n < start + count; n += 1) set.add(n);
34429
+ out.set(file, set);
34430
+ }
34431
+ return out;
34432
+ }
34433
+ function mergeAddedLines(into, from) {
34434
+ for (const [file, lines] of from) {
34435
+ const prev = into.get(file);
34436
+ if (prev === "all") continue;
34437
+ const set = prev ?? /* @__PURE__ */ new Set();
34438
+ for (const n of lines) set.add(n);
34439
+ into.set(file, set);
34440
+ }
34441
+ }
34442
+ function findingsOnAddedLines(findings, lines) {
34443
+ if (lines === void 0) return [];
34444
+ if (lines === "all") return [...findings];
34445
+ return findings.filter((f) => lines.has(f.line));
34446
+ }
33896
34447
  var COMMENT_CASES = [
33897
34448
  {
33898
34449
  name: "a block-comment continuation carrying no leading star is flagged",
@@ -34070,6 +34621,7 @@ ${HELP}`);
34070
34621
  return 1;
34071
34622
  }
34072
34623
  const candidates = (explicit.length > 0 ? explicit : changedFiles(root, base)).filter(isScannable);
34624
+ const added = explicit.length > 0 ? null : addedLineMap(root, base);
34073
34625
  const findings = [];
34074
34626
  for (const rel of candidates) {
34075
34627
  const abs = path85.isAbsolute(rel) ? rel : path85.join(root, rel);
@@ -34079,7 +34631,8 @@ ${HELP}`);
34079
34631
  } catch {
34080
34632
  continue;
34081
34633
  }
34082
- findings.push(...scanText2(rel, text));
34634
+ const all = scanText2(rel, text);
34635
+ findings.push(...added ? findingsOnAddedLines(all, added.get(rel)) : all);
34083
34636
  }
34084
34637
  try {
34085
34638
  reportScanned({
@@ -34386,8 +34939,8 @@ if (_isCliEntry44()) {
34386
34939
  }
34387
34940
 
34388
34941
  // src/scripts/hooks/session_eol_hook.ts
34389
- import * as fs89 from "node:fs";
34390
- import * as path91 from "node:path";
34942
+ import * as fs91 from "node:fs";
34943
+ import * as path93 from "node:path";
34391
34944
  import process15 from "node:process";
34392
34945
  import { fileURLToPath as fileURLToPath59, pathToFileURL as pathToFileURL52 } from "node:url";
34393
34946
 
@@ -34561,9 +35114,112 @@ function readNewCompleteLines(filePath, fromByte) {
34561
35114
  }
34562
35115
  }
34563
35116
 
34564
- // src/scripts/_lib/run_checkpoint.ts
35117
+ // src/scripts/_lib/context_observation.ts
35118
+ import * as fs89 from "node:fs";
35119
+ import * as path91 from "node:path";
35120
+
35121
+ // src/scripts/_lib/loop_guards.ts
34565
35122
  import * as fs88 from "node:fs";
34566
35123
  import * as path90 from "node:path";
35124
+ function atomicWriteJson(file, value) {
35125
+ const tmp = `${file}.${String(process.pid)}.tmp`;
35126
+ try {
35127
+ fs88.mkdirSync(path90.dirname(file), { recursive: true });
35128
+ fs88.writeFileSync(tmp, `${JSON.stringify(value)}
35129
+ `, "utf8");
35130
+ fs88.renameSync(tmp, file);
35131
+ return true;
35132
+ } catch {
35133
+ try {
35134
+ fs88.unlinkSync(tmp);
35135
+ } catch {
35136
+ }
35137
+ return false;
35138
+ }
35139
+ }
35140
+ var DEPENDENCY_PATTERNS = [
35141
+ { kind: "binary", re: /\b(?:command not found|is not recognized as an internal or external command)\b/i },
35142
+ { kind: "credential", re: /\b(?:not (?:authenticated|logged in)|authentication (?:failed|required)|no credentials found)\b/i },
35143
+ { kind: "credential", re: /\b(?:missing|unset|not set)\b[^\n]{0,40}\b(?:credential|token|api[_ -]?key|secret)\b/i },
35144
+ { kind: "permission", re: /\b(?:permission denied|forbidden|403 forbidden)\b/i },
35145
+ { kind: "quota", re: /\b(?:rate limit(?:ed| exceeded| reached)?|quota exceeded|429 too many requests|spend (?:cap|limit) reached)\b/i },
35146
+ { kind: "service", re: /\b(?:5\d\d (?:internal )?server error|service unavailable|connection refused)\b/i }
35147
+ ];
35148
+ var DEPENDENCY_SCAN_BYTES = 64 * 1024;
35149
+ function detectUnavailableDependency(transcriptTail, tailLines = 200) {
35150
+ const lines = transcriptTail.split("\n");
35151
+ const window = lines.slice(Math.max(0, lines.length - tailLines));
35152
+ for (const line of window) {
35153
+ for (const { kind, re } of DEPENDENCY_PATTERNS) {
35154
+ if (re.test(line)) {
35155
+ return { kind, evidence: line.trim().slice(0, 200) };
35156
+ }
35157
+ }
35158
+ }
35159
+ return null;
35160
+ }
35161
+ function stallSignal(history, window = 3) {
35162
+ if (history.length === 0) {
35163
+ return { level: "flat", flatRun: 0, minimum: null, newMinimum: false };
35164
+ }
35165
+ const last = history[history.length - 1];
35166
+ const minimum = Math.min(...history);
35167
+ const earlier = history.slice(0, -1);
35168
+ const newMinimum = earlier.length > 0 && last < Math.min(...earlier);
35169
+ let flatRun = 1;
35170
+ for (let i = history.length - 2; i >= 0; i -= 1) {
35171
+ if (history[i] !== last) break;
35172
+ flatRun += 1;
35173
+ }
35174
+ if (newMinimum) return { level: "progressing", flatRun, minimum, newMinimum };
35175
+ if (flatRun >= window) return { level: "stalled", flatRun, minimum, newMinimum };
35176
+ return { level: "flat", flatRun, minimum, newMinimum };
35177
+ }
35178
+ function readUnavailableDependency(transcriptPath) {
35179
+ try {
35180
+ const raw = fs88.readFileSync(transcriptPath, "utf8");
35181
+ return detectUnavailableDependency(raw.slice(-DEPENDENCY_SCAN_BYTES));
35182
+ } catch {
35183
+ return null;
35184
+ }
35185
+ }
35186
+
35187
+ // src/scripts/_lib/context_observation.ts
35188
+ var CONTEXT_OBSERVATION_REL = path91.join(
35189
+ "agents",
35190
+ "runtime",
35191
+ "state",
35192
+ "context-observation.json"
35193
+ );
35194
+ function observationFile(repoRoot) {
35195
+ return path91.join(repoRoot, CONTEXT_OBSERVATION_REL);
35196
+ }
35197
+ function readContextObservation(repoRoot) {
35198
+ try {
35199
+ const parsed = JSON.parse(fs89.readFileSync(observationFile(repoRoot), "utf-8"));
35200
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
35201
+ const o = parsed;
35202
+ if (typeof o["fingerprint"] !== "string" || o["fingerprint"] === "") return null;
35203
+ if (typeof o["at"] !== "string") return null;
35204
+ return {
35205
+ schema_version: 1,
35206
+ roadmap: typeof o["roadmap"] === "string" ? o["roadmap"] : "",
35207
+ fingerprint: o["fingerprint"],
35208
+ at: o["at"]
35209
+ };
35210
+ } catch {
35211
+ return null;
35212
+ }
35213
+ }
35214
+ function premiseMoved(claimed, observed) {
35215
+ if (claimed === null || claimed === void 0 || claimed === "") return false;
35216
+ if (observed === null || observed === void 0 || observed === "") return false;
35217
+ return claimed !== observed;
35218
+ }
35219
+
35220
+ // src/scripts/_lib/run_checkpoint.ts
35221
+ import * as fs90 from "node:fs";
35222
+ import * as path92 from "node:path";
34567
35223
 
34568
35224
  // src/scripts/_lib/roadmap_checkboxes.ts
34569
35225
  var CHECKBOX_LINE = /^[ \t]*[-*][ \t]+\[([ xX~-])\][ \t]+(.*)$/;
@@ -34593,7 +35249,7 @@ function phaseLines(text) {
34593
35249
  }
34594
35250
 
34595
35251
  // src/scripts/_lib/run_checkpoint.ts
34596
- var CHECKPOINT_DIR_REL = path90.join("agents", "runtime", "state", "checkpoints");
35252
+ var CHECKPOINT_DIR_REL = path92.join("agents", "runtime", "state", "checkpoints");
34597
35253
  function countRoadmap(text) {
34598
35254
  const counts = { open: 0, done: 0, parked: 0, next: null };
34599
35255
  for (const raw of phaseLines(text)) {
@@ -34614,34 +35270,34 @@ function countRoadmap(text) {
34614
35270
  return counts;
34615
35271
  }
34616
35272
  function checkpointFile(repoRoot, runId) {
34617
- return path90.join(repoRoot, CHECKPOINT_DIR_REL, `${runId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
35273
+ return path92.join(repoRoot, CHECKPOINT_DIR_REL, `${runId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
34618
35274
  }
34619
35275
  function roadmapPath(repoRoot, slug) {
34620
- return path90.join(repoRoot, "agents", "roadmaps", `${slug}.md`);
35276
+ return path92.join(repoRoot, "agents", "roadmaps", `${slug}.md`);
34621
35277
  }
34622
35278
  function readHead(repoRoot) {
34623
35279
  try {
34624
- let gitDir = path90.join(repoRoot, ".git");
34625
- if (fs88.statSync(gitDir).isFile()) {
34626
- const m = /^gitdir:\s*(.+)$/m.exec(fs88.readFileSync(gitDir, "utf-8").trim());
35280
+ let gitDir = path92.join(repoRoot, ".git");
35281
+ if (fs90.statSync(gitDir).isFile()) {
35282
+ const m = /^gitdir:\s*(.+)$/m.exec(fs90.readFileSync(gitDir, "utf-8").trim());
34627
35283
  if (m === null) return null;
34628
- gitDir = path90.resolve(repoRoot, m[1]);
35284
+ gitDir = path92.resolve(repoRoot, m[1]);
34629
35285
  }
34630
35286
  let refRoot = gitDir;
34631
35287
  try {
34632
- const common = fs88.readFileSync(path90.join(gitDir, "commondir"), "utf-8").trim();
34633
- if (common !== "") refRoot = path90.resolve(gitDir, common);
35288
+ const common = fs90.readFileSync(path92.join(gitDir, "commondir"), "utf-8").trim();
35289
+ if (common !== "") refRoot = path92.resolve(gitDir, common);
34634
35290
  } catch {
34635
35291
  }
34636
- const head = fs88.readFileSync(path90.join(gitDir, "HEAD"), "utf-8").trim();
35292
+ const head = fs90.readFileSync(path92.join(gitDir, "HEAD"), "utf-8").trim();
34637
35293
  if (/^[0-9a-f]{40}$/.test(head)) return head;
34638
35294
  const ref = /^ref:\s*(.+)$/.exec(head);
34639
35295
  if (ref === null) return null;
34640
35296
  const refName = ref[1];
34641
35297
  try {
34642
- return fs88.readFileSync(path90.join(refRoot, refName), "utf-8").trim() || null;
35298
+ return fs90.readFileSync(path92.join(refRoot, refName), "utf-8").trim() || null;
34643
35299
  } catch {
34644
- const packed = fs88.readFileSync(path90.join(refRoot, "packed-refs"), "utf-8");
35300
+ const packed = fs90.readFileSync(path92.join(refRoot, "packed-refs"), "utf-8");
34645
35301
  for (const line of packed.split("\n")) {
34646
35302
  const pm = /^([0-9a-f]{40})\s+(.+)$/.exec(line.trim());
34647
35303
  if (pm !== null && pm[2] === refName) return pm[1];
@@ -34655,7 +35311,7 @@ function readHead(repoRoot) {
34655
35311
  function buildCheckpoint(repoRoot, runId, slug, opts = {}) {
34656
35312
  let text;
34657
35313
  try {
34658
- text = fs88.readFileSync(roadmapPath(repoRoot, slug), "utf-8");
35314
+ text = fs90.readFileSync(roadmapPath(repoRoot, slug), "utf-8");
34659
35315
  } catch {
34660
35316
  return null;
34661
35317
  }
@@ -34679,8 +35335,8 @@ function buildCheckpoint(repoRoot, runId, slug, opts = {}) {
34679
35335
  function writeCheckpoint(repoRoot, cp) {
34680
35336
  const file = checkpointFile(repoRoot, cp.run_id);
34681
35337
  try {
34682
- fs88.mkdirSync(path90.dirname(file), { recursive: true });
34683
- fs88.writeFileSync(file, `${JSON.stringify(cp, null, 2)}
35338
+ fs90.mkdirSync(path92.dirname(file), { recursive: true });
35339
+ fs90.writeFileSync(file, `${JSON.stringify(cp, null, 2)}
34684
35340
  `, "utf-8");
34685
35341
  return file;
34686
35342
  } catch {
@@ -34691,7 +35347,7 @@ function writeCheckpoint(repoRoot, cp) {
34691
35347
  // src/scripts/hooks/session_eol_hook.ts
34692
35348
  var EXIT_WARN17 = 2;
34693
35349
  var THRESHOLD_OVERRIDE_ENV = "AGENT_RECYCLE_THRESHOLD_TOKENS";
34694
- var CONTEXT_FILL_REL = path91.join("agents", "runtime", "state", "context-fill.json");
35350
+ var CONTEXT_FILL_REL = path93.join("agents", "runtime", "state", "context-fill.json");
34695
35351
  function str12(value) {
34696
35352
  return typeof value === "string" ? value : "";
34697
35353
  }
@@ -34706,7 +35362,7 @@ function payloadSessionId(payload, envelope) {
34706
35362
  }
34707
35363
  function readState4(file) {
34708
35364
  try {
34709
- const parsed = JSON.parse(fs89.readFileSync(file, "utf-8"));
35365
+ const parsed = JSON.parse(fs91.readFileSync(file, "utf-8"));
34710
35366
  if (parsed && typeof parsed === "object" && parsed.schema_version === 1 && parsed.counters && typeof parsed.counters.scanned_bytes === "number") {
34711
35367
  return {
34712
35368
  schema_version: 1,
@@ -34727,10 +35383,10 @@ function readState4(file) {
34727
35383
  };
34728
35384
  }
34729
35385
  function envelopeExists(workspaceRoot3, since = null) {
34730
- const target = path91.join(workspaceRoot3, RECYCLE_ENVELOPE_REL);
35386
+ const target = path93.join(workspaceRoot3, RECYCLE_ENVELOPE_REL);
34731
35387
  let raw;
34732
35388
  try {
34733
- raw = fs89.readFileSync(target, "utf-8");
35389
+ raw = fs91.readFileSync(target, "utf-8");
34734
35390
  } catch (exc) {
34735
35391
  return exc?.code !== "ENOENT";
34736
35392
  }
@@ -34744,7 +35400,7 @@ function envelopeExists(workspaceRoot3, since = null) {
34744
35400
  }
34745
35401
  }
34746
35402
  function buildMissingEnvelopeLine(workspaceRoot3) {
34747
- return `recycle advised earlier, but no envelope exists at ${path91.join(workspaceRoot3, RECYCLE_ENVELOPE_REL)} \u2014 /clear now starts the successor from nothing. Re-run \`agent-config session:recycle\` and check that it prints the absolute path it wrote before clearing.`;
35403
+ return `recycle advised earlier, but no envelope exists at ${path93.join(workspaceRoot3, RECYCLE_ENVELOPE_REL)} \u2014 /clear now starts the successor from nothing. Re-run \`agent-config session:recycle\` and check that it prints the absolute path it wrote before clearing.`;
34748
35404
  }
34749
35405
  function readThresholdTokens() {
34750
35406
  const override = process15.env[THRESHOLD_OVERRIDE_ENV];
@@ -34760,7 +35416,7 @@ function buildAdvisoryLine4(tokens, threshold) {
34760
35416
  }
34761
35417
  function writeContextFill(workspaceRoot3, tokens, threshold, ts) {
34762
35418
  try {
34763
- atomic_write_json(path91.join(workspaceRoot3, CONTEXT_FILL_REL), {
35419
+ atomic_write_json(path93.join(workspaceRoot3, CONTEXT_FILL_REL), {
34764
35420
  schema_version: 1,
34765
35421
  final_context_tokens: tokens,
34766
35422
  recycle_threshold_tokens: threshold,
@@ -34823,7 +35479,10 @@ function main58() {
34823
35479
  try {
34824
35480
  const slug = read_claimed_slug(workspaceRoot3, checkpointRunId);
34825
35481
  if (slug !== null) {
34826
- const cp = buildCheckpoint(workspaceRoot3, eolSessionKey(checkpointRunId), slug);
35482
+ const observed = readContextObservation(workspaceRoot3)?.fingerprint ?? null;
35483
+ const cp = buildCheckpoint(workspaceRoot3, eolSessionKey(checkpointRunId), slug, {
35484
+ ...observed !== null ? { contextFingerprint: observed } : {}
35485
+ });
34827
35486
  if (cp !== null) writeCheckpoint(workspaceRoot3, cp);
34828
35487
  }
34829
35488
  } catch {
@@ -34860,13 +35519,13 @@ function _isCliEntry45() {
34860
35519
  if (process15.argv[1] === void 0) {
34861
35520
  return false;
34862
35521
  }
34863
- const argvUrl = pathToFileURL52(path91.resolve(process15.argv[1])).href;
35522
+ const argvUrl = pathToFileURL52(path93.resolve(process15.argv[1])).href;
34864
35523
  if (import.meta.url === argvUrl) {
34865
35524
  return true;
34866
35525
  }
34867
35526
  try {
34868
- const here = fs89.realpathSync(fileURLToPath59(import.meta.url));
34869
- const argv = fs89.realpathSync(path91.resolve(process15.argv[1]));
35527
+ const here = fs91.realpathSync(fileURLToPath59(import.meta.url));
35528
+ const argv = fs91.realpathSync(path93.resolve(process15.argv[1]));
34870
35529
  return here === argv;
34871
35530
  } catch {
34872
35531
  return false;
@@ -34877,14 +35536,14 @@ if (_isCliEntry45()) {
34877
35536
  }
34878
35537
 
34879
35538
  // src/scripts/hooks/tool_result_bytes_hook.ts
34880
- import * as fs90 from "node:fs";
34881
- import * as path92 from "node:path";
35539
+ import * as fs92 from "node:fs";
35540
+ import * as path94 from "node:path";
34882
35541
  import { fileURLToPath as fileURLToPath60, pathToFileURL as pathToFileURL53 } from "node:url";
34883
35542
  var EXIT_ALLOW30 = 0;
34884
35543
  function isObject15(v) {
34885
35544
  return typeof v === "object" && v !== null && !Array.isArray(v);
34886
35545
  }
34887
- var CENSUS_FILE = path92.join("agents", "runtime", "state", "tool-result-census.jsonl");
35546
+ var CENSUS_FILE = path94.join("agents", "runtime", "state", "tool-result-census.jsonl");
34888
35547
  var RESULT_KEYS = ["tool_response", "toolResponse", "tool_result", "toolUseResult"];
34889
35548
  function _resultBytes(payload) {
34890
35549
  for (const key of RESULT_KEYS) {
@@ -34919,13 +35578,13 @@ function unwrapPayload4(envelope) {
34919
35578
  return isObject15(inner) ? inner : envelope;
34920
35579
  }
34921
35580
  function censusFile(consumerRoot) {
34922
- return path92.join(consumerRoot, CENSUS_FILE);
35581
+ return path94.join(consumerRoot, CENSUS_FILE);
34923
35582
  }
34924
35583
  function _appendCensusLine(consumerRoot, line) {
34925
35584
  if (is_replay_mode()) return;
34926
35585
  const file = censusFile(consumerRoot);
34927
- fs90.mkdirSync(path92.dirname(file), { recursive: true });
34928
- fs90.appendFileSync(file, `${JSON.stringify(line)}
35586
+ fs92.mkdirSync(path94.dirname(file), { recursive: true });
35587
+ fs92.appendFileSync(file, `${JSON.stringify(line)}
34929
35588
  `, "utf8");
34930
35589
  }
34931
35590
  function processEnvelope4(envelope, consumerRoot) {
@@ -34971,10 +35630,10 @@ function _isCliEntry46() {
34971
35630
  return false;
34972
35631
  }
34973
35632
  if (process.argv[1] === void 0) return false;
34974
- const argvUrl = pathToFileURL53(path92.resolve(process.argv[1])).href;
35633
+ const argvUrl = pathToFileURL53(path94.resolve(process.argv[1])).href;
34975
35634
  if (import.meta.url === argvUrl) return true;
34976
35635
  try {
34977
- return fs90.realpathSync(fileURLToPath60(import.meta.url)) === fs90.realpathSync(path92.resolve(process.argv[1]));
35636
+ return fs92.realpathSync(fileURLToPath60(import.meta.url)) === fs92.realpathSync(path94.resolve(process.argv[1]));
34978
35637
  } catch {
34979
35638
  return false;
34980
35639
  }
@@ -34982,8 +35641,8 @@ function _isCliEntry46() {
34982
35641
  if (_isCliEntry46()) process.exit(main59());
34983
35642
 
34984
35643
  // src/scripts/hooks/spawn_guard_shadow_hook.ts
34985
- import * as fs91 from "node:fs";
34986
- import * as path93 from "node:path";
35644
+ import * as fs93 from "node:fs";
35645
+ import * as path95 from "node:path";
34987
35646
  import { fileURLToPath as fileURLToPath61, pathToFileURL as pathToFileURL54 } from "node:url";
34988
35647
  var EXIT_ALLOW31 = 0;
34989
35648
  function isObject16(v) {
@@ -35061,10 +35720,10 @@ function main60() {
35061
35720
  function _isCliEntry47() {
35062
35721
  if (true) return false;
35063
35722
  if (process.argv[1] === void 0) return false;
35064
- const argvUrl = pathToFileURL54(path93.resolve(process.argv[1])).href;
35723
+ const argvUrl = pathToFileURL54(path95.resolve(process.argv[1])).href;
35065
35724
  if (import.meta.url === argvUrl) return true;
35066
35725
  try {
35067
- return fs91.realpathSync(fileURLToPath61(import.meta.url)) === fs91.realpathSync(path93.resolve(process.argv[1]));
35726
+ return fs93.realpathSync(fileURLToPath61(import.meta.url)) === fs93.realpathSync(path95.resolve(process.argv[1]));
35068
35727
  } catch {
35069
35728
  return false;
35070
35729
  }
@@ -35072,13 +35731,13 @@ function _isCliEntry47() {
35072
35731
  if (_isCliEntry47()) process.exit(main60());
35073
35732
 
35074
35733
  // src/scripts/hooks/source_first_gate_hook.ts
35075
- import * as fs92 from "node:fs";
35076
- import * as path94 from "node:path";
35734
+ import * as fs94 from "node:fs";
35735
+ import * as path96 from "node:path";
35077
35736
  import { fileURLToPath as fileURLToPath62, pathToFileURL as pathToFileURL55 } from "node:url";
35078
35737
  var EXIT_ALLOW32 = 0;
35079
35738
  var MAX_SHADOW_WARNS = 2;
35080
- var SHADOW_LOG = path94.join("agents", "runtime", "state", "source-first-gate.jsonl");
35081
- var STATE_FILE9 = path94.join("agents", "runtime", "state", "source-first-gate.json");
35739
+ var SHADOW_LOG = path96.join("agents", "runtime", "state", "source-first-gate.jsonl");
35740
+ var STATE_FILE9 = path96.join("agents", "runtime", "state", "source-first-gate.json");
35082
35741
  function isObject17(v) {
35083
35742
  return typeof v === "object" && v !== null && !Array.isArray(v);
35084
35743
  }
@@ -35152,7 +35811,7 @@ var EMPTY_STATE = { handoverSeen: false, sourceRead: false, wouldWarn: 0 };
35152
35811
  var MAX_SESSIONS2 = 50;
35153
35812
  function readState5(root, session) {
35154
35813
  try {
35155
- const all = JSON.parse(fs92.readFileSync(path94.join(root, STATE_FILE9), "utf-8"));
35814
+ const all = JSON.parse(fs94.readFileSync(path96.join(root, STATE_FILE9), "utf-8"));
35156
35815
  const entry = all[session];
35157
35816
  if (entry && typeof entry.handoverSeen === "boolean" && typeof entry.sourceRead === "boolean" && typeof entry.wouldWarn === "number") {
35158
35817
  return entry;
@@ -35164,10 +35823,10 @@ function readState5(root, session) {
35164
35823
  function writeState4(root, session, state) {
35165
35824
  if (is_replay_mode()) return;
35166
35825
  try {
35167
- const file = path94.join(root, STATE_FILE9);
35826
+ const file = path96.join(root, STATE_FILE9);
35168
35827
  let all = {};
35169
35828
  try {
35170
- all = JSON.parse(fs92.readFileSync(file, "utf-8"));
35829
+ all = JSON.parse(fs94.readFileSync(file, "utf-8"));
35171
35830
  } catch {
35172
35831
  }
35173
35832
  if (!isObject17(all)) all = {};
@@ -35178,8 +35837,8 @@ function writeState4(root, session, state) {
35178
35837
  for (const key of keys.slice(keys.length - MAX_SESSIONS2)) pruned[key] = all[key];
35179
35838
  all = pruned;
35180
35839
  }
35181
- fs92.mkdirSync(path94.dirname(file), { recursive: true });
35182
- fs92.writeFileSync(file, JSON.stringify(all));
35840
+ fs94.mkdirSync(path96.dirname(file), { recursive: true });
35841
+ fs94.writeFileSync(file, JSON.stringify(all));
35183
35842
  } catch {
35184
35843
  }
35185
35844
  }
@@ -35200,7 +35859,7 @@ function evaluateCandidates2(handoverSeen, handoverOnDisk, sourceRead) {
35200
35859
  }
35201
35860
  function handoverPresentOnDisk(root) {
35202
35861
  try {
35203
- return fs92.statSync(path94.join(root, ".claude", "design-system")).isDirectory();
35862
+ return fs94.statSync(path96.join(root, ".claude", "design-system")).isDirectory();
35204
35863
  } catch {
35205
35864
  return false;
35206
35865
  }
@@ -35208,9 +35867,9 @@ function handoverPresentOnDisk(root) {
35208
35867
  function appendShadowLine(root, line) {
35209
35868
  if (is_replay_mode()) return;
35210
35869
  try {
35211
- const file = path94.join(root, SHADOW_LOG);
35212
- fs92.mkdirSync(path94.dirname(file), { recursive: true });
35213
- fs92.appendFileSync(file, `${JSON.stringify(line)}
35870
+ const file = path96.join(root, SHADOW_LOG);
35871
+ fs94.mkdirSync(path96.dirname(file), { recursive: true });
35872
+ fs94.appendFileSync(file, `${JSON.stringify(line)}
35214
35873
  `, "utf8");
35215
35874
  } catch {
35216
35875
  }
@@ -35286,10 +35945,10 @@ function main61() {
35286
35945
  function _isCliEntry48() {
35287
35946
  if (true) return false;
35288
35947
  if (process.argv[1] === void 0) return false;
35289
- const argvUrl = pathToFileURL55(path94.resolve(process.argv[1])).href;
35948
+ const argvUrl = pathToFileURL55(path96.resolve(process.argv[1])).href;
35290
35949
  if (import.meta.url === argvUrl) return true;
35291
35950
  try {
35292
- return fs92.realpathSync(fileURLToPath62(import.meta.url)) === fs92.realpathSync(path94.resolve(process.argv[1]));
35951
+ return fs94.realpathSync(fileURLToPath62(import.meta.url)) === fs94.realpathSync(path96.resolve(process.argv[1]));
35293
35952
  } catch {
35294
35953
  return false;
35295
35954
  }
@@ -35297,12 +35956,12 @@ function _isCliEntry48() {
35297
35956
  if (_isCliEntry48()) process.exit(main61());
35298
35957
 
35299
35958
  // src/scripts/hooks/interruption_ledger_hook.ts
35300
- import * as fs93 from "node:fs";
35301
- import * as path95 from "node:path";
35959
+ import * as fs95 from "node:fs";
35960
+ import * as path97 from "node:path";
35302
35961
  import { fileURLToPath as fileURLToPath63, pathToFileURL as pathToFileURL56 } from "node:url";
35303
35962
  var EXIT_OK2 = 0;
35304
35963
  var TRANSCRIPT_READ_MAX_BYTES2 = 2 * 1024 * 1024;
35305
- var LEDGER_RELPATH = path95.join("agents", "runtime", "state", "interruptions.jsonl");
35964
+ var LEDGER_RELPATH = path97.join("agents", "runtime", "state", "interruptions.jsonl");
35306
35965
  function hasNumberedOptions(reply) {
35307
35966
  let count = 0;
35308
35967
  for (const line of reply.split("\n")) {
@@ -35326,14 +35985,14 @@ function classifyReply(reply) {
35326
35985
  function readClaimedRoadmap(workspaceRoot3, sessionId2) {
35327
35986
  if (!sessionId2) return null;
35328
35987
  try {
35329
- const file = path95.join(
35988
+ const file = path97.join(
35330
35989
  workspaceRoot3,
35331
35990
  "agents",
35332
35991
  "runtime",
35333
35992
  "state",
35334
35993
  `roadmap-claim-${sessionId2}.json`
35335
35994
  );
35336
- const parsed = JSON.parse(fs93.readFileSync(file, "utf8"));
35995
+ const parsed = JSON.parse(fs95.readFileSync(file, "utf8"));
35337
35996
  if (parsed === null || typeof parsed !== "object") return null;
35338
35997
  const slug = parsed["slug"];
35339
35998
  return typeof slug === "string" && slug.length > 0 ? slug : null;
@@ -35344,7 +36003,7 @@ function readClaimedRoadmap(workspaceRoot3, sessionId2) {
35344
36003
  function alreadyRecorded(ledgerPath, runId, turn) {
35345
36004
  let raw;
35346
36005
  try {
35347
- raw = fs93.readFileSync(ledgerPath, "utf8");
36006
+ raw = fs95.readFileSync(ledgerPath, "utf8");
35348
36007
  } catch {
35349
36008
  return false;
35350
36009
  }
@@ -35388,7 +36047,7 @@ function main62() {
35388
36047
  if (!lastAssistant) return EXIT_OK2;
35389
36048
  const sessionId2 = str14(payload["session_id"]) || str14(envelope["session_id"]);
35390
36049
  const runId = sessionId2 ? derive_session_tag(sessionId2) : "unknown";
35391
- const ledgerPath = path95.join(workspaceRoot3, LEDGER_RELPATH);
36050
+ const ledgerPath = path97.join(workspaceRoot3, LEDGER_RELPATH);
35392
36051
  if (alreadyRecorded(ledgerPath, runId, turnOrdinal)) return EXIT_OK2;
35393
36052
  const verdict = classifyReply(lastAssistant);
35394
36053
  const record = {
@@ -35400,8 +36059,8 @@ function main62() {
35400
36059
  at: (/* @__PURE__ */ new Date()).toISOString()
35401
36060
  };
35402
36061
  try {
35403
- fs93.mkdirSync(path95.dirname(ledgerPath), { recursive: true });
35404
- fs93.appendFileSync(ledgerPath, `${JSON.stringify(record)}
36062
+ fs95.mkdirSync(path97.dirname(ledgerPath), { recursive: true });
36063
+ fs95.appendFileSync(ledgerPath, `${JSON.stringify(record)}
35405
36064
  `, "utf8");
35406
36065
  } catch {
35407
36066
  }
@@ -35411,13 +36070,13 @@ function _isCliEntry49() {
35411
36070
  if (process.argv[1] === void 0) {
35412
36071
  return false;
35413
36072
  }
35414
- const argvUrl = pathToFileURL56(path95.resolve(process.argv[1])).href;
36073
+ const argvUrl = pathToFileURL56(path97.resolve(process.argv[1])).href;
35415
36074
  if (import.meta.url === argvUrl) {
35416
36075
  return true;
35417
36076
  }
35418
36077
  try {
35419
- const here = fs93.realpathSync(fileURLToPath63(import.meta.url));
35420
- const argv = fs93.realpathSync(path95.resolve(process.argv[1]));
36078
+ const here = fs95.realpathSync(fileURLToPath63(import.meta.url));
36079
+ const argv = fs95.realpathSync(path97.resolve(process.argv[1]));
35421
36080
  return here === argv;
35422
36081
  } catch {
35423
36082
  return false;
@@ -35428,15 +36087,15 @@ if (_isCliEntry49()) {
35428
36087
  }
35429
36088
 
35430
36089
  // src/scripts/hooks/run_continuation_hook.ts
35431
- import * as fs96 from "node:fs";
36090
+ import * as fs97 from "node:fs";
35432
36091
 
35433
36092
  // src/scripts/_lib/billing_grant.ts
35434
- import * as fs94 from "node:fs";
35435
- import * as path96 from "node:path";
35436
- var BILLING_GRANT_DIR_REL = path96.join("agents", "runtime", "billing-grants");
36093
+ import * as fs96 from "node:fs";
36094
+ import * as path98 from "node:path";
36095
+ var BILLING_GRANT_DIR_REL = path98.join("agents", "runtime", "billing-grants");
35437
36096
  var BILLING_GRANT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
35438
36097
  function grantFile(repoRoot, runId) {
35439
- return path96.join(
36098
+ return path98.join(
35440
36099
  repoRoot,
35441
36100
  BILLING_GRANT_DIR_REL,
35442
36101
  `${runId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`
@@ -35444,79 +36103,96 @@ function grantFile(repoRoot, runId) {
35444
36103
  }
35445
36104
  function revokeBillingGrant(repoRoot, runId) {
35446
36105
  try {
35447
- fs94.unlinkSync(grantFile(repoRoot, runId));
36106
+ fs96.unlinkSync(grantFile(repoRoot, runId));
35448
36107
  } catch {
35449
36108
  }
35450
36109
  }
35451
36110
 
35452
36111
  // src/scripts/hooks/run_continuation_hook.ts
35453
- import * as path98 from "node:path";
36112
+ import * as path99 from "node:path";
35454
36113
  import { fileURLToPath as fileURLToPath64, pathToFileURL as pathToFileURL57 } from "node:url";
35455
36114
 
35456
- // src/scripts/_lib/loop_guards.ts
35457
- import * as fs95 from "node:fs";
35458
- import * as path97 from "node:path";
35459
- function atomicWriteJson(file, value) {
35460
- const tmp = `${file}.${String(process.pid)}.tmp`;
35461
- try {
35462
- fs95.mkdirSync(path97.dirname(file), { recursive: true });
35463
- fs95.writeFileSync(tmp, `${JSON.stringify(value)}
35464
- `, "utf8");
35465
- fs95.renameSync(tmp, file);
35466
- return true;
35467
- } catch {
35468
- try {
35469
- fs95.unlinkSync(tmp);
35470
- } catch {
35471
- }
35472
- return false;
35473
- }
35474
- }
35475
- var DEPENDENCY_PATTERNS = [
35476
- { kind: "binary", re: /\b(?:command not found|is not recognized as an internal or external command)\b/i },
35477
- { kind: "credential", re: /\b(?:not (?:authenticated|logged in)|authentication (?:failed|required)|no credentials found)\b/i },
35478
- { kind: "credential", re: /\b(?:missing|unset|not set)\b[^\n]{0,40}\b(?:credential|token|api[_ -]?key|secret)\b/i },
35479
- { kind: "permission", re: /\b(?:permission denied|forbidden|403 forbidden)\b/i },
35480
- { kind: "quota", re: /\b(?:rate limit(?:ed| exceeded| reached)?|quota exceeded|429 too many requests|spend (?:cap|limit) reached)\b/i },
35481
- { kind: "service", re: /\b(?:5\d\d (?:internal )?server error|service unavailable|connection refused)\b/i }
36115
+ // src/scripts/_lib/outcome_vocabularies.ts
36116
+ var RUN_TERMINAL_STATES = [
36117
+ "success",
36118
+ "clean-no-op",
36119
+ "blocked",
36120
+ "approval-required",
36121
+ "exhausted",
36122
+ "stagnated",
36123
+ /**
36124
+ * The plan premise this run was built on no longer holds — the situational
36125
+ * -awareness fingerprint the run engaged under (`origin/main` plus every open
36126
+ * PR head) was re-observed and differs.
36127
+ *
36128
+ * Added by `road-to-wired-instruments` Phase 2 under the AI-council decision
36129
+ * of 2026-09-03 (option (a), unanimous). It is NOT `blocked` with a reason
36130
+ * field: premise invalidation is operationally distinct from a missing
36131
+ * precondition and from an exhausted budget, and folding it into either loses
36132
+ * both aggregation by state and type-enforced exhaustive handling. The
36133
+ * remedy differs too — re-probe and re-plan, where `blocked` says obtain the
36134
+ * missing thing and `exhausted` says the budget was too small.
36135
+ */
36136
+ "premise-invalidated"
35482
36137
  ];
35483
- var DEPENDENCY_SCAN_BYTES = 64 * 1024;
35484
- function detectUnavailableDependency(transcriptTail, tailLines = 200) {
35485
- const lines = transcriptTail.split("\n");
35486
- const window = lines.slice(Math.max(0, lines.length - tailLines));
35487
- for (const line of window) {
35488
- for (const { kind, re } of DEPENDENCY_PATTERNS) {
35489
- if (re.test(line)) {
35490
- return { kind, evidence: line.trim().slice(0, 200) };
35491
- }
35492
- }
35493
- }
35494
- return null;
36138
+ var RUN_TERMINAL_VOCABULARY_VERSION = 2;
36139
+
36140
+ // src/scripts/_lib/continuation_ladder.ts
36141
+ var MAX_ITERATIONS = 25;
36142
+ var WALL_CLOCK_CAP_MS = 4 * 60 * 60 * 1e3;
36143
+ var STALL_WINDOW = 3;
36144
+ var HALT_ACTIONS = [
36145
+ "halt-max-iterations",
36146
+ "halt-wall-clock",
36147
+ "halt-stall",
36148
+ // Phase 5.4. Checked BEFORE the counter rungs: a dependency the run cannot
36149
+ // obtain is not something more iterations close, so consuming the budget
36150
+ // against it is the waste the rung exists to stop.
36151
+ "halt-dependency-unavailable",
36152
+ // `road-to-wired-instruments` 2.2, and BEFORE the counter rungs for the same
36153
+ // reason: a run whose plan premise moved does not become correct by running
36154
+ // more iterations against the stale plan, and letting it cap out reports
36155
+ // `exhausted` — a budget problem, whose remedy is a bigger budget — for what
36156
+ // is actually a staleness problem, whose remedy is a re-probe.
36157
+ "halt-premise-invalidated"
36158
+ ];
36159
+ var TERMINAL_STATE_BY_ACTION = {
36160
+ engage: null,
36161
+ complete: "success",
36162
+ blocked: "blocked",
36163
+ "halt-max-iterations": "exhausted",
36164
+ "halt-wall-clock": "exhausted",
36165
+ "halt-stall": "stagnated",
36166
+ "halt-dependency-unavailable": "blocked",
36167
+ "halt-premise-invalidated": "premise-invalidated"
36168
+ };
36169
+ function terminalStateFor(action) {
36170
+ return TERMINAL_STATE_BY_ACTION[action] ?? null;
35495
36171
  }
35496
- function stallSignal(history, window = 3) {
35497
- if (history.length === 0) {
35498
- return { level: "flat", flatRun: 0, minimum: null, newMinimum: false };
36172
+ function ladder(state, openCount, nowMs, blockedCount, caps = {
36173
+ maxIterations: MAX_ITERATIONS,
36174
+ wallClockMs: WALL_CLOCK_CAP_MS,
36175
+ stallWindow: STALL_WINDOW
36176
+ }, unavailable = null, premiseInvalidated = false) {
36177
+ if (state.halted) return state.halted;
36178
+ if (openCount === 0) return blockedCount > 0 ? "blocked" : "complete";
36179
+ if (unavailable !== null) return "halt-dependency-unavailable";
36180
+ if (premiseInvalidated) return "halt-premise-invalidated";
36181
+ if (state.iterations >= caps.maxIterations) return "halt-max-iterations";
36182
+ const started = Date.parse(state.started_at);
36183
+ if (Number.isFinite(started) && nowMs - started >= caps.wallClockMs) {
36184
+ return "halt-wall-clock";
35499
36185
  }
35500
- const last = history[history.length - 1];
35501
- const minimum = Math.min(...history);
35502
- const earlier = history.slice(0, -1);
35503
- const newMinimum = earlier.length > 0 && last < Math.min(...earlier);
35504
- let flatRun = 1;
35505
- for (let i = history.length - 2; i >= 0; i -= 1) {
35506
- if (history[i] !== last) break;
35507
- flatRun += 1;
36186
+ const tail = state.history.slice(-caps.stallWindow);
36187
+ if (tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
36188
+ return "halt-stall";
35508
36189
  }
35509
- if (newMinimum) return { level: "progressing", flatRun, minimum, newMinimum };
35510
- if (flatRun >= window) return { level: "stalled", flatRun, minimum, newMinimum };
35511
- return { level: "flat", flatRun, minimum, newMinimum };
36190
+ return "engage";
35512
36191
  }
35513
- function readUnavailableDependency(transcriptPath) {
35514
- try {
35515
- const raw = fs95.readFileSync(transcriptPath, "utf8");
35516
- return detectUnavailableDependency(raw.slice(-DEPENDENCY_SCAN_BYTES));
35517
- } catch {
35518
- return null;
35519
- }
36192
+ function parseHaltStamp(v) {
36193
+ if (typeof v !== "string") return null;
36194
+ if (HALT_ACTIONS.includes(v)) return v;
36195
+ return v.startsWith("halt-") ? v : null;
35520
36196
  }
35521
36197
 
35522
36198
  // src/scripts/hooks/run_continuation_hook.ts
@@ -35524,16 +36200,13 @@ var EXIT_ALLOW33 = 0;
35524
36200
  var EXIT_BLOCK5 = 1;
35525
36201
  var BLOCKED_BY_MARKER = /<!--\s*blocked-by:\s*\S[^>]*-->/;
35526
36202
  var LEDGER_TAIL_MAX_BYTES = 512 * 1024;
35527
- var EVENTS_RELPATH = path98.join(
36203
+ var EVENTS_RELPATH = path99.join(
35528
36204
  "agents",
35529
36205
  "runtime",
35530
36206
  "state",
35531
36207
  "run-continuation.jsonl"
35532
36208
  );
35533
- var STATE_DIR_REL = path98.join("agents", "runtime", "state");
35534
- var MAX_ITERATIONS = 25;
35535
- var WALL_CLOCK_CAP_MS = 4 * 60 * 60 * 1e3;
35536
- var STALL_WINDOW = 3;
36209
+ var STATE_DIR_REL = path99.join("agents", "runtime", "state");
35537
36210
  var ABSENT_CONFIRM_FIRES = 2;
35538
36211
  var DUPLICATE_WINDOW_MS = 60 * 1e3;
35539
36212
  function isDuplicateFire(state, turnOrdinal, openCount, nowMs, roadmapSource) {
@@ -35548,15 +36221,6 @@ function isDuplicateFire(state, turnOrdinal, openCount, nowMs, roadmapSource) {
35548
36221
  const at = Date.parse(state.last_engaged_at ?? "");
35549
36222
  return Number.isFinite(at) && nowMs - at < DUPLICATE_WINDOW_MS;
35550
36223
  }
35551
- var HALT_ACTIONS = [
35552
- "halt-max-iterations",
35553
- "halt-wall-clock",
35554
- "halt-stall",
35555
- // Phase 5.4. Checked BEFORE the counter rungs: a dependency the run cannot
35556
- // obtain is not something more iterations close, so consuming the budget
35557
- // against it is the waste the rung exists to stop.
35558
- "halt-dependency-unavailable"
35559
- ];
35560
36224
  function parseExecutionMode(text) {
35561
36225
  if (!text.startsWith("---")) return null;
35562
36226
  const end = text.indexOf("\n---", 3);
@@ -35611,42 +36275,23 @@ function extractVerify(stepText) {
35611
36275
  if (backticked !== null) return backticked[1].trim() || null;
35612
36276
  return null;
35613
36277
  }
35614
- function ladder(state, openCount, nowMs, blockedCount, caps = {
35615
- maxIterations: MAX_ITERATIONS,
35616
- wallClockMs: WALL_CLOCK_CAP_MS,
35617
- stallWindow: STALL_WINDOW
35618
- }, unavailable = null) {
35619
- if (state.halted) return state.halted;
35620
- if (openCount === 0) return blockedCount > 0 ? "blocked" : "complete";
35621
- if (unavailable !== null) return "halt-dependency-unavailable";
35622
- if (state.iterations >= caps.maxIterations) return "halt-max-iterations";
35623
- const started = Date.parse(state.started_at);
35624
- if (Number.isFinite(started) && nowMs - started >= caps.wallClockMs) {
35625
- return "halt-wall-clock";
35626
- }
35627
- const tail = state.history.slice(-caps.stallWindow);
35628
- if (tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
35629
- return "halt-stall";
35630
- }
35631
- return "engage";
35632
- }
35633
36278
  function stateRelPath(runId, slug) {
35634
36279
  const safeRun = runId.replace(/[^A-Za-z0-9_-]/g, "_");
35635
36280
  const safeSlug = slug.replace(/[^A-Za-z0-9_-]/g, "_");
35636
- return path98.join(STATE_DIR_REL, `run-continuation-${safeRun}-${safeSlug}.json`);
36281
+ return path99.join(STATE_DIR_REL, `run-continuation-${safeRun}-${safeSlug}.json`);
35637
36282
  }
35638
36283
  function legacyStateRelPath(runId) {
35639
36284
  const safe = runId.replace(/[^A-Za-z0-9_-]/g, "_");
35640
- return path98.join(STATE_DIR_REL, `run-continuation-${safe}.json`);
36285
+ return path99.join(STATE_DIR_REL, `run-continuation-${safe}.json`);
35641
36286
  }
35642
36287
  function readRunState(workspaceRoot3, runId, slug) {
35643
- const keyedFile = path98.join(workspaceRoot3, stateRelPath(runId, slug));
36288
+ const keyedFile = path99.join(workspaceRoot3, stateRelPath(runId, slug));
35644
36289
  const keyed = readState6(keyedFile);
35645
36290
  if (keyed !== null) return { state: keyed, from: "keyed", keyedFile };
35646
36291
  if (fileExists(keyedFile)) {
35647
36292
  return { state: null, from: "keyed", keyedFile };
35648
36293
  }
35649
- const legacy = readState6(path98.join(workspaceRoot3, legacyStateRelPath(runId)));
36294
+ const legacy = readState6(path99.join(workspaceRoot3, legacyStateRelPath(runId)));
35650
36295
  if (legacy === null) return { state: null, from: "none", keyedFile };
35651
36296
  if (legacy.roadmap !== void 0 && legacy.roadmap !== slug) {
35652
36297
  return { state: null, from: "none", keyedFile };
@@ -35659,25 +36304,25 @@ function writeState5(file, state) {
35659
36304
  function clearRunState(workspaceRoot3, runId, keyedFile, adoptedLegacy) {
35660
36305
  revokeBillingGrant(workspaceRoot3, runId);
35661
36306
  try {
35662
- fs96.rmSync(keyedFile, { force: true });
36307
+ fs97.rmSync(keyedFile, { force: true });
35663
36308
  } catch {
35664
36309
  }
35665
36310
  if (!adoptedLegacy) return;
35666
36311
  try {
35667
- fs96.rmSync(path98.join(workspaceRoot3, legacyStateRelPath(runId)), { force: true });
36312
+ fs97.rmSync(path99.join(workspaceRoot3, legacyStateRelPath(runId)), { force: true });
35668
36313
  } catch {
35669
36314
  }
35670
36315
  }
35671
36316
  function fileExists(p) {
35672
36317
  try {
35673
- return fs96.statSync(p).isFile();
36318
+ return fs97.statSync(p).isFile();
35674
36319
  } catch {
35675
36320
  return false;
35676
36321
  }
35677
36322
  }
35678
36323
  function readState6(file) {
35679
36324
  try {
35680
- const parsed = JSON.parse(fs96.readFileSync(file, "utf8"));
36325
+ const parsed = JSON.parse(fs97.readFileSync(file, "utf8"));
35681
36326
  if (parsed === null || typeof parsed !== "object") return null;
35682
36327
  const o = parsed;
35683
36328
  if (typeof o["started_at"] !== "string") return null;
@@ -35707,26 +36352,27 @@ function readState6(file) {
35707
36352
  if (o["inert_reported"] === true) {
35708
36353
  rec.inert_reported = true;
35709
36354
  }
35710
- const halted = o["halted"];
35711
- if (typeof halted === "string" && HALT_ACTIONS.includes(halted)) {
35712
- rec.halted = halted;
36355
+ if (typeof o["context_fingerprint"] === "string" && o["context_fingerprint"] !== "") {
36356
+ rec.context_fingerprint = o["context_fingerprint"];
35713
36357
  }
36358
+ const halted = parseHaltStamp(o["halted"]);
36359
+ if (halted !== null) rec.halted = halted;
35714
36360
  return rec;
35715
36361
  } catch {
35716
36362
  return null;
35717
36363
  }
35718
36364
  }
35719
36365
  function normalizeDir(p) {
35720
- const abs = path98.resolve(p);
36366
+ const abs = path99.resolve(p);
35721
36367
  try {
35722
- return fs96.realpathSync(abs);
36368
+ return fs97.realpathSync(abs);
35723
36369
  } catch {
35724
36370
  return abs;
35725
36371
  }
35726
36372
  }
35727
36373
  function normalizeFile(p) {
35728
- const abs = path98.resolve(p);
35729
- return path98.join(normalizeDir(path98.dirname(abs)), path98.basename(abs));
36374
+ const abs = path99.resolve(p);
36375
+ return path99.join(normalizeDir(path99.dirname(abs)), path99.basename(abs));
35730
36376
  }
35731
36377
  function provenance(workspaceRoot3, claimPath, sessionRoot, sessionCwd) {
35732
36378
  const reader = normalizeDir(workspaceRoot3);
@@ -35753,36 +36399,36 @@ function provenance(workspaceRoot3, claimPath, sessionRoot, sessionCwd) {
35753
36399
  };
35754
36400
  }
35755
36401
  function resolveRoadmap(sessionRoot, workspaceRoot3, slug) {
35756
- const dirRel = path98.join("agents", "roadmaps");
36402
+ const dirRel = path99.join("agents", "roadmaps");
35757
36403
  const stem = slug.trim().replace(/\.md$/, "");
35758
- const rel = path98.join(dirRel, `${stem}.md`);
35759
- const norm = path98.normalize(rel);
35760
- if (path98.dirname(norm) !== dirRel) return null;
35761
- if (sessionRoot !== workspaceRoot3 && isDirectory(path98.join(sessionRoot, dirRel))) {
35762
- return path98.join(sessionRoot, norm);
36404
+ const rel = path99.join(dirRel, `${stem}.md`);
36405
+ const norm = path99.normalize(rel);
36406
+ if (path99.dirname(norm) !== dirRel) return null;
36407
+ if (sessionRoot !== workspaceRoot3 && isDirectory(path99.join(sessionRoot, dirRel))) {
36408
+ return path99.join(sessionRoot, norm);
35763
36409
  }
35764
- return path98.join(normalizeDir(workspaceRoot3), norm);
36410
+ return path99.join(normalizeDir(workspaceRoot3), norm);
35765
36411
  }
35766
36412
  function isDirectory(p) {
35767
36413
  try {
35768
- return fs96.statSync(p).isDirectory();
36414
+ return fs97.statSync(p).isDirectory();
35769
36415
  } catch {
35770
36416
  return false;
35771
36417
  }
35772
36418
  }
35773
36419
  function eventAlreadyLogged(workspaceRoot3, runId, slug, event) {
35774
36420
  try {
35775
- const file = path98.join(workspaceRoot3, EVENTS_RELPATH);
35776
- const size = fs96.statSync(file).size;
36421
+ const file = path99.join(workspaceRoot3, EVENTS_RELPATH);
36422
+ const size = fs97.statSync(file).size;
35777
36423
  const start = Math.max(0, size - LEDGER_TAIL_MAX_BYTES);
35778
- const fd = fs96.openSync(file, "r");
36424
+ const fd = fs97.openSync(file, "r");
35779
36425
  let text;
35780
36426
  try {
35781
36427
  const buf = Buffer.alloc(size - start);
35782
- const read = fs96.readSync(fd, buf, 0, buf.length, start);
36428
+ const read = fs97.readSync(fd, buf, 0, buf.length, start);
35783
36429
  text = buf.subarray(0, read).toString("utf8");
35784
36430
  } finally {
35785
- fs96.closeSync(fd);
36431
+ fs97.closeSync(fd);
35786
36432
  }
35787
36433
  for (const line of text.split("\n")) {
35788
36434
  if (line === "") continue;
@@ -35805,9 +36451,9 @@ function eventAlreadyLogged(workspaceRoot3, runId, slug, event) {
35805
36451
  }
35806
36452
  function appendEvent2(workspaceRoot3, record) {
35807
36453
  try {
35808
- const file = path98.join(workspaceRoot3, EVENTS_RELPATH);
35809
- fs96.mkdirSync(path98.dirname(file), { recursive: true });
35810
- fs96.appendFileSync(file, `${JSON.stringify(record)}
36454
+ const file = path99.join(workspaceRoot3, EVENTS_RELPATH);
36455
+ fs97.mkdirSync(path99.dirname(file), { recursive: true });
36456
+ fs97.appendFileSync(file, `${JSON.stringify(record)}
35811
36457
  `, "utf8");
35812
36458
  } catch {
35813
36459
  }
@@ -35815,7 +36461,7 @@ function appendEvent2(workspaceRoot3, record) {
35815
36461
  function refusedThisTurn(workspaceRoot3, sessionId2, turnOrdinal) {
35816
36462
  try {
35817
36463
  const file = sessionRefusalFile(workspaceRoot3, deriveSessionKey(sessionId2 || "unknown-session"));
35818
- const rec = parseRecord(fs96.readFileSync(file, "utf-8"));
36464
+ const rec = parseRecord(fs97.readFileSync(file, "utf-8"));
35819
36465
  return rec !== null && rec.refused_turn === turnOrdinal;
35820
36466
  } catch {
35821
36467
  return false;
@@ -35849,7 +36495,7 @@ function main63() {
35849
36495
  let roadmapText = null;
35850
36496
  if (roadmapPath2 !== null) {
35851
36497
  try {
35852
- roadmapText = fs96.readFileSync(roadmapPath2, "utf8");
36498
+ roadmapText = fs97.readFileSync(roadmapPath2, "utf8");
35853
36499
  roadmapFile = roadmapPath2;
35854
36500
  } catch {
35855
36501
  roadmapText = null;
@@ -35898,7 +36544,7 @@ function main63() {
35898
36544
  );
35899
36545
  if (!transcriptPath) return EXIT_ALLOW33;
35900
36546
  try {
35901
- if (fs96.statSync(transcriptPath).size > TRANSCRIPT_READ_MAX_BYTES) {
36547
+ if (fs97.statSync(transcriptPath).size > TRANSCRIPT_READ_MAX_BYTES) {
35902
36548
  const inert = readRunState(workspaceRoot3, runId, slug);
35903
36549
  if (inert.state !== null && inert.state.inert_reported !== true) {
35904
36550
  const inertRoots = provenance(
@@ -35973,7 +36619,17 @@ function main63() {
35973
36619
  }
35974
36620
  const unavailable = readUnavailableDependency(transcriptPath);
35975
36621
  state.stall = stallSignal(state.history, STALL_WINDOW).level;
35976
- const action = ladder(state, scan.open, Date.now(), scan.blocked, void 0, unavailable);
36622
+ const observed = readContextObservation(workspaceRoot3)?.fingerprint ?? null;
36623
+ const premiseInvalidated = premiseMoved(state.context_fingerprint, observed);
36624
+ const action = ladder(
36625
+ state,
36626
+ scan.open,
36627
+ Date.now(),
36628
+ scan.blocked,
36629
+ void 0,
36630
+ unavailable,
36631
+ premiseInvalidated
36632
+ );
35977
36633
  if (action !== "engage") {
35978
36634
  const alreadyStamped = prev?.halted !== void 0;
35979
36635
  const alreadyBlocked = action === "blocked" && eventAlreadyLogged(workspaceRoot3, runId, slug, "blocked");
@@ -35986,6 +36642,12 @@ function main63() {
35986
36642
  iterations: state.iterations,
35987
36643
  open: scan.open,
35988
36644
  blocked: scan.blocked,
36645
+ // A ledger line is a persisted shape carrying a `RunTerminalState`,
36646
+ // so it names the vocabulary version it was written against: a
36647
+ // reader meeting an unknown value can then tell a newer writer
36648
+ // from a corrupt row.
36649
+ terminal_state: terminalStateFor(action),
36650
+ terminal_vocabulary_version: RUN_TERMINAL_VOCABULARY_VERSION,
35989
36651
  at: (/* @__PURE__ */ new Date()).toISOString(),
35990
36652
  ...roots
35991
36653
  });
@@ -36006,6 +36668,9 @@ function main63() {
36006
36668
  }
36007
36669
  state.iterations += 1;
36008
36670
  state.last_turn = turnOrdinal;
36671
+ if (state.context_fingerprint === void 0 && observed !== null) {
36672
+ state.context_fingerprint = observed;
36673
+ }
36009
36674
  state.history.push(scan.open);
36010
36675
  state.last_engaged_at = (/* @__PURE__ */ new Date()).toISOString();
36011
36676
  if (!writeState5(stateFile4, state)) {
@@ -36038,11 +36703,11 @@ function _isCliEntry50() {
36038
36703
  return false;
36039
36704
  }
36040
36705
  if (process.argv[1] === void 0) return false;
36041
- const argvUrl = pathToFileURL57(path98.resolve(process.argv[1])).href;
36706
+ const argvUrl = pathToFileURL57(path99.resolve(process.argv[1])).href;
36042
36707
  if (import.meta.url === argvUrl) return true;
36043
36708
  try {
36044
- const here = fs96.realpathSync(fileURLToPath64(import.meta.url));
36045
- const argv = fs96.realpathSync(path98.resolve(process.argv[1]));
36709
+ const here = fs97.realpathSync(fileURLToPath64(import.meta.url));
36710
+ const argv = fs97.realpathSync(path99.resolve(process.argv[1]));
36046
36711
  return here === argv;
36047
36712
  } catch {
36048
36713
  return false;
@@ -36053,26 +36718,14 @@ if (_isCliEntry50()) {
36053
36718
  }
36054
36719
 
36055
36720
  // src/scripts/hooks/journal_record_hook.ts
36056
- import * as fs98 from "node:fs";
36057
- import * as path100 from "node:path";
36721
+ import * as fs99 from "node:fs";
36722
+ import * as path101 from "node:path";
36058
36723
  import { fileURLToPath as fileURLToPath65, pathToFileURL as pathToFileURL58 } from "node:url";
36059
36724
 
36060
36725
  // src/scripts/_lib/runtime_journal.ts
36061
36726
  import * as crypto14 from "node:crypto";
36062
- import * as fs97 from "node:fs";
36063
- import * as path99 from "node:path";
36064
-
36065
- // src/scripts/_lib/outcome_vocabularies.ts
36066
- var RUN_TERMINAL_STATES = [
36067
- "success",
36068
- "clean-no-op",
36069
- "blocked",
36070
- "approval-required",
36071
- "exhausted",
36072
- "stagnated"
36073
- ];
36074
-
36075
- // src/scripts/_lib/runtime_journal.ts
36727
+ import * as fs98 from "node:fs";
36728
+ import * as path100 from "node:path";
36076
36729
  var JOURNAL_SCHEMA_VERSION = 3;
36077
36730
  var BOUNDARY_RULE_VERSION = 1;
36078
36731
  var RETENTION_TTL_DAYS = 30;
@@ -36163,7 +36816,7 @@ function requireId(name, id, optional) {
36163
36816
  }
36164
36817
  var JOURNAL_DIR_NAME = "agent-journal";
36165
36818
  var JOURNAL_FILE_NAME = "journal.sqlite";
36166
- var JOURNAL_FALLBACK_REL = path99.join("agents", "runtime", "state", JOURNAL_FILE_NAME);
36819
+ var JOURNAL_FALLBACK_REL = path100.join("agents", "runtime", "state", JOURNAL_FILE_NAME);
36167
36820
  function digest(domain, input) {
36168
36821
  return crypto14.createHash("sha256").update(`${domain}\0${input}`).digest("hex").slice(0, 12);
36169
36822
  }
@@ -36174,20 +36827,20 @@ function resolveJournal(root) {
36174
36827
  if (common !== null) {
36175
36828
  const own = git_dir(root) ?? common;
36176
36829
  return {
36177
- path: path99.join(common, JOURNAL_DIR_NAME, JOURNAL_FILE_NAME),
36830
+ path: path100.join(common, JOURNAL_DIR_NAME, JOURNAL_FILE_NAME),
36178
36831
  scope: "repo-shared",
36179
36832
  reason: "common git dir resolved \u2014 every worktree of this repo writes one database",
36180
36833
  repository_id: digest(REPO_DOMAIN, common),
36181
36834
  worktree_id: digest(WORKTREE_DOMAIN, own)
36182
36835
  };
36183
36836
  }
36184
- let real = path99.resolve(root);
36837
+ let real = path100.resolve(root);
36185
36838
  try {
36186
- real = fs97.realpathSync(real);
36839
+ real = fs98.realpathSync(real);
36187
36840
  } catch {
36188
36841
  }
36189
36842
  return {
36190
- path: path99.join(real, JOURNAL_FALLBACK_REL),
36843
+ path: path100.join(real, JOURNAL_FALLBACK_REL),
36191
36844
  scope: "worktree-local",
36192
36845
  reason: "no common git dir \u2014 not inside a git repository; falling back to the tree-local path",
36193
36846
  repository_id: digest(REPO_DOMAIN, real),
@@ -36273,7 +36926,7 @@ function ensureWal(db) {
36273
36926
  function discard(dbPath) {
36274
36927
  for (const p of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
36275
36928
  try {
36276
- fs97.rmSync(p, { force: true });
36929
+ fs98.rmSync(p, { force: true });
36277
36930
  } catch {
36278
36931
  }
36279
36932
  }
@@ -36285,9 +36938,9 @@ function openJournal(root) {
36285
36938
  );
36286
36939
  }
36287
36940
  const location = resolveJournal(root);
36288
- fs97.mkdirSync(path99.dirname(location.path), { recursive: true });
36941
+ fs98.mkdirSync(path100.dirname(location.path), { recursive: true });
36289
36942
  const { DatabaseSync } = loadSqliteSync("runtime_journal");
36290
- if (fs97.existsSync(location.path)) {
36943
+ if (fs98.existsSync(location.path)) {
36291
36944
  let drift = false;
36292
36945
  let probe = null;
36293
36946
  try {
@@ -36369,7 +37022,7 @@ function recordEvent(h, input) {
36369
37022
  const explicit = requireId("episode_id", input.episode_id, true);
36370
37023
  if (input.terminal_state !== null && input.terminal_state !== void 0 && !TERMINAL_STATES.includes(input.terminal_state)) {
36371
37024
  throw new JournalContractError(
36372
- `terminal_state ${JSON.stringify(input.terminal_state)} is not one of the six.`
37025
+ `terminal_state ${JSON.stringify(input.terminal_state)} is not in the run vocabulary.`
36373
37026
  );
36374
37027
  }
36375
37028
  if (input.consumption !== null && input.consumption !== void 0 && !CONSUMPTION_STATES.includes(input.consumption)) {
@@ -36530,10 +37183,10 @@ function _isCliEntry51() {
36530
37183
  return false;
36531
37184
  }
36532
37185
  if (process.argv[1] === void 0) return false;
36533
- const argvUrl = pathToFileURL58(path100.resolve(process.argv[1])).href;
37186
+ const argvUrl = pathToFileURL58(path101.resolve(process.argv[1])).href;
36534
37187
  if (import.meta.url === argvUrl) return true;
36535
37188
  try {
36536
- return fs98.realpathSync(fileURLToPath65(import.meta.url)) === fs98.realpathSync(path100.resolve(process.argv[1]));
37189
+ return fs99.realpathSync(fileURLToPath65(import.meta.url)) === fs99.realpathSync(path101.resolve(process.argv[1]));
36537
37190
  } catch {
36538
37191
  return false;
36539
37192
  }
@@ -36696,8 +37349,8 @@ function _py_json_dumps2(value, indent) {
36696
37349
 
36697
37350
  // src/scripts/_lib/collector_denominator.ts
36698
37351
  import crypto15 from "node:crypto";
36699
- import fs100 from "node:fs";
36700
- import path102 from "node:path";
37352
+ import fs101 from "node:fs";
37353
+ import path103 from "node:path";
36701
37354
 
36702
37355
  // src/scripts/_lib/collector_record.ts
36703
37356
  var COLLECTOR_EVENTS = [
@@ -36774,30 +37427,30 @@ var FIELD_PURPOSE = {
36774
37427
  var ALLOWED_FIELDS = new Set(Object.keys(FIELD_PURPOSE));
36775
37428
 
36776
37429
  // src/scripts/_lib/collector_store.ts
36777
- import * as fs99 from "node:fs";
37430
+ import * as fs100 from "node:fs";
36778
37431
  import * as os12 from "node:os";
36779
- import * as path101 from "node:path";
37432
+ import * as path102 from "node:path";
36780
37433
  var COLLECTOR_DIR_NAME = "agent-collector";
36781
37434
  var COLLECTOR_FILE_NAME = "collector.sqlite";
36782
37435
  var OPT_OUT_MARKER_NAME = "OPT-OUT";
36783
37436
  var MIGRATION_MARKER_NAME = "MIGRATION-IN-FLIGHT";
36784
37437
  var QUARANTINE_DIR_NAME = "quarantine";
36785
37438
  function defaultUserRoot() {
36786
- return path101.join(os12.homedir(), ".event4u", "agent-config");
37439
+ return path102.join(os12.homedir(), ".event4u", "agent-config");
36787
37440
  }
36788
37441
  function resolveCollectorStore(userRoot = defaultUserRoot()) {
36789
- const dir = path101.join(userRoot, COLLECTOR_DIR_NAME);
37442
+ const dir = path102.join(userRoot, COLLECTOR_DIR_NAME);
36790
37443
  return {
36791
37444
  userRoot,
36792
37445
  root: dir,
36793
- path: path101.join(dir, COLLECTOR_FILE_NAME),
36794
- optOutMarker: path101.join(dir, OPT_OUT_MARKER_NAME),
36795
- migrationMarker: path101.join(dir, MIGRATION_MARKER_NAME),
36796
- quarantineDir: path101.join(dir, QUARANTINE_DIR_NAME)
37446
+ path: path102.join(dir, COLLECTOR_FILE_NAME),
37447
+ optOutMarker: path102.join(dir, OPT_OUT_MARKER_NAME),
37448
+ migrationMarker: path102.join(dir, MIGRATION_MARKER_NAME),
37449
+ quarantineDir: path102.join(dir, QUARANTINE_DIR_NAME)
36797
37450
  };
36798
37451
  }
36799
37452
  function isOptedOut(userRoot = defaultUserRoot()) {
36800
- return fs99.existsSync(resolveCollectorStore(userRoot).optOutMarker);
37453
+ return fs100.existsSync(resolveCollectorStore(userRoot).optOutMarker);
36801
37454
  }
36802
37455
  var MIGRATIONS = Object.freeze({
36803
37456
  // 0 → 1. No DDL: `user_version` 0 is the default SQLite assigns, and a
@@ -36814,24 +37467,24 @@ var SPOOL_DIR_NAME = "spool";
36814
37467
  var SPOOL_PENDING_NAME = "pending.jsonl";
36815
37468
  var ENABLED_MARKER_NAME = "ENABLED";
36816
37469
  function denominatorPath(userRoot) {
36817
- return path102.join(resolveCollectorStore(userRoot).root, DENOMINATOR_FILE_NAME);
37470
+ return path103.join(resolveCollectorStore(userRoot).root, DENOMINATOR_FILE_NAME);
36818
37471
  }
36819
37472
  function spoolDir(userRoot) {
36820
- return path102.join(resolveCollectorStore(userRoot).root, SPOOL_DIR_NAME);
37473
+ return path103.join(resolveCollectorStore(userRoot).root, SPOOL_DIR_NAME);
36821
37474
  }
36822
37475
  function spoolPendingPath(userRoot) {
36823
- return path102.join(spoolDir(userRoot), SPOOL_PENDING_NAME);
37476
+ return path103.join(spoolDir(userRoot), SPOOL_PENDING_NAME);
36824
37477
  }
36825
37478
  function enabledMarkerPath(userRoot) {
36826
- return path102.join(resolveCollectorStore(userRoot).root, ENABLED_MARKER_NAME);
37479
+ return path103.join(resolveCollectorStore(userRoot).root, ENABLED_MARKER_NAME);
36827
37480
  }
36828
37481
  function isCollectorEnabled(userRoot) {
36829
37482
  if (isOptedOut(userRoot)) return false;
36830
- if (fs100.existsSync(killSwitchPathLocal(userRoot))) return false;
36831
- return fs100.existsSync(enabledMarkerPath(userRoot));
37483
+ if (fs101.existsSync(killSwitchPathLocal(userRoot))) return false;
37484
+ return fs101.existsSync(enabledMarkerPath(userRoot));
36832
37485
  }
36833
37486
  function killSwitchPathLocal(userRoot) {
36834
- return path102.join(resolveCollectorStore(userRoot).root, "STOP");
37487
+ return path103.join(resolveCollectorStore(userRoot).root, "STOP");
36835
37488
  }
36836
37489
  function utcDate(at = Date.now()) {
36837
37490
  return new Date(at).toISOString().slice(0, 10);
@@ -36843,8 +37496,8 @@ function recordOpportunity(event, platform, userRoot, at = Date.now(), opts = {}
36843
37496
  if (!COLLECTOR_EVENTS.includes(event)) return false;
36844
37497
  if (!COLLECTOR_PLATFORMS.includes(platform)) return false;
36845
37498
  const target = denominatorPath(userRoot);
36846
- fs100.mkdirSync(path102.dirname(target), { recursive: true });
36847
- fs100.appendFileSync(target, `${utcDate(at)} ${event} ${platform}
37499
+ fs101.mkdirSync(path103.dirname(target), { recursive: true });
37500
+ fs101.appendFileSync(target, `${utcDate(at)} ${event} ${platform}
36848
37501
  `);
36849
37502
  return true;
36850
37503
  } catch {
@@ -36858,19 +37511,19 @@ var MACHINE_ID_NAME = "machine-id";
36858
37511
  var EPISODE_DIR_NAME = "episodes";
36859
37512
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
36860
37513
  function machineIdPath(userRoot) {
36861
- return path102.join(resolveCollectorStore(userRoot).root, MACHINE_ID_NAME);
37514
+ return path103.join(resolveCollectorStore(userRoot).root, MACHINE_ID_NAME);
36862
37515
  }
36863
37516
  function machineId(userRoot) {
36864
37517
  const target = machineIdPath(userRoot);
36865
37518
  try {
36866
- const existing = fs100.readFileSync(target, "utf8").trim();
37519
+ const existing = fs101.readFileSync(target, "utf8").trim();
36867
37520
  if (UUID_RE.test(existing)) return existing;
36868
37521
  } catch {
36869
37522
  }
36870
37523
  try {
36871
37524
  const minted = crypto15.randomUUID();
36872
- fs100.mkdirSync(path102.dirname(target), { recursive: true });
36873
- fs100.writeFileSync(target, `${minted}
37525
+ fs101.mkdirSync(path103.dirname(target), { recursive: true });
37526
+ fs101.writeFileSync(target, `${minted}
36874
37527
  `);
36875
37528
  return minted;
36876
37529
  } catch {
@@ -36894,17 +37547,17 @@ function episodeId(env = process.env) {
36894
37547
  ].join("-");
36895
37548
  }
36896
37549
  function nextSequence(episode, userRoot) {
36897
- const dir = path102.join(resolveCollectorStore(userRoot).root, EPISODE_DIR_NAME);
36898
- const target = path102.join(dir, `${episode}.seq`);
37550
+ const dir = path103.join(resolveCollectorStore(userRoot).root, EPISODE_DIR_NAME);
37551
+ const target = path103.join(dir, `${episode}.seq`);
36899
37552
  try {
36900
- fs100.mkdirSync(dir, { recursive: true });
37553
+ fs101.mkdirSync(dir, { recursive: true });
36901
37554
  let current = 0;
36902
37555
  try {
36903
- const raw = Number.parseInt(fs100.readFileSync(target, "utf8").trim(), 10);
37556
+ const raw = Number.parseInt(fs101.readFileSync(target, "utf8").trim(), 10);
36904
37557
  if (Number.isInteger(raw) && raw >= 0) current = raw + 1;
36905
37558
  } catch {
36906
37559
  }
36907
- fs100.writeFileSync(target, `${current}
37560
+ fs101.writeFileSync(target, `${current}
36908
37561
  `);
36909
37562
  return current;
36910
37563
  } catch {
@@ -36943,12 +37596,12 @@ function spoolRecord(record, userRoot) {
36943
37596
  try {
36944
37597
  if (!isCollectorEnabled(userRoot)) return false;
36945
37598
  const target = spoolPendingPath(userRoot);
36946
- fs100.mkdirSync(path102.dirname(target), { recursive: true });
37599
+ fs101.mkdirSync(path103.dirname(target), { recursive: true });
36947
37600
  try {
36948
- if (fs100.statSync(target).size >= SPOOL_MAX_BYTES) return false;
37601
+ if (fs101.statSync(target).size >= SPOOL_MAX_BYTES) return false;
36949
37602
  } catch {
36950
37603
  }
36951
- fs100.appendFileSync(target, `${JSON.stringify(record)}
37604
+ fs101.appendFileSync(target, `${JSON.stringify(record)}
36952
37605
  `);
36953
37606
  return true;
36954
37607
  } catch {
@@ -36958,11 +37611,11 @@ function spoolRecord(record, userRoot) {
36958
37611
 
36959
37612
  // src/scripts/hooks/dispatch_hook.ts
36960
37613
  var _IN_BUNDLE = true;
36961
- var REPO_ROOT = path103.resolve(
36962
- path103.dirname(fileURLToPath66(import.meta.url)),
37614
+ var REPO_ROOT = path104.resolve(
37615
+ path104.dirname(fileURLToPath66(import.meta.url)),
36963
37616
  ..._IN_BUNDLE ? ["..", ".."] : ["..", "..", ".."]
36964
37617
  );
36965
- var MANIFEST_PATH = path103.join(REPO_ROOT, "src", "scripts", "hook_manifest.yaml");
37618
+ var MANIFEST_PATH = path104.join(REPO_ROOT, "src", "scripts", "hook_manifest.yaml");
36966
37619
  var EXIT_ALLOW35 = 0;
36967
37620
  var EXIT_BLOCK6 = 1;
36968
37621
  var EXIT_WARN18 = 2;
@@ -37024,11 +37677,11 @@ function _manifest_fingerprint(text) {
37024
37677
  return `${h.toString(16)}:${String(text.length)}`;
37025
37678
  }
37026
37679
  function _load_yaml3(p) {
37027
- const text = fs101.readFileSync(p, "utf-8");
37680
+ const text = fs102.readFileSync(p, "utf-8");
37028
37681
  const compiled = p.replace(/\.ya?ml$/u, ".json");
37029
37682
  if (compiled !== p) {
37030
37683
  try {
37031
- const raw = JSON.parse(fs101.readFileSync(compiled, "utf-8"));
37684
+ const raw = JSON.parse(fs102.readFileSync(compiled, "utf-8"));
37032
37685
  if (_isObject17(raw) && raw["fingerprint"] === _manifest_fingerprint(text) && _isObject17(raw["manifest"])) {
37033
37686
  return raw["manifest"];
37034
37687
  }
@@ -37134,7 +37787,7 @@ function _maybe_capture_payload(args, payload_text) {
37134
37787
  }
37135
37788
  try {
37136
37789
  const target = _expanduser3(capture_dir);
37137
- fs101.mkdirSync(target, { recursive: true });
37790
+ fs102.mkdirSync(target, { recursive: true });
37138
37791
  let payload;
37139
37792
  try {
37140
37793
  payload = payload_text.trim() ? JSON.parse(payload_text) : {};
@@ -37151,8 +37804,8 @@ function _maybe_capture_payload(args, payload_text) {
37151
37804
  const ts = Date.now();
37152
37805
  const native = (args.native_event || args.event).replace(/\//g, "_");
37153
37806
  const fname = `${args.platform}__${native}__${ts}__${process.pid}.json`;
37154
- fs101.writeFileSync(
37155
- path103.join(target, fname),
37807
+ fs102.writeFileSync(
37808
+ path104.join(target, fname),
37156
37809
  _py_json_dumps2(record, 2) + "\n",
37157
37810
  { encoding: "utf-8" }
37158
37811
  );
@@ -37163,7 +37816,7 @@ function _maybe_capture_payload(args, payload_text) {
37163
37816
  function _expanduser3(p) {
37164
37817
  if (p === "~" || p.startsWith("~/")) {
37165
37818
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "";
37166
- return path103.join(home, p.slice(1));
37819
+ return path104.join(home, p.slice(1));
37167
37820
  }
37168
37821
  return p;
37169
37822
  }
@@ -37195,25 +37848,25 @@ function _resolve_tsx_invocation2(scriptPath, scriptArgs) {
37195
37848
  const walkUp = (start) => {
37196
37849
  let dir = start;
37197
37850
  for (; ; ) {
37198
- const candidate = path103.join(dir, "node_modules", ".bin", binName);
37851
+ const candidate = path104.join(dir, "node_modules", ".bin", binName);
37199
37852
  if (_isFile8(candidate)) {
37200
37853
  return candidate;
37201
37854
  }
37202
- const parent = path103.dirname(dir);
37855
+ const parent = path104.dirname(dir);
37203
37856
  if (parent === dir) {
37204
37857
  return null;
37205
37858
  }
37206
37859
  dir = parent;
37207
37860
  }
37208
37861
  };
37209
- const tsx = walkUp(path103.dirname(scriptPath)) ?? walkUp(path103.dirname(fileURLToPath66(import.meta.url)));
37862
+ const tsx = walkUp(path104.dirname(scriptPath)) ?? walkUp(path104.dirname(fileURLToPath66(import.meta.url)));
37210
37863
  if (tsx !== null) {
37211
37864
  return { command: tsx, args: [scriptPath, ...scriptArgs] };
37212
37865
  }
37213
37866
  return { command: "npx", args: ["tsx", scriptPath, ...scriptArgs] };
37214
37867
  }
37215
37868
  function _run_concern_inproc(main_fn, concern, envelope) {
37216
- const scriptAbs = path103.join(REPO_ROOT, String(concern["script"]));
37869
+ const scriptAbs = path104.join(REPO_ROOT, String(concern["script"]));
37217
37870
  const argsList = Array.isArray(concern["args"]) ? concern["args"].map((a) => String(a)) : [];
37218
37871
  argsList.push("--platform", String(envelope["platform"] || "generic"));
37219
37872
  const workspace = String(envelope["workspace_root"] || process.cwd());
@@ -37290,7 +37943,7 @@ function _run_concern(concern, envelope) {
37290
37943
  return _run_concern_inproc(inproc, concern, envelope);
37291
37944
  }
37292
37945
  }
37293
- const script = path103.join(REPO_ROOT, String(concern["script"]));
37946
+ const script = path104.join(REPO_ROOT, String(concern["script"]));
37294
37947
  const argsList = Array.isArray(concern["args"]) ? concern["args"].map((a) => String(a)) : [];
37295
37948
  const cmd = [script, ...argsList];
37296
37949
  cmd.push("--platform", String(envelope["platform"] || "generic"));
@@ -37347,7 +38000,7 @@ function _run_concern(concern, envelope) {
37347
38000
  }
37348
38001
  function _isFile8(p) {
37349
38002
  try {
37350
- return fs101.statSync(p).isFile();
38003
+ return fs102.statSync(p).isFile();
37351
38004
  } catch {
37352
38005
  return false;
37353
38006
  }
@@ -37369,10 +38022,10 @@ function _write_feedback(envelope, session_id, entries, final_rc, started_at) {
37369
38022
  return;
37370
38023
  }
37371
38024
  const workspace = String(envelope["workspace_root"] || process.cwd());
37372
- const state_root = path103.join(workspace, "agents", "runtime", "state");
38025
+ const state_root = path104.join(workspace, "agents", "runtime", "state");
37373
38026
  const fb_dir = feedback_dir(state_root, session_id);
37374
38027
  try {
37375
- fs101.mkdirSync(fb_dir, { recursive: true });
38028
+ fs102.mkdirSync(fb_dir, { recursive: true });
37376
38029
  } catch (exc) {
37377
38030
  const msg = exc instanceof Error ? exc.message : String(exc);
37378
38031
  process.stderr.write(`dispatch_hook: feedback dir unavailable: ${msg}
@@ -37380,7 +38033,7 @@ function _write_feedback(envelope, session_id, entries, final_rc, started_at) {
37380
38033
  return;
37381
38034
  }
37382
38035
  for (const entry of entries) {
37383
- const target = path103.join(fb_dir, `${entry["concern"]}.json`);
38036
+ const target = path104.join(fb_dir, `${entry["concern"]}.json`);
37384
38037
  try {
37385
38038
  atomic_write_json(target, entry);
37386
38039
  } catch (exc) {
@@ -37429,7 +38082,7 @@ function _write_feedback(envelope, session_id, entries, final_rc, started_at) {
37429
38082
  };
37430
38083
  try {
37431
38084
  const invocation = `${String(process.pid)}-${process.hrtime.bigint().toString()}`;
37432
- update_json_under_lock(path103.join(fb_dir, "summary.json"), (loaded) => {
38085
+ update_json_under_lock(path104.join(fb_dir, "summary.json"), (loaded) => {
37433
38086
  const priorRaw = loaded["invocations"];
37434
38087
  const prior = Array.isArray(priorRaw) ? priorRaw : [];
37435
38088
  const next = [...prior, { invocation, ...summary }];
@@ -37456,8 +38109,8 @@ function _record_rule_trips(envelope, entries) {
37456
38109
  if (tripped.length === 0) return;
37457
38110
  try {
37458
38111
  const workspace = String(envelope["workspace_root"] || process.cwd());
37459
- const state_dir = path103.join(workspace, "agents", "runtime", "state");
37460
- const target = path103.join(state_dir, "rule-trips.json");
38112
+ const state_dir = path104.join(workspace, "agents", "runtime", "state");
38113
+ const target = path104.join(state_dir, "rule-trips.json");
37461
38114
  const today = _now_iso3().slice(0, 10);
37462
38115
  update_json_under_lock(target, (loaded) => {
37463
38116
  const doc = { schema_version: 1, concerns: {}, ...loaded };
@@ -37599,7 +38252,7 @@ function main65(argv) {
37599
38252
  recordOpportunity(args.event, args.platform);
37600
38253
  recordCapture(args.event, args.platform);
37601
38254
  const manifest_path = args.manifest;
37602
- if (!fs101.existsSync(manifest_path)) {
38255
+ if (!fs102.existsSync(manifest_path)) {
37603
38256
  process.stderr.write(`dispatch_hook: manifest missing at ${manifest_path}
37604
38257
  `);
37605
38258
  return EXIT_ALLOW35;
@@ -37766,7 +38419,7 @@ function main65(argv) {
37766
38419
  }
37767
38420
  function _isDir4(p) {
37768
38421
  try {
37769
- return fs101.statSync(p).isDirectory();
38422
+ return fs102.statSync(p).isDirectory();
37770
38423
  } catch {
37771
38424
  return false;
37772
38425
  }
@@ -37783,13 +38436,13 @@ function _isCliEntry52() {
37783
38436
  if (process.argv[1] === void 0) {
37784
38437
  return false;
37785
38438
  }
37786
- const argvUrl = pathToFileURL59(path103.resolve(process.argv[1])).href;
38439
+ const argvUrl = pathToFileURL59(path104.resolve(process.argv[1])).href;
37787
38440
  if (import.meta.url === argvUrl) {
37788
38441
  return true;
37789
38442
  }
37790
38443
  try {
37791
- const here = fs101.realpathSync(fileURLToPath66(import.meta.url));
37792
- const argv = fs101.realpathSync(path103.resolve(process.argv[1]));
38444
+ const here = fs102.realpathSync(fileURLToPath66(import.meta.url));
38445
+ const argv = fs102.realpathSync(path104.resolve(process.argv[1]));
37793
38446
  return here === argv;
37794
38447
  } catch {
37795
38448
  return false;
@@ -37802,7 +38455,7 @@ if (isCliEntry14) {
37802
38455
 
37803
38456
  // src/scripts/hooks/dispatch_entry.ts
37804
38457
  var _selfPath = fileURLToPath67(import.meta.url);
37805
- var _invokedDirectly2 = globalThis.__AC_HOOKS_BUNDLE_DIRECT === true || process.argv[1] !== void 0 && path104.resolve(process.argv[1]) === path104.resolve(_selfPath);
38458
+ var _invokedDirectly2 = globalThis.__AC_HOOKS_BUNDLE_DIRECT === true || process.argv[1] !== void 0 && path105.resolve(process.argv[1]) === path105.resolve(_selfPath);
37806
38459
  if (_invokedDirectly2) {
37807
38460
  process.exitCode = main65(process.argv.slice(2));
37808
38461
  }