@event4u/agent-config 9.33.0 → 9.35.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 (105) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +154 -145
  3. package/CONTRIBUTING.md +23 -14
  4. package/dist/agent-src/commands/agent-handoff.md +15 -3
  5. package/dist/agent-src/commands/roadmap/next.md +37 -5
  6. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +35 -1
  7. package/dist/agent-src/scripts/roadmap_gates.ts +119 -4
  8. package/dist/agent-src/skills/corpus-grounding/SKILL.md +21 -12
  9. package/dist/agent-src/skills/design-tokens/SKILL.md +15 -0
  10. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +24 -0
  11. package/dist/agent-src/skills/rule-writing/SKILL.md +23 -0
  12. package/dist/agent-src/skills/skill-writing/SKILL.md +80 -2
  13. package/dist/agent-src/skills/systematic-debugging/SKILL.md +5 -3
  14. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +24 -0
  15. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +25 -0
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/agent-src/templates/scripts/memory_lookup.ts +1 -2
  18. package/dist/agent-src/templates/scripts/memory_signal.ts +2 -4
  19. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +35 -5
  20. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation.ts +252 -0
  21. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation_store.ts +307 -0
  22. package/dist/cli/registry.js +2 -2
  23. package/dist/cli/registry.js.map +1 -1
  24. package/dist/cli-delegate/{chunk-PMQ6Z3T3.js → chunk-2TGMGLBL.js} +11 -4
  25. package/dist/cli-delegate/{chunk-YPAWXXXR.js → chunk-55RI5UXK.js} +17 -0
  26. package/dist/cli-delegate/{chunk-MA4GFWDY.js → chunk-H6F2CQFI.js} +1 -1
  27. package/dist/cli-delegate/{chunk-2WELHO3P.js → chunk-I5EAFRII.js} +35 -0
  28. package/dist/cli-delegate/{chunk-3EWYC3YK.js → chunk-UEZ7D35A.js} +4 -3
  29. package/dist/cli-delegate/{chunk-UVKNIJH2.js → chunk-ZAXH3ZWU.js} +3 -0
  30. package/dist/cli-delegate/cmd_conformance.js +2 -2
  31. package/dist/cli-delegate/cmd_doctor.js +2 -2
  32. package/dist/cli-delegate/cmd_export.js +2 -2
  33. package/dist/cli-delegate/cmd_handoff.js +3 -3
  34. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  35. package/dist/cli-delegate/cmd_prune.js +2 -2
  36. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  37. package/dist/cli-delegate/cmd_session_recycle.js +7 -2
  38. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  39. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  40. package/dist/cli-delegate/cmd_sync.js +2 -2
  41. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  42. package/dist/cli-delegate/cmd_validate.js +2 -2
  43. package/dist/discovery/deprecation-report.md +1 -1
  44. package/dist/discovery/discovery-manifest.json +13 -13
  45. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  46. package/dist/discovery/discovery-manifest.summary.md +2 -2
  47. package/dist/discovery/dormancy-report.md +17 -0
  48. package/dist/discovery/orphan-report.md +1 -1
  49. package/dist/discovery/packs.json +3 -3
  50. package/dist/discovery/trust-report.md +1 -1
  51. package/dist/discovery/workspaces.json +3 -3
  52. package/dist/hooks/dispatch.js +355 -85
  53. package/dist/install/install.mjs +3 -0
  54. package/dist/mcp/registry-manifest.json +3 -3
  55. package/dist/mcp/server.json +2 -2
  56. package/dist/mcp/server.mjs +192 -12
  57. package/dist/server/schemas/settings.js +1 -0
  58. package/dist/server/schemas/settings.js.map +1 -1
  59. package/docs/contracts/settings-classes.md +3 -2
  60. package/docs/guidelines/agent-infra/failure-signatures.md +47 -12
  61. package/package.json +2 -2
  62. package/src/agent-src/scripts/archive_completed_roadmaps.ts +35 -1
  63. package/src/agent-src/scripts/roadmap_gates.ts +119 -4
  64. package/src/agent-src/templates/scripts/memory_lookup.ts +1 -2
  65. package/src/agent-src/templates/scripts/memory_signal.ts +2 -4
  66. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +35 -5
  67. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation.ts +252 -0
  68. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/staged_confirmation_store.ts +307 -0
  69. package/src/config/agent-settings.template.yml +11 -2
  70. package/src/config/quorum-attendance-budget.json +11 -3
  71. package/src/scripts/_cli/cmd_session_recycle.ts +6 -1
  72. package/src/scripts/_dispatch.bash +6 -2
  73. package/src/scripts/_lib/env_kill_switch.ts +37 -0
  74. package/src/scripts/_lib/security_lint.ts +18 -4
  75. package/src/scripts/_lib/self_repair.ts +60 -0
  76. package/src/scripts/_lib/self_repair_store.ts +65 -5
  77. package/src/scripts/_lib/session_register.ts +67 -1
  78. package/src/scripts/_lib/subagent_capsule.ts +78 -1
  79. package/src/scripts/ai_council/config.ts +60 -0
  80. package/src/scripts/ai_council/events_log.ts +105 -6
  81. package/src/scripts/ai_council/orchestrator.ts +38 -0
  82. package/src/scripts/ai_council/prompts.ts +165 -3
  83. package/src/scripts/ai_council/quorum.ts +87 -0
  84. package/src/scripts/ai_team/review_gate.ts +2 -2
  85. package/src/scripts/build_discovery_manifest.ts +165 -0
  86. package/src/scripts/check_claims.ts +51 -2
  87. package/src/scripts/ci_time_ratio.ts +3 -1
  88. package/src/scripts/council_cli.ts +36 -1
  89. package/src/scripts/discovery_graph.ts +201 -27
  90. package/src/scripts/dispatch_r2_reviewer.ts +102 -1
  91. package/src/scripts/hooks/turn_end_gate_hook.ts +172 -11
  92. package/src/scripts/lint_framework_leakage.ts +195 -28
  93. package/src/scripts/lint_framework_leakage_allowlist.json +94 -86
  94. package/src/scripts/lint_handoffs.ts +92 -2
  95. package/src/scripts/lint_hedge_words.ts +317 -0
  96. package/src/scripts/lint_mcp_config_security.ts +1 -1
  97. package/src/scripts/lint_skill_frontmatter_safety.ts +1 -1
  98. package/src/scripts/memory_lookup.ts +1 -2
  99. package/src/scripts/schemas/command.schema.json +4 -0
  100. package/src/scripts/schemas/skill.schema.json +4 -0
  101. package/src/scripts/self_repair_cli.ts +26 -1
  102. package/src/scripts/session_register_hook.ts +238 -18
  103. package/src/scripts/sessions_cli.ts +377 -12
  104. package/src/scripts/skill_linter.ts +58 -2
  105. package/src/server/schemas/settings.ts +3 -0
@@ -3996,10 +3996,10 @@ var require_resolve_block_map = __commonJS({
3996
3996
  let offset = bm.offset;
3997
3997
  let commentEnd = null;
3998
3998
  for (const collItem of bm.items) {
3999
- const { start, key, sep: sep6, value } = collItem;
3999
+ const { start, key, sep: sep7, value } = collItem;
4000
4000
  const keyProps = resolveProps.resolveProps(start, {
4001
4001
  indicator: "explicit-key-ind",
4002
- next: key ?? sep6?.[0],
4002
+ next: key ?? sep7?.[0],
4003
4003
  offset,
4004
4004
  onError,
4005
4005
  parentIndent: bm.indent,
@@ -4013,7 +4013,7 @@ var require_resolve_block_map = __commonJS({
4013
4013
  else if ("indent" in key && key.indent !== bm.indent)
4014
4014
  onError(offset, "BAD_INDENT", startColMsg);
4015
4015
  }
4016
- if (!keyProps.anchor && !keyProps.tag && !sep6) {
4016
+ if (!keyProps.anchor && !keyProps.tag && !sep7) {
4017
4017
  commentEnd = keyProps.end;
4018
4018
  if (keyProps.comment) {
4019
4019
  if (map.comment)
@@ -4037,7 +4037,7 @@ var require_resolve_block_map = __commonJS({
4037
4037
  ctx.atKey = false;
4038
4038
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
4039
4039
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
4040
- const valueProps = resolveProps.resolveProps(sep6 ?? [], {
4040
+ const valueProps = resolveProps.resolveProps(sep7 ?? [], {
4041
4041
  indicator: "map-value-ind",
4042
4042
  next: value,
4043
4043
  offset: keyNode.range[2],
@@ -4053,7 +4053,7 @@ var require_resolve_block_map = __commonJS({
4053
4053
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
4054
4054
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
4055
4055
  }
4056
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep6, null, valueProps, onError);
4056
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep7, null, valueProps, onError);
4057
4057
  if (ctx.schema.compat)
4058
4058
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
4059
4059
  offset = valueNode.range[2];
@@ -4144,7 +4144,7 @@ var require_resolve_end = __commonJS({
4144
4144
  let comment = "";
4145
4145
  if (end) {
4146
4146
  let hasSpace = false;
4147
- let sep6 = "";
4147
+ let sep7 = "";
4148
4148
  for (const token of end) {
4149
4149
  const { source, type } = token;
4150
4150
  switch (type) {
@@ -4158,13 +4158,13 @@ var require_resolve_end = __commonJS({
4158
4158
  if (!comment)
4159
4159
  comment = cb;
4160
4160
  else
4161
- comment += sep6 + cb;
4162
- sep6 = "";
4161
+ comment += sep7 + cb;
4162
+ sep7 = "";
4163
4163
  break;
4164
4164
  }
4165
4165
  case "newline":
4166
4166
  if (comment)
4167
- sep6 += source;
4167
+ sep7 += source;
4168
4168
  hasSpace = true;
4169
4169
  break;
4170
4170
  default:
@@ -4207,18 +4207,18 @@ var require_resolve_flow_collection = __commonJS({
4207
4207
  let offset = fc.offset + fc.start.source.length;
4208
4208
  for (let i = 0; i < fc.items.length; ++i) {
4209
4209
  const collItem = fc.items[i];
4210
- const { start, key, sep: sep6, value } = collItem;
4210
+ const { start, key, sep: sep7, value } = collItem;
4211
4211
  const props = resolveProps.resolveProps(start, {
4212
4212
  flow: fcName,
4213
4213
  indicator: "explicit-key-ind",
4214
- next: key ?? sep6?.[0],
4214
+ next: key ?? sep7?.[0],
4215
4215
  offset,
4216
4216
  onError,
4217
4217
  parentIndent: fc.indent,
4218
4218
  startOnNewline: false
4219
4219
  });
4220
4220
  if (!props.found) {
4221
- if (!props.anchor && !props.tag && !sep6 && !value) {
4221
+ if (!props.anchor && !props.tag && !sep7 && !value) {
4222
4222
  if (i === 0 && props.comma)
4223
4223
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
4224
4224
  else if (i < fc.items.length - 1)
@@ -4272,8 +4272,8 @@ var require_resolve_flow_collection = __commonJS({
4272
4272
  }
4273
4273
  }
4274
4274
  }
4275
- if (!isMap && !sep6 && !props.found) {
4276
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep6, null, props, onError);
4275
+ if (!isMap && !sep7 && !props.found) {
4276
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep7, null, props, onError);
4277
4277
  coll.items.push(valueNode);
4278
4278
  offset = valueNode.range[2];
4279
4279
  if (isBlock(value))
@@ -4285,7 +4285,7 @@ var require_resolve_flow_collection = __commonJS({
4285
4285
  if (isBlock(key))
4286
4286
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
4287
4287
  ctx.atKey = false;
4288
- const valueProps = resolveProps.resolveProps(sep6 ?? [], {
4288
+ const valueProps = resolveProps.resolveProps(sep7 ?? [], {
4289
4289
  flow: fcName,
4290
4290
  indicator: "map-value-ind",
4291
4291
  next: value,
@@ -4296,8 +4296,8 @@ var require_resolve_flow_collection = __commonJS({
4296
4296
  });
4297
4297
  if (valueProps.found) {
4298
4298
  if (!isMap && !props.found && ctx.options.strict) {
4299
- if (sep6)
4300
- for (const st of sep6) {
4299
+ if (sep7)
4300
+ for (const st of sep7) {
4301
4301
  if (st === valueProps.found)
4302
4302
  break;
4303
4303
  if (st.type === "newline") {
@@ -4314,7 +4314,7 @@ var require_resolve_flow_collection = __commonJS({
4314
4314
  else
4315
4315
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
4316
4316
  }
4317
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep6, null, valueProps, onError) : null;
4317
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep7, null, valueProps, onError) : null;
4318
4318
  if (valueNode) {
4319
4319
  if (isBlock(value))
4320
4320
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -4494,7 +4494,7 @@ var require_resolve_block_scalar = __commonJS({
4494
4494
  chompStart = i + 1;
4495
4495
  }
4496
4496
  let value = "";
4497
- let sep6 = "";
4497
+ let sep7 = "";
4498
4498
  let prevMoreIndented = false;
4499
4499
  for (let i = 0; i < contentStart; ++i)
4500
4500
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -4511,24 +4511,24 @@ var require_resolve_block_scalar = __commonJS({
4511
4511
  indent = "";
4512
4512
  }
4513
4513
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
4514
- value += sep6 + indent.slice(trimIndent) + content;
4515
- sep6 = "\n";
4514
+ value += sep7 + indent.slice(trimIndent) + content;
4515
+ sep7 = "\n";
4516
4516
  } else if (indent.length > trimIndent || content[0] === " ") {
4517
- if (sep6 === " ")
4518
- sep6 = "\n";
4519
- else if (!prevMoreIndented && sep6 === "\n")
4520
- sep6 = "\n\n";
4521
- value += sep6 + indent.slice(trimIndent) + content;
4522
- sep6 = "\n";
4517
+ if (sep7 === " ")
4518
+ sep7 = "\n";
4519
+ else if (!prevMoreIndented && sep7 === "\n")
4520
+ sep7 = "\n\n";
4521
+ value += sep7 + indent.slice(trimIndent) + content;
4522
+ sep7 = "\n";
4523
4523
  prevMoreIndented = true;
4524
4524
  } else if (content === "") {
4525
- if (sep6 === "\n")
4525
+ if (sep7 === "\n")
4526
4526
  value += "\n";
4527
4527
  else
4528
- sep6 = "\n";
4528
+ sep7 = "\n";
4529
4529
  } else {
4530
- value += sep6 + content;
4531
- sep6 = " ";
4530
+ value += sep7 + content;
4531
+ sep7 = " ";
4532
4532
  prevMoreIndented = false;
4533
4533
  }
4534
4534
  }
@@ -4710,25 +4710,25 @@ var require_resolve_flow_scalar = __commonJS({
4710
4710
  if (!match)
4711
4711
  return source;
4712
4712
  let res = match[1];
4713
- let sep6 = " ";
4713
+ let sep7 = " ";
4714
4714
  let pos = first.lastIndex;
4715
4715
  line.lastIndex = pos;
4716
4716
  while (match = line.exec(source)) {
4717
4717
  if (match[1] === "") {
4718
- if (sep6 === "\n")
4719
- res += sep6;
4718
+ if (sep7 === "\n")
4719
+ res += sep7;
4720
4720
  else
4721
- sep6 = "\n";
4721
+ sep7 = "\n";
4722
4722
  } else {
4723
- res += sep6 + match[1];
4724
- sep6 = " ";
4723
+ res += sep7 + match[1];
4724
+ sep7 = " ";
4725
4725
  }
4726
4726
  pos = line.lastIndex;
4727
4727
  }
4728
4728
  const last = /[ \t]*(.*)/sy;
4729
4729
  last.lastIndex = pos;
4730
4730
  match = last.exec(source);
4731
- return res + sep6 + (match?.[1] ?? "");
4731
+ return res + sep7 + (match?.[1] ?? "");
4732
4732
  }
4733
4733
  function doubleQuotedValue(source, onError) {
4734
4734
  let res = "";
@@ -5538,14 +5538,14 @@ var require_cst_stringify = __commonJS({
5538
5538
  }
5539
5539
  }
5540
5540
  }
5541
- function stringifyItem({ start, key, sep: sep6, value }) {
5541
+ function stringifyItem({ start, key, sep: sep7, value }) {
5542
5542
  let res = "";
5543
5543
  for (const st of start)
5544
5544
  res += st.source;
5545
5545
  if (key)
5546
5546
  res += stringifyToken(key);
5547
- if (sep6)
5548
- for (const st of sep6)
5547
+ if (sep7)
5548
+ for (const st of sep7)
5549
5549
  res += st.source;
5550
5550
  if (value)
5551
5551
  res += stringifyToken(value);
@@ -6712,18 +6712,18 @@ var require_parser = __commonJS({
6712
6712
  if (this.type === "map-value-ind") {
6713
6713
  const prev = getPrevProps(this.peek(2));
6714
6714
  const start = getFirstKeyStartProps(prev);
6715
- let sep6;
6715
+ let sep7;
6716
6716
  if (scalar.end) {
6717
- sep6 = scalar.end;
6718
- sep6.push(this.sourceToken);
6717
+ sep7 = scalar.end;
6718
+ sep7.push(this.sourceToken);
6719
6719
  delete scalar.end;
6720
6720
  } else
6721
- sep6 = [this.sourceToken];
6721
+ sep7 = [this.sourceToken];
6722
6722
  const map = {
6723
6723
  type: "block-map",
6724
6724
  offset: scalar.offset,
6725
6725
  indent: scalar.indent,
6726
- items: [{ start, key: scalar, sep: sep6 }]
6726
+ items: [{ start, key: scalar, sep: sep7 }]
6727
6727
  };
6728
6728
  this.onKeyLine = true;
6729
6729
  this.stack[this.stack.length - 1] = map;
@@ -6876,15 +6876,15 @@ var require_parser = __commonJS({
6876
6876
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
6877
6877
  const start2 = getFirstKeyStartProps(it.start);
6878
6878
  const key = it.key;
6879
- const sep6 = it.sep;
6880
- sep6.push(this.sourceToken);
6879
+ const sep7 = it.sep;
6880
+ sep7.push(this.sourceToken);
6881
6881
  delete it.key;
6882
6882
  delete it.sep;
6883
6883
  this.stack.push({
6884
6884
  type: "block-map",
6885
6885
  offset: this.offset,
6886
6886
  indent: this.indent,
6887
- items: [{ start: start2, key, sep: sep6 }]
6887
+ items: [{ start: start2, key, sep: sep7 }]
6888
6888
  });
6889
6889
  } else if (start.length > 0) {
6890
6890
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -7078,13 +7078,13 @@ var require_parser = __commonJS({
7078
7078
  const prev = getPrevProps(parent);
7079
7079
  const start = getFirstKeyStartProps(prev);
7080
7080
  fixFlowSeqItems(fc);
7081
- const sep6 = fc.end.splice(1, fc.end.length);
7082
- sep6.push(this.sourceToken);
7081
+ const sep7 = fc.end.splice(1, fc.end.length);
7082
+ sep7.push(this.sourceToken);
7083
7083
  const map = {
7084
7084
  type: "block-map",
7085
7085
  offset: fc.offset,
7086
7086
  indent: fc.indent,
7087
- items: [{ start, key: fc, sep: sep6 }]
7087
+ items: [{ start, key: fc, sep: sep7 }]
7088
7088
  };
7089
7089
  this.onKeyLine = true;
7090
7090
  this.stack[this.stack.length - 1] = map;
@@ -7993,7 +7993,7 @@ function* iter_corpus(roots = DEFAULT_SCAN_ROOTS, exts = [".md"]) {
7993
7993
  function report(findings, opts) {
7994
7994
  const check_label = opts.check_label;
7995
7995
  if (findings.length === 0) {
7996
- process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note()}).
7996
+ process.stdout.write(`\u2705 ${check_label}: clean (${_corpus_note(opts.scanned_roots)}).
7997
7997
  `);
7998
7998
  return 0;
7999
7999
  }
@@ -8033,8 +8033,9 @@ function _pyG(n) {
8033
8033
  }
8034
8034
  return s;
8035
8035
  }
8036
- function _corpus_note() {
8037
- return "scanned " + DEFAULT_SCAN_ROOTS.join(", ");
8036
+ function _corpus_note(scanned_roots) {
8037
+ const roots = scanned_roots && scanned_roots.length > 0 ? scanned_roots : DEFAULT_SCAN_ROOTS;
8038
+ return "scanned " + roots.join(", ");
8038
8039
  }
8039
8040
  var PyFloat = class {
8040
8041
  constructor(value) {
@@ -14035,6 +14036,11 @@ var import_yaml3 = __toESM(require_dist(), 1);
14035
14036
  import * as fs14 from "node:fs";
14036
14037
  import * as os2 from "node:os";
14037
14038
  import * as path13 from "node:path";
14039
+
14040
+ // src/scripts/ai_council/quorum.ts
14041
+ var SOLO_FLOOR_MIN_PRESENT = 2;
14042
+
14043
+ // src/scripts/ai_council/config.ts
14038
14044
  var _VALID_PROVIDERS = /* @__PURE__ */ new Set([
14039
14045
  "anthropic",
14040
14046
  "openai",
@@ -14341,6 +14347,9 @@ function _build_config(raw, source_path) {
14341
14347
  _asDict(_getOr(raw, "cli_call_budget", {}))
14342
14348
  );
14343
14349
  const quorum = _build_quorum(_get(raw, "quorum", "majority"));
14350
+ const quorum_min_present = _build_quorum_min_present(
14351
+ _get(raw, "quorum_min_present", SOLO_FLOOR_MIN_PRESENT)
14352
+ );
14344
14353
  const necessity_classifier = _build_necessity_classifier(
14345
14354
  _asDict(_getOr(raw, "necessity_classifier", {}))
14346
14355
  );
@@ -14389,6 +14398,7 @@ function _build_config(raw, source_path) {
14389
14398
  consensus_scoring: consensus,
14390
14399
  cli_call_budget,
14391
14400
  quorum,
14401
+ quorum_min_present,
14392
14402
  necessity_classifier,
14393
14403
  model_downgrade,
14394
14404
  debate,
@@ -15216,6 +15226,14 @@ function _build_quorum(raw) {
15216
15226
  `\`quorum\`=${_pyRepr3(raw)} must be 'majority' or an integer >= 1 (got ${_pyTypeName(raw)}).`
15217
15227
  );
15218
15228
  }
15229
+ function _build_quorum_min_present(raw) {
15230
+ if (_isInt(raw) && raw >= 1) {
15231
+ return raw;
15232
+ }
15233
+ throw new CouncilConfigError(
15234
+ `\`quorum_min_present\`=${_pyRepr3(raw)} must be an integer >= 1 (got ${_pyTypeName(raw)}).`
15235
+ );
15236
+ }
15219
15237
  function _build_advisor(name, cfg) {
15220
15238
  if (!_isDict(cfg)) {
15221
15239
  throw new CouncilConfigError(`advisors.${name}: must be a mapping.`);
@@ -16205,6 +16223,38 @@ function hasBoundaryMarker(block) {
16205
16223
  // outside the datamarking while still riding in the same block.
16206
16224
  block.trimEnd().endsWith(ENVELOPE_BOUNDARY_CLOSE);
16207
16225
  }
16226
+ var DECISION_REVERSIBILITY_TAGS = ["reversible", "irreversible"];
16227
+ function decisionTagErrors(decisions) {
16228
+ if (!Array.isArray(decisions)) {
16229
+ return [];
16230
+ }
16231
+ const errors = [];
16232
+ for (const line of decisions) {
16233
+ if (typeof line !== "string") {
16234
+ continue;
16235
+ }
16236
+ const m = /\[([^\]]*)\]\s*$/.exec(line.trimEnd());
16237
+ if (m === null) {
16238
+ continue;
16239
+ }
16240
+ const inner = (m[1] ?? "").trim();
16241
+ const lowered = inner.toLowerCase();
16242
+ if (DECISION_REVERSIBILITY_TAGS.includes(lowered)) {
16243
+ if (inner !== lowered) {
16244
+ errors.push(
16245
+ `decisions: reversibility tag must be lower-case \u2014 got \`[${inner}]\`, expected \`[${lowered}]\``
16246
+ );
16247
+ }
16248
+ continue;
16249
+ }
16250
+ if (/^i?rr?ever[a-z]*[^a-z]*$/i.test(lowered)) {
16251
+ errors.push(
16252
+ `decisions: \`[${inner}]\` is not a reversibility tag \u2014 use exactly ` + DECISION_REVERSIBILITY_TAGS.map((t) => `\`[${t}]\``).join(" or ") + ", or drop the bracket"
16253
+ );
16254
+ }
16255
+ }
16256
+ return errors;
16257
+ }
16208
16258
  var RECYCLE_ENVELOPE_KEYS = /* @__PURE__ */ new Set([
16209
16259
  "capsule_version",
16210
16260
  "variant",
@@ -16217,6 +16267,7 @@ var RECYCLE_ENVELOPE_KEYS = /* @__PURE__ */ new Set([
16217
16267
  "not_carried_forward",
16218
16268
  "decisions",
16219
16269
  "constraints",
16270
+ "do_not_touch",
16220
16271
  "open_worker_envelopes",
16221
16272
  "artifact_paths",
16222
16273
  "assumptions",
@@ -16269,7 +16320,9 @@ function validateRecycleEnvelope(input) {
16269
16320
  checkList(errors, "remaining", e["remaining"], MAX_LINE_CHARS, true);
16270
16321
  checkList(errors, "not_carried_forward", e["not_carried_forward"], MAX_LINE_CHARS, true);
16271
16322
  checkList(errors, "decisions", e["decisions"], MAX_LINE_CHARS, false);
16323
+ errors.push(...decisionTagErrors(e["decisions"]));
16272
16324
  checkList(errors, "constraints", e["constraints"], MAX_LINE_CHARS, false);
16325
+ checkList(errors, "do_not_touch", e["do_not_touch"], MAX_REF_CHARS, false);
16273
16326
  checkList(errors, "open_worker_envelopes", e["open_worker_envelopes"], MAX_REF_CHARS, false);
16274
16327
  checkList(errors, "artifact_paths", e["artifact_paths"], MAX_REF_CHARS, false);
16275
16328
  checkList(errors, "suggested_skills", e["suggested_skills"], MAX_REF_CHARS, false);
@@ -18839,6 +18892,12 @@ function claude_config_dir() {
18839
18892
  return env !== "" ? env : path32.join(os4.homedir(), ".claude");
18840
18893
  }
18841
18894
 
18895
+ // src/scripts/_lib/env_kill_switch.ts
18896
+ var _OFF_VALUES = /* @__PURE__ */ new Set(["", "0", "false", "False"]);
18897
+ function isEnvKillSwitchActive(name) {
18898
+ return !_OFF_VALUES.has(process.env[name] ?? "");
18899
+ }
18900
+
18842
18901
  // src/scripts/ai_team/config.ts
18843
18902
  var TeamConfigError = class extends Error {
18844
18903
  constructor(message) {
@@ -19048,8 +19107,7 @@ function circuit_breaker_notice(bound) {
19048
19107
  return `\u26A0\uFE0F team review-gate circuit breaker: ${bound} consecutive BLOCK verdict(s) this session (bound: \`ai_team.review_gate.max_consecutive_blocks\`). The managed gate stops re-blocking now \u2014 you decide: review the last BLOCK reason via \`/team:status\`, fix what is real, ignore what is not. An ALLOW verdict resets the counter.`;
19049
19108
  }
19050
19109
  function _kill_switch_active() {
19051
- const value = process.env[_KILL_SWITCH_ENV] ?? "";
19052
- return !(value === "" || value === "0" || value === "false" || value === "False");
19110
+ return isEnvKillSwitchActive(_KILL_SWITCH_ENV);
19053
19111
  }
19054
19112
  function _iso_seconds_z(d) {
19055
19113
  const pad = (n, w = 2) => String(n).padStart(w, "0");
@@ -19930,6 +19988,28 @@ function fingerprint2(defect_class, evidence) {
19930
19988
  const shape = evidence.toLowerCase().replace(/\d+/g, "#").replace(/[`"'‚'„"»«]/g, "").replace(/[^a-zà-ÿ#\s]/g, " ").replace(/\s+/g, " ").trim();
19931
19989
  return createHash4("sha256").update(`${defect_class}|${shape}`).digest("hex").slice(0, 16);
19932
19990
  }
19991
+ var NEW_RECORDS_PER_SOURCE_PER_WINDOW = 20;
19992
+ var CREATION_WINDOW_MS = 24 * 60 * 60 * 1e3;
19993
+ function recentCreations(existing, source, now) {
19994
+ const end = Date.parse(now);
19995
+ if (Number.isNaN(end)) {
19996
+ return 0;
19997
+ }
19998
+ return existing.filter((r) => {
19999
+ if (r.source !== source) {
20000
+ return false;
20001
+ }
20002
+ const t = Date.parse(r.first_seen);
20003
+ return Number.isNaN(t) || end - t < CREATION_WINDOW_MS && end - t >= 0;
20004
+ }).length;
20005
+ }
20006
+ function creationCapReached(existing, finding, now) {
20007
+ const fp = fingerprint2(finding.defect_class, finding.evidence);
20008
+ if (existing.some((r) => r.fingerprint === fp)) {
20009
+ return false;
20010
+ }
20011
+ return recentCreations(existing, finding.source, now) >= NEW_RECORDS_PER_SOURCE_PER_WINDOW;
20012
+ }
19933
20013
  function mergeRecord(existing, finding, now) {
19934
20014
  const fp = fingerprint2(finding.defect_class, finding.evidence);
19935
20015
  if (existing !== null && existing.fingerprint === fp) {
@@ -19995,7 +20075,7 @@ function listRecords(root) {
19995
20075
  }
19996
20076
  const out = [];
19997
20077
  for (const n of names.sort()) {
19998
- if (!n.endsWith(".json")) {
20078
+ if (!n.endsWith(".json") || n === OVERFLOW_FILE) {
19999
20079
  continue;
20000
20080
  }
20001
20081
  const rec = readRecord(root, n.slice(0, -".json".length));
@@ -20009,9 +20089,35 @@ function listRecords(root) {
20009
20089
  function openRecords(root) {
20010
20090
  return listRecords(root).filter((r) => r.status === "open");
20011
20091
  }
20092
+ var OVERFLOW_FILE = "_overflow.json";
20093
+ function overflowPath(root) {
20094
+ return path38.join(storeDir(root), OVERFLOW_FILE);
20095
+ }
20096
+ function readOverflow(root) {
20097
+ try {
20098
+ const parsed = JSON.parse(fs37.readFileSync(overflowPath(root), "utf-8"));
20099
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
20100
+ } catch {
20101
+ return {};
20102
+ }
20103
+ }
20104
+ function bumpOverflow(root, source, now) {
20105
+ const counts = readOverflow(root);
20106
+ const prev = counts[source]?.dropped ?? 0;
20107
+ counts[source] = { dropped: prev + 1, last_seen: now };
20108
+ const dir = storeDir(root);
20109
+ fs37.mkdirSync(dir, { recursive: true });
20110
+ fs37.writeFileSync(overflowPath(root), `${JSON.stringify(counts, null, 2)}
20111
+ `, "utf-8");
20112
+ }
20012
20113
  function upsertFinding(root, finding, now) {
20013
20114
  const fp = fingerprint2(finding.defect_class, finding.evidence);
20014
- const merged = mergeRecord(readRecord(root, fp), finding, now);
20115
+ const existing = readRecord(root, fp);
20116
+ if (existing === null && creationCapReached(listRecords(root), finding, now)) {
20117
+ bumpOverflow(root, finding.source, now);
20118
+ return null;
20119
+ }
20120
+ const merged = mergeRecord(existing, finding, now);
20015
20121
  writeRecord(root, merged);
20016
20122
  return merged;
20017
20123
  }
@@ -20405,35 +20511,109 @@ function foreign_live_records(dir, session_id, options = {}) {
20405
20511
  const own = safe_stem(session_id);
20406
20512
  return read_live_records(dir, options).filter((r) => safe_stem(r.session_id) !== own);
20407
20513
  }
20514
+ function classify_collisions(others, here) {
20515
+ const out = [];
20516
+ for (const r of others) {
20517
+ if (here.roadmap_slug !== null && r.roadmap_slug !== null && r.roadmap_slug === here.roadmap_slug) {
20518
+ out.push({ kind: "roadmap", record: r });
20519
+ }
20520
+ }
20521
+ for (const r of others) {
20522
+ if (here.branch !== null && r.branch === here.branch) {
20523
+ out.push({ kind: "branch", record: r });
20524
+ }
20525
+ }
20526
+ return out;
20527
+ }
20408
20528
 
20409
20529
  // src/scripts/session_register_hook.ts
20410
20530
  var REPLAY_ENV_VAR6 = "AGENT_CONFIG_REPLAY";
20411
- var ROADMAP_CLAIM_REL = path42.join("agents", "runtime", "state", "roadmap-claim.json");
20531
+ var _STATE_REL = path42.join("agents", "runtime", "state");
20532
+ var ROADMAP_CLAIM_REL = path42.join(_STATE_REL, "roadmap-claim.json");
20533
+ function roadmap_claim_rel(session_id) {
20534
+ const id = String(session_id ?? "").trim();
20535
+ if (id === "") {
20536
+ return ROADMAP_CLAIM_REL;
20537
+ }
20538
+ const rel = path42.join(_STATE_REL, `roadmap-claim-${safe_stem(id)}.json`);
20539
+ const norm = path42.normalize(rel);
20540
+ if (!norm.startsWith(`${_STATE_REL}${path42.sep}`) || norm.includes("..")) {
20541
+ return ROADMAP_CLAIM_REL;
20542
+ }
20543
+ return norm;
20544
+ }
20412
20545
  function _is_replay_mode4() {
20413
20546
  return String(process9.env[REPLAY_ENV_VAR6] ?? "").trim() !== "";
20414
20547
  }
20415
- function read_claimed_slug(workspace_root) {
20548
+ function _read_claim_file(file) {
20416
20549
  try {
20417
- const raw = fs41.readFileSync(path42.join(workspace_root, ROADMAP_CLAIM_REL), "utf-8");
20418
- const parsed = JSON.parse(raw);
20419
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
20420
- const slug = parsed["slug"];
20421
- if (typeof slug === "string" && slug.trim().length > 0) {
20422
- return slug.trim();
20423
- }
20550
+ const parsed = JSON.parse(fs41.readFileSync(file, "utf-8"));
20551
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
20552
+ return null;
20424
20553
  }
20425
- return null;
20554
+ const rec = parsed;
20555
+ const slug = rec["slug"];
20556
+ if (typeof slug !== "string" || slug.trim().length === 0) {
20557
+ return null;
20558
+ }
20559
+ const sid = rec["session_id"];
20560
+ return {
20561
+ slug: slug.trim(),
20562
+ ...typeof sid === "string" && sid.trim() !== "" ? { session_id: sid.trim() } : {}
20563
+ };
20426
20564
  } catch {
20427
20565
  return null;
20428
20566
  }
20429
20567
  }
20568
+ function read_claimed_slug(workspace_root, session_id) {
20569
+ const own = roadmap_claim_rel(session_id);
20570
+ if (own !== ROADMAP_CLAIM_REL) {
20571
+ const mine = _read_claim_file(path42.join(workspace_root, own));
20572
+ if (mine !== null) {
20573
+ return mine.slug;
20574
+ }
20575
+ }
20576
+ const legacy = _read_claim_file(path42.join(workspace_root, ROADMAP_CLAIM_REL));
20577
+ if (legacy === null) {
20578
+ return null;
20579
+ }
20580
+ const id = String(session_id ?? "").trim();
20581
+ if (id !== "") {
20582
+ return null;
20583
+ }
20584
+ return legacy.slug;
20585
+ }
20586
+ function claim_is_stale(workspace_root, slug, peer_worktree) {
20587
+ if (slug === null || slug.trim() === "") {
20588
+ return false;
20589
+ }
20590
+ const base = slug.trim().replace(/\.md$/, "");
20591
+ const rel = path42.normalize(path42.join("agents", "roadmaps", `${base}.md`));
20592
+ const inside = rel.startsWith(`agents${path42.sep}roadmaps${path42.sep}`) && !rel.includes("..");
20593
+ if (!inside || path42.isAbsolute(base)) {
20594
+ return true;
20595
+ }
20596
+ const roots = [peer_worktree, workspace_root].filter(
20597
+ (r) => typeof r === "string" && r.trim() !== ""
20598
+ );
20599
+ for (const root of roots) {
20600
+ try {
20601
+ if (fs41.existsSync(path42.join(root, rel))) {
20602
+ return false;
20603
+ }
20604
+ } catch {
20605
+ return false;
20606
+ }
20607
+ }
20608
+ return true;
20609
+ }
20430
20610
  function build_record(workspace_root, session_id, platform, started_at, now = /* @__PURE__ */ new Date()) {
20431
20611
  return {
20432
20612
  session_id,
20433
20613
  platform,
20434
20614
  worktree: workspace_root,
20435
20615
  branch: current_branch(workspace_root),
20436
- roadmap_slug: read_claimed_slug(workspace_root),
20616
+ roadmap_slug: read_claimed_slug(workspace_root, session_id),
20437
20617
  started_at,
20438
20618
  last_seen: iso_now(now)
20439
20619
  };
@@ -20451,20 +20631,26 @@ function foreign_sessions_block(workspace_root, session_id, now = /* @__PURE__ *
20451
20631
  const others = foreign_live_records(dir, session_id, { prune: true, now });
20452
20632
  if (others.length === 0) return null;
20453
20633
  const here = current_branch(workspace_root);
20634
+ const my_slug = read_claimed_slug(workspace_root, session_id);
20454
20635
  const lines = [];
20455
- let collision = null;
20636
+ const live_others = [];
20456
20637
  for (const r of others) {
20457
20638
  const age_min = Math.max(0, Math.round((now.getTime() - Date.parse(r.last_seen)) / 6e4));
20458
- const slug = r.roadmap_slug === null ? "no roadmap claimed" : `roadmap: ${r.roadmap_slug}`;
20639
+ const stale = claim_is_stale(workspace_root, r.roadmap_slug, r.worktree);
20640
+ const slug = r.roadmap_slug === null ? "no roadmap claimed" : stale ? `roadmap: ${r.roadmap_slug} (STALE \u2014 no such open roadmap; treat as no claim)` : `roadmap: ${r.roadmap_slug}`;
20459
20641
  const ttl_note = ttl_is_measured(r.platform) ? "" : ` \xB7 unmeasured host, TTL falls back to ${Math.round(ttl_seconds_for(r.platform) / 3600)}h`;
20460
20642
  lines.push(
20461
20643
  `- ${r.platform} \xB7 branch \`${r.branch ?? "detached"}\` \xB7 ${slug} \xB7 last seen ${age_min} min ago${ttl_note}
20462
20644
  worktree: ${r.worktree}`
20463
20645
  );
20464
- if (collision === null && here !== null && r.branch === here) {
20465
- collision = r;
20466
- }
20646
+ live_others.push(stale ? { ...r, roadmap_slug: null } : r);
20467
20647
  }
20648
+ const collisions = classify_collisions(live_others, {
20649
+ branch: here,
20650
+ roadmap_slug: claim_is_stale(workspace_root, my_slug) ? null : my_slug
20651
+ });
20652
+ const roadmap_hits = collisions.filter((c) => c.kind === "roadmap");
20653
+ const branch_hit = collisions.find((c) => c.kind === "branch") ?? null;
20468
20654
  const parts = [
20469
20655
  "<session-register>",
20470
20656
  "The following OTHER agent sessions are live on this repository right now.",
@@ -20472,7 +20658,23 @@ function foreign_sessions_block(workspace_root, session_id, now = /* @__PURE__ *
20472
20658
  "",
20473
20659
  ...lines
20474
20660
  ];
20475
- if (collision !== null) {
20661
+ if (roadmap_hits.length > 0) {
20662
+ parts.push(
20663
+ "",
20664
+ `DUPLICATE WORK: ${roadmap_hits.length} other live session(s) claim the SAME roadmap \`${my_slug}\`.`,
20665
+ ...roadmap_hits.map(
20666
+ (h) => ` \xB7 branch \`${h.record.branch ?? "detached"}\` in ${h.record.worktree}`
20667
+ ),
20668
+ "A different branch name does NOT make this a different task \u2014 measured twice,",
20669
+ "two sessions built one roadmap phase under two branch names and one PR was wasted.",
20670
+ "STOP before writing code. Ask the user ONCE, as numbered options:",
20671
+ " 1. Take a different roadmap (`agent-config sessions:list` shows what is held)",
20672
+ " 2. Split the roadmap explicitly \u2014 name which phases are yours",
20673
+ " 3. Continue anyway, accepting that one of the two results is discarded",
20674
+ "Never decide silently, in either direction."
20675
+ );
20676
+ }
20677
+ if (branch_hit !== null) {
20476
20678
  parts.push(
20477
20679
  "",
20478
20680
  `COLLISION: another live session already holds branch \`${here}\`.`,
@@ -20513,6 +20715,10 @@ function main25() {
20513
20715
  if (event === "session_end" || event === "stop" && stop_means_session_end(platform)) {
20514
20716
  const dir = register_dir(root);
20515
20717
  if (dir !== null) delete_record(dir, session_id);
20718
+ try {
20719
+ fs41.unlinkSync(path42.join(root, roadmap_claim_rel(session_id)));
20720
+ } catch {
20721
+ }
20516
20722
  return 0;
20517
20723
  }
20518
20724
  if (event === "session_start" || event === "user_prompt_submit" || event === "stop") {
@@ -22439,7 +22645,7 @@ import { fileURLToPath as fileURLToPath36, pathToFileURL as pathToFileURL29 } fr
22439
22645
 
22440
22646
  // src/install/rtkDetection.ts
22441
22647
  import { spawnSync as spawnSync4 } from "node:child_process";
22442
- import { existsSync as existsSync8, statSync as statSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9 } from "node:fs";
22648
+ import { existsSync as existsSync9, statSync as statSync9, readFileSync as readFileSync19, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9 } from "node:fs";
22443
22649
  import { homedir as homedir4 } from "node:os";
22444
22650
  import { delimiter, join as join19, dirname as dirname9 } from "node:path";
22445
22651
  var TOKEN_KILLER_GAIN_SIGNATURE = /RTK Token Savings/;
@@ -22452,7 +22658,7 @@ function resolveBinaryOnPath(name, pathEnv) {
22452
22658
  if (dir.length === 0) continue;
22453
22659
  for (const suffix of suffixes) {
22454
22660
  const candidate = join19(dir, `${name}${suffix}`);
22455
- if (existsSync8(candidate)) return candidate;
22661
+ if (existsSync9(candidate)) return candidate;
22456
22662
  }
22457
22663
  }
22458
22664
  return null;
@@ -25266,6 +25472,32 @@ function detectLanguage(reply, pinned) {
25266
25472
  reason: `the pinned reply language is "${pinned}" but the reply classifies as "${verdict.language}" (${verdict.de_markers} de / ${verdict.en_markers} en markers; code, quotes, tables, URLs and paths excluded, then scored lead-first by the same classifier that sets the pin)`
25267
25473
  };
25268
25474
  }
25475
+ var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
25476
+ var _VERIFY_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|typecheck|lint|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
25477
+ function isVerificationCommand(command) {
25478
+ return _VERIFY_RE.test(command);
25479
+ }
25480
+ function detectUnverifiedEdit(toolCalls) {
25481
+ let lastEdit = -1;
25482
+ for (let i = 0; i < toolCalls.length; i += 1) {
25483
+ if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
25484
+ }
25485
+ if (lastEdit === -1) return null;
25486
+ for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
25487
+ const c = toolCalls[i];
25488
+ if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
25489
+ return null;
25490
+ }
25491
+ }
25492
+ const edited = toolCalls[lastEdit].path;
25493
+ return {
25494
+ detector: "verification",
25495
+ // The path, never the diff: the evidence span is quoted into a refusal,
25496
+ // and `ToolCall` is shaped so a file body cannot reach it.
25497
+ evidence: edited ?? toolCalls[lastEdit].name,
25498
+ reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
25499
+ };
25500
+ }
25269
25501
  function stateDir(workspaceRoot2) {
25270
25502
  return path59.join(workspaceRoot2, "agents", "runtime", "state", "turn-end-gate");
25271
25503
  }
@@ -25298,7 +25530,7 @@ function markRefusedTurn(workspaceRoot2, sessionKey, turnOrdinal, detector) {
25298
25530
  }
25299
25531
  }
25300
25532
  function readTranscriptTail(transcriptPath, opts = {}) {
25301
- const empty = { lastAssistant: "", turnOrdinal: 0 };
25533
+ const empty = { lastAssistant: "", turnOrdinal: 0, toolCalls: [] };
25302
25534
  if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
25303
25535
  let lines;
25304
25536
  try {
@@ -25311,6 +25543,7 @@ function readTranscriptTail(transcriptPath, opts = {}) {
25311
25543
  }
25312
25544
  let lastAssistant = "";
25313
25545
  let turnOrdinal = 0;
25546
+ let toolCalls = [];
25314
25547
  for (const rawLine of lines) {
25315
25548
  const line = rawLine.trim();
25316
25549
  if (!line) continue;
@@ -25327,15 +25560,42 @@ function readTranscriptTail(transcriptPath, opts = {}) {
25327
25560
  if (role !== "assistant" && role !== "user") continue;
25328
25561
  const msg = entry["message"];
25329
25562
  if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
25330
- const text = _messageText(msg["content"]);
25563
+ const content = msg["content"];
25564
+ if (role === "assistant") {
25565
+ toolCalls.push(..._toolCalls(content));
25566
+ }
25567
+ const text = _messageText(content);
25331
25568
  if (text === null) continue;
25332
25569
  if (role === "assistant") {
25333
25570
  lastAssistant = text;
25334
25571
  } else if (!isSyntheticPrompt(text)) {
25335
25572
  turnOrdinal += 1;
25573
+ toolCalls = [];
25336
25574
  }
25337
25575
  }
25338
- return { lastAssistant: lastAssistant.trim(), turnOrdinal };
25576
+ return { lastAssistant: lastAssistant.trim(), turnOrdinal, toolCalls };
25577
+ }
25578
+ function _toolCalls(content) {
25579
+ if (!Array.isArray(content)) return [];
25580
+ const out = [];
25581
+ for (const blk of content) {
25582
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
25583
+ const b = blk;
25584
+ if (b["type"] !== "tool_use") continue;
25585
+ const name = b["name"];
25586
+ if (typeof name !== "string") continue;
25587
+ const input = b["input"];
25588
+ const call = { name };
25589
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
25590
+ const inp = input;
25591
+ const cmd = inp["command"];
25592
+ if (typeof cmd === "string") call.command = cmd;
25593
+ const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
25594
+ if (typeof p === "string") call.path = p;
25595
+ }
25596
+ out.push(call);
25597
+ }
25598
+ return out;
25339
25599
  }
25340
25600
  function _messageText(content) {
25341
25601
  if (typeof content === "string") return content;
@@ -25359,7 +25619,12 @@ function _isOn(v) {
25359
25619
  return null;
25360
25620
  }
25361
25621
  function readGateSettings(workspaceRoot2) {
25362
- const off = { enabled: false, promissory: true, language: true };
25622
+ const off = {
25623
+ enabled: false,
25624
+ promissory: true,
25625
+ language: true,
25626
+ verification: true
25627
+ };
25363
25628
  let settings;
25364
25629
  try {
25365
25630
  settings = load_agent_settings({ cwd: workspaceRoot2 });
@@ -25378,7 +25643,8 @@ function readGateSettings(workspaceRoot2) {
25378
25643
  return {
25379
25644
  enabled: flag("enabled", false),
25380
25645
  promissory: flag("promissory", true),
25381
- language: flag("language", true)
25646
+ language: flag("language", true),
25647
+ verification: flag("verification", true)
25382
25648
  };
25383
25649
  }
25384
25650
  function str4(v) {
@@ -25399,7 +25665,7 @@ function main41() {
25399
25665
  const transcriptPath = str4(
25400
25666
  payload["transcript_path"] ?? payload["transcriptPath"]
25401
25667
  );
25402
- const { lastAssistant, turnOrdinal } = readTranscriptTail(transcriptPath, {
25668
+ const { lastAssistant, turnOrdinal, toolCalls } = readTranscriptTail(transcriptPath, {
25403
25669
  maxBytes: TRANSCRIPT_READ_MAX_BYTES
25404
25670
  });
25405
25671
  if (!lastAssistant) return EXIT_ALLOW17;
@@ -25416,6 +25682,10 @@ function main41() {
25416
25682
  const f = detectLanguage(lastAssistant, readLanguagePin(workspaceRoot2));
25417
25683
  if (f) findings.push(f);
25418
25684
  }
25685
+ if (settings.verification) {
25686
+ const f = detectUnverifiedEdit(toolCalls);
25687
+ if (f) findings.push(f);
25688
+ }
25419
25689
  if (findings.length === 0) return EXIT_ALLOW17;
25420
25690
  markRefusedTurn(workspaceRoot2, sessionKey, turnOrdinal, findings[0].detector);
25421
25691
  const lines = findings.map(
@@ -25425,7 +25695,7 @@ function main41() {
25425
25695
  process.stderr.write(
25426
25696
  `turn-end-gate: REFUSED \u2014 this turn is not finished.
25427
25697
  ${lines.join("\n")}
25428
- Do the promised work now, or correct the language, then end the turn.
25698
+ Do the promised work now, correct the language, or run the verification the edit needs, then end the turn.
25429
25699
  This turn will not be refused a second time.
25430
25700
  Disable: hooks.turn_end_gate.enabled: false in .agent-settings.yml
25431
25701
  `