@event4u/agent-config 9.36.0 → 10.1.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 (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +117 -2
  3. package/agents/templates/.ai-council.yml.example +31 -51
  4. package/dist/agent-src/commands/analyze/conformance.md +15 -4
  5. package/dist/agent-src/rules/session-canary.md +28 -0
  6. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
  7. package/dist/agent-src/skills/adr-create/SKILL.md +5 -0
  8. package/dist/agent-src/skills/check-refs/SKILL.md +5 -0
  9. package/dist/agent-src/skills/lint-skills/SKILL.md +5 -0
  10. package/dist/agent-src/skills/md-language-check/SKILL.md +5 -0
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/{chunk-SFGARMAB.js → chunk-2H5SE7DU.js} +24 -4
  13. package/dist/cli-delegate/{chunk-U7CMAIOC.js → chunk-3JUTT26I.js} +1 -1
  14. package/dist/cli-delegate/{chunk-JP53LNVT.js → chunk-HJSKTMIF.js} +6 -14
  15. package/dist/cli-delegate/{chunk-QEEB4LHO.js → chunk-LRXJK6GN.js} +1 -1
  16. package/dist/cli-delegate/{chunk-NR7HSN3P.js → chunk-NX5AXSLD.js} +3 -3
  17. package/dist/cli-delegate/{chunk-AG47LAGQ.js → chunk-TVGXH7VA.js} +1 -1
  18. package/dist/cli-delegate/{chunk-55RI5UXK.js → chunk-XT2YTJCZ.js} +22 -18
  19. package/dist/cli-delegate/{chunk-JEJABGKB.js → chunk-XTC5ARB6.js} +16 -12
  20. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  21. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  22. package/dist/cli-delegate/cmd_conformance.js +1037 -196
  23. package/dist/cli-delegate/cmd_doctor.js +3 -3
  24. package/dist/cli-delegate/cmd_explain.js +2 -2
  25. package/dist/cli-delegate/cmd_export.js +4 -4
  26. package/dist/cli-delegate/cmd_handoff.js +3 -3
  27. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  28. package/dist/cli-delegate/cmd_migrate.js +2 -2
  29. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  30. package/dist/cli-delegate/cmd_prune.js +4 -4
  31. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  32. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  33. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  34. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  35. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  36. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  37. package/dist/cli-delegate/cmd_sync.js +4 -4
  38. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  39. package/dist/cli-delegate/cmd_update.js +1 -1
  40. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  41. package/dist/cli-delegate/cmd_validate.js +4 -4
  42. package/dist/cli-delegate/cmd_versions.js +1 -1
  43. package/dist/discovery/deprecation-report.md +1 -1
  44. package/dist/discovery/discovery-manifest.json +8 -8
  45. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  46. package/dist/discovery/discovery-manifest.summary.md +1 -1
  47. package/dist/discovery/dormancy-report.md +1 -1
  48. package/dist/discovery/orphan-report.md +1 -1
  49. package/dist/discovery/packs.json +2 -2
  50. package/dist/discovery/trust-report.md +1 -1
  51. package/dist/discovery/workspaces.json +2 -2
  52. package/dist/hooks/dispatch.js +125 -89
  53. package/dist/install/install.mjs +22 -26
  54. package/dist/mcp/registry-manifest.json +1 -1
  55. package/dist/mcp/server.json +2 -2
  56. package/dist/mcp/server.mjs +1579 -616
  57. package/dist/scripts/_lib/agent_settings.js +39 -25
  58. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  59. package/dist/scripts/condense.js +15 -2
  60. package/dist/scripts/condense.js.map +1 -1
  61. package/dist/server/routes/wizard.js +23 -3
  62. package/dist/server/routes/wizard.js.map +1 -1
  63. package/dist/server/schemas/settings.js +6 -6
  64. package/dist/server/schemas/settings.js.map +1 -1
  65. package/docs/contracts/settings-classes.md +14 -9
  66. package/package.json +1 -1
  67. package/src/agent-src/scripts/archive_completed_roadmaps.ts +17 -0
  68. package/src/config/agent-settings.template.yml +6 -36
  69. package/src/config/ci-local-parity.yml +44 -0
  70. package/src/scripts/_lib/agent_settings.ts +39 -25
  71. package/src/scripts/_lib/environment_detector.ts +52 -1
  72. package/src/scripts/_lib/untrusted_content.test.ts +175 -0
  73. package/src/scripts/_lib/untrusted_content.ts +184 -0
  74. package/src/scripts/ai_council/config.ts +62 -24
  75. package/src/scripts/before_complete_hook.ts +39 -0
  76. package/src/scripts/check_bridge_derivation.ts +89 -3
  77. package/src/scripts/check_ci_local_parity.ts +74 -0
  78. package/src/scripts/check_references.ts +15 -1
  79. package/src/scripts/condense.ts +16 -2
  80. package/src/scripts/conformance_scan.ts +200 -9
  81. package/src/scripts/council_cli.ts +95 -3
  82. package/src/scripts/hook_manifest.yaml +12 -6
  83. package/src/scripts/hooks/block_no_verify.ts +82 -9
  84. package/src/scripts/hooks/turn_end_gate_hook.ts +138 -105
  85. package/src/scripts/probe_promissory_closing.ts +180 -0
  86. package/src/scripts/probe_session_canary.ts +180 -0
  87. package/src/scripts/schemas/skill.schema.json +42 -0
  88. package/src/scripts/skill_linter.ts +57 -2
  89. package/src/server/schemas/settings.ts +6 -14
@@ -11852,14 +11852,18 @@ function load_agent_settings(options = {}) {
11852
11852
  _warn_removed_always_on_keys(merged);
11853
11853
  return merged;
11854
11854
  }
11855
- var REMOVED_ALWAYS_ON_KEYS = [
11856
- "subagents.enabled",
11857
- "subagents.auto",
11858
- "subagents.host_capabilities",
11859
- "subagents.budget_routing",
11860
- "ai_team.enabled"
11861
- ];
11862
- var _warnedRemovedAlwaysOnKeys = /* @__PURE__ */ new Set();
11855
+ var REMOVED_KEYS = /* @__PURE__ */ new Map([
11856
+ ["subagents.enabled", "always-on orchestration"],
11857
+ ["subagents.auto", "always-on orchestration"],
11858
+ ["subagents.host_capabilities", "always-on orchestration"],
11859
+ ["subagents.budget_routing", "always-on orchestration"],
11860
+ ["ai_team.enabled", "always-on orchestration"],
11861
+ ["hooks.turn_end_gate.enabled", "the turn-end gate is always armed"],
11862
+ ["hooks.turn_end_gate.promissory", "the turn-end gate is always armed"],
11863
+ ["hooks.turn_end_gate.language", "the turn-end gate is always armed"],
11864
+ ["hooks.turn_end_gate.verification", "the turn-end gate is always armed"]
11865
+ ]);
11866
+ var _warnedRemovedKeys = /* @__PURE__ */ new Set();
11863
11867
  function _readDottedSettingsPath(root, dotted) {
11864
11868
  let node = root;
11865
11869
  for (const part of dotted.split(".")) {
@@ -11871,15 +11875,15 @@ function _readDottedSettingsPath(root, dotted) {
11871
11875
  return node;
11872
11876
  }
11873
11877
  function _warn_removed_always_on_keys(merged) {
11874
- for (const key of REMOVED_ALWAYS_ON_KEYS) {
11875
- if (_warnedRemovedAlwaysOnKeys.has(key)) {
11878
+ for (const [key, reason] of REMOVED_KEYS) {
11879
+ if (_warnedRemovedKeys.has(key)) {
11876
11880
  continue;
11877
11881
  }
11878
11882
  if (_readDottedSettingsPath(merged, key) === void 0) {
11879
11883
  continue;
11880
11884
  }
11881
- _warnedRemovedAlwaysOnKeys.add(key);
11882
- process.stderr.write(`${key} was removed (always-on orchestration); ignored.
11885
+ _warnedRemovedKeys.add(key);
11886
+ process.stderr.write(`${key} was removed (${reason}); ignored.
11883
11887
  `);
11884
11888
  }
11885
11889
  }
@@ -14048,7 +14052,6 @@ var _VALID_PROVIDERS = /* @__PURE__ */ new Set([
14048
14052
  "xai",
14049
14053
  "perplexity"
14050
14054
  ]);
14051
- var _VALID_MODES = /* @__PURE__ */ new Set(["api", "manual", "cli", "auto"]);
14052
14055
  var _RAW_KEY_PREFIXES = [
14053
14056
  "sk-",
14054
14057
  "sk-ant-",
@@ -14301,7 +14304,11 @@ function _build_config(raw, source_path) {
14301
14304
  if (!_isBool(enabled4)) {
14302
14305
  throw new CouncilConfigError("`enabled` must be a bool.");
14303
14306
  }
14304
- const defaults = _build_defaults(_asDict(_getOr(raw, "defaults", {})));
14307
+ const ignored_transport_keys = [];
14308
+ const defaults = _build_defaults(
14309
+ _asDict(_getOr(raw, "defaults", {})),
14310
+ ignored_transport_keys
14311
+ );
14305
14312
  const cost_budget = _build_cost_budget(
14306
14313
  _asDict(_getOr(raw, "cost_budget", {}))
14307
14314
  );
@@ -14313,7 +14320,12 @@ function _build_config(raw, source_path) {
14313
14320
  for (const [name, cfg] of Object.entries(members_raw)) {
14314
14321
  members.set(
14315
14322
  name,
14316
- _build_member(name, _asDict(_orEmpty(cfg)), defaults.mode)
14323
+ _build_member(
14324
+ name,
14325
+ _asDict(_orEmpty(cfg)),
14326
+ defaults.mode,
14327
+ ignored_transport_keys
14328
+ )
14317
14329
  );
14318
14330
  }
14319
14331
  const advisors_raw = _getOr(raw, "advisors", {});
@@ -14412,7 +14424,8 @@ function _build_config(raw, source_path) {
14412
14424
  routing,
14413
14425
  low_impact,
14414
14426
  lens_overrides,
14415
- source_path
14427
+ source_path,
14428
+ ignored_transport_keys
14416
14429
  };
14417
14430
  }
14418
14431
  function _orEmpty(value) {
@@ -14890,16 +14903,14 @@ function _build_consensus_scoring(d) {
14890
14903
  };
14891
14904
  }
14892
14905
  var _VALID_MEMBER_MODES = /* @__PURE__ */ new Set(["cli", "api"]);
14893
- function _build_defaults(d) {
14906
+ function _build_defaults(d, ignored = []) {
14894
14907
  if (!_isDict(d)) {
14895
14908
  throw new CouncilConfigError("`defaults` must be a mapping.");
14896
14909
  }
14897
- const mode = _get(d, "mode", "auto");
14898
- if (!(_isStr(mode) && _VALID_MODES.has(mode))) {
14899
- throw new CouncilConfigError(
14900
- `defaults.mode=${_pyRepr3(mode)} not in ${_sortedListRepr(_VALID_MODES)}.`
14901
- );
14910
+ if (_get(d, "mode", null) !== null) {
14911
+ ignored.push("defaults.mode");
14902
14912
  }
14913
+ const mode = "auto";
14903
14914
  const member_mode = _get(d, "member_mode", "cli");
14904
14915
  if (!(_isStr(member_mode) && _VALID_MEMBER_MODES.has(member_mode))) {
14905
14916
  throw new CouncilConfigError(
@@ -15056,7 +15067,7 @@ function _build_cost_budget(d) {
15056
15067
  }
15057
15068
  return cb;
15058
15069
  }
15059
- function _build_member(name, cfg, default_mode = "auto") {
15070
+ function _build_member(name, cfg, default_mode = "auto", ignored = []) {
15060
15071
  if (!_VALID_PROVIDERS.has(name)) {
15061
15072
  throw new CouncilConfigError(
15062
15073
  `members.${name}: unknown provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
@@ -15067,14 +15078,11 @@ function _build_member(name, cfg, default_mode = "auto") {
15067
15078
  const model = _pyTruthy(modelVal) ? modelVal : "";
15068
15079
  const api_key_ref_raw = _get(cfg, "api_key_ref", null);
15069
15080
  const api_key_ref = api_key_ref_raw === void 0 ? null : api_key_ref_raw;
15070
- const member_mode_raw = _get(cfg, "mode", null);
15071
- const member_mode = member_mode_raw === void 0 ? null : member_mode_raw;
15072
- if (member_mode !== null && !(_isStr(member_mode) && _VALID_MODES.has(member_mode))) {
15073
- throw new CouncilConfigError(
15074
- `members.${name}.mode=${_pyRepr3(member_mode)} not in ${_sortedListRepr(_VALID_MODES)}.`
15075
- );
15081
+ if (_get(cfg, "mode", null) !== null) {
15082
+ ignored.push(`members.${name}.mode`);
15076
15083
  }
15077
- const effective_mode = member_mode !== null ? member_mode : default_mode;
15084
+ const member_mode = null;
15085
+ const effective_mode = default_mode;
15078
15086
  const binary_raw = _get(cfg, "binary", null);
15079
15087
  const binary = binary_raw === void 0 ? null : binary_raw;
15080
15088
  if (binary !== null) {
@@ -17233,6 +17241,14 @@ function _empty_state2() {
17233
17241
  verified_this_turn: false,
17234
17242
  ci_saw_pending: false,
17235
17243
  nonevidence_this_turn: 0,
17244
+ // Round 7 § Phase 1 — the last CI read, SESSION-scoped on purpose. Every
17245
+ // other counter here is turn-scoped because a stale POSITIVE would wrongly
17246
+ // vouch ("I verified"). This one is a NEGATIVE ("CI was not settled"), and a
17247
+ // stale negative only ever refuses more often — so surviving the turn
17248
+ // boundary preserves the freshness invariant rather than bypassing it. It
17249
+ // has to survive it: the measured failure is a completion claim in a LATER
17250
+ // turn than the poll it rests on.
17251
+ ci_last: null,
17236
17252
  checked_at: _now2()
17237
17253
  };
17238
17254
  }
@@ -17360,6 +17376,7 @@ function _update2(state, event, envelope) {
17360
17376
  if (session_id && session_id !== state["session_id"]) {
17361
17377
  state["session_id"] = session_id;
17362
17378
  state["verifications_this_session"] = 0;
17379
+ state["ci_last"] = null;
17363
17380
  state = _reset_turn(state, session_id);
17364
17381
  }
17365
17382
  let payload = envelope["payload"];
@@ -17385,6 +17402,12 @@ function _update2(state, event, envelope) {
17385
17402
  } else {
17386
17403
  counts = false;
17387
17404
  }
17405
+ state["ci_last"] = {
17406
+ at: _now2(),
17407
+ command: cmd.slice(0, 512),
17408
+ pending,
17409
+ settled: pending === 0 && counts === true
17410
+ };
17388
17411
  }
17389
17412
  state["last_verification"] = {
17390
17413
  command: cmd.slice(0, 512),
@@ -22054,28 +22077,51 @@ function _looks_like_git_invocation(cmd, depth = 0) {
22054
22077
  }
22055
22078
  return false;
22056
22079
  }
22080
+ var _SHELL_CONSUMER_RE = /(?:^|[|&;]|\$\(|`)\s*(?:env\s+(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*)?(?:\S*\/)?(?:ba|z|k)?sh\b/;
22081
+ function _heredocs(cmd) {
22082
+ const re = /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1([\s\S]*?)^\s*\2\s*$/gm;
22083
+ const docs = [];
22084
+ let stripped = "";
22085
+ let last = 0;
22086
+ let m;
22087
+ while ((m = re.exec(cmd)) !== null) {
22088
+ const lineStart = cmd.lastIndexOf("\n", m.index) + 1;
22089
+ const prefix = cmd.slice(lineStart, m.index);
22090
+ docs.push({ body: m[3] ?? "", shellConsumed: _SHELL_CONSUMER_RE.test(prefix) });
22091
+ stripped += cmd.slice(last, m.index) + "<<HEREDOC";
22092
+ last = m.index + m[0].length;
22093
+ }
22094
+ stripped += cmd.slice(last);
22095
+ return { stripped, docs };
22096
+ }
22057
22097
  function _check_command(cmd, depth = 0) {
22098
+ const { stripped, docs } = depth <= 3 ? _heredocs(cmd) : { stripped: cmd, docs: [] };
22058
22099
  if (depth <= 3) {
22059
- const withoutHeredocs = cmd.replace(
22060
- /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[\s\S]*?^\s*\2\s*$/gm,
22061
- "<<HEREDOC"
22062
- );
22063
- for (const payload of substitutionPayloads(withoutHeredocs)) {
22100
+ for (const payload of substitutionPayloads(stripped)) {
22064
22101
  const [blocked, reason] = _check_command(payload, depth + 1);
22065
22102
  if (blocked) {
22066
22103
  return [true, reason];
22067
22104
  }
22068
22105
  }
22106
+ for (const doc of docs) {
22107
+ if (!doc.shellConsumed) {
22108
+ continue;
22109
+ }
22110
+ const [blocked, reason] = _check_command(doc.body, depth + 1);
22111
+ if (blocked) {
22112
+ return [true, reason];
22113
+ }
22114
+ }
22069
22115
  }
22070
22116
  let tokens;
22071
22117
  try {
22072
- tokens = shlexSplit(cmd);
22118
+ tokens = shlexSplit(stripped);
22073
22119
  } catch (e) {
22074
22120
  if (e instanceof ShlexError) {
22075
- if (_looks_like_git_invocation(cmd)) {
22121
+ if (_looks_like_git_invocation(stripped)) {
22076
22122
  return [
22077
22123
  true,
22078
- "command parse failed (shlex) on a git-containing command \u2014 fail-closed (git-history-discipline). \nMost common cause: an apostrophe or backtick inside a heredoc commit message in the SAME compound command as `git`. \nFix: write the message to a file and use `git commit -F <file>` \u2014 do not retry the heredoc."
22124
+ "command parse failed (shlex) on a git-containing command \u2014 fail-closed (git-history-discipline). \nThe quoting is unbalanced OUTSIDE any terminated heredoc \u2014 an unterminated heredoc, or an odd quote. \nbash refuses this command too; fix the quoting rather than working around the guard."
22079
22125
  ];
22080
22126
  }
22081
22127
  return [false, ""];
@@ -25654,6 +25700,33 @@ function detectPromissory(reply) {
25654
25700
  }
25655
25701
  return null;
25656
25702
  }
25703
+ function readCiSettled(workspaceRoot2) {
25704
+ try {
25705
+ const raw = fs60.readFileSync(path61.join(workspaceRoot2, STATE_FILE3), "utf-8");
25706
+ const decoded = JSON.parse(raw);
25707
+ if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
25708
+ const ci = decoded["ci_last"];
25709
+ if (typeof ci === "object" && ci !== null && !Array.isArray(ci)) {
25710
+ return { seen: true, settled: ci["settled"] === true };
25711
+ }
25712
+ }
25713
+ } catch {
25714
+ }
25715
+ return { seen: false, settled: false };
25716
+ }
25717
+ var _COMPLETION_RE = /(^|\n)\s*(?:\*\*)?(?:fertig\b|damit ist alles erledigt|aufgabe erledigt|alles erledigt\b|komplett(?:er)? (?:auftrag|abgearbeitet)|der komplette auftrag ist durch|done[.!]|all done\b|task complete)/i;
25718
+ function detectCompletionClaim(reply, ci) {
25719
+ if (!ci.seen || ci.settled) return null;
25720
+ const prose = visibleProse(reply);
25721
+ const m = _COMPLETION_RE.exec(prose);
25722
+ if (!m) return null;
25723
+ const at = m.index;
25724
+ return {
25725
+ detector: "completion",
25726
+ evidence: prose.slice(at, at + 120).trim(),
25727
+ reason: "a completion claim while the last CI read was not settled \u2014 read the verdict, then claim it (verify-before-complete)"
25728
+ };
25729
+ }
25657
25730
  function readLanguagePin(workspaceRoot2) {
25658
25731
  try {
25659
25732
  const raw = fs60.readFileSync(path61.join(workspaceRoot2, STATE_FILE5), "utf-8");
@@ -25816,43 +25889,6 @@ function _messageText(content) {
25816
25889
  }
25817
25890
  return parts.length > 0 ? parts.join("\n") : null;
25818
25891
  }
25819
- function _isOn(v) {
25820
- if (typeof v === "boolean") return v;
25821
- if (typeof v !== "string") return null;
25822
- const s = v.trim().toLowerCase();
25823
- if (s === "true" || s === "yes" || s === "on") return true;
25824
- if (s === "false" || s === "no" || s === "off") return false;
25825
- return null;
25826
- }
25827
- function readGateSettings(workspaceRoot2) {
25828
- const off = {
25829
- enabled: false,
25830
- promissory: true,
25831
- language: true,
25832
- verification: true
25833
- };
25834
- let settings;
25835
- try {
25836
- settings = load_agent_settings({ cwd: workspaceRoot2 });
25837
- } catch {
25838
- return off;
25839
- }
25840
- const hooks = settings["hooks"];
25841
- if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return off;
25842
- const block = hooks["turn_end_gate"];
25843
- if (typeof block !== "object" || block === null || Array.isArray(block)) return off;
25844
- const gate = block;
25845
- const flag = (name, dflt) => {
25846
- const v = _isOn(gate[name]);
25847
- return v === null ? dflt : v;
25848
- };
25849
- return {
25850
- enabled: flag("enabled", false),
25851
- promissory: flag("promissory", true),
25852
- language: flag("language", true),
25853
- verification: flag("verification", true)
25854
- };
25855
- }
25856
25892
  function str4(v) {
25857
25893
  return typeof v === "string" ? v : "";
25858
25894
  }
@@ -25866,8 +25902,6 @@ function main42() {
25866
25902
  }
25867
25903
  if (payload["stop_hook_active"] === true) return EXIT_ALLOW18;
25868
25904
  const workspaceRoot2 = str4(envelope["workspace_root"]) || process.cwd();
25869
- const settings = readGateSettings(workspaceRoot2);
25870
- if (!settings.enabled) return EXIT_ALLOW18;
25871
25905
  const transcriptPath = str4(
25872
25906
  payload["transcript_path"] ?? payload["transcriptPath"]
25873
25907
  );
@@ -25880,16 +25914,19 @@ function main42() {
25880
25914
  );
25881
25915
  if (alreadyRefusedTurn(workspaceRoot2, sessionKey, turnOrdinal)) return EXIT_ALLOW18;
25882
25916
  const findings = [];
25883
- if (settings.promissory) {
25884
- const f = detectPromissory(lastAssistant);
25885
- if (f) findings.push(f);
25886
- }
25887
- if (settings.language) {
25888
- const f = detectLanguage(lastAssistant, readLanguagePin(workspaceRoot2));
25889
- if (f) findings.push(f);
25890
- }
25891
- if (settings.verification) {
25892
- const f = detectUnverifiedEdit(toolCalls);
25917
+ for (const f of [
25918
+ detectPromissory(lastAssistant),
25919
+ detectLanguage(lastAssistant, readLanguagePin(workspaceRoot2)),
25920
+ detectUnverifiedEdit(toolCalls),
25921
+ // Round 7 § Phase 1 — detector D, in the same unconditional list as the
25922
+ // other three. It shipped with its own settings flag one commit earlier
25923
+ // and that flag is gone: `hooks.turn_end_gate.*` was deleted on
25924
+ // 2026-08-12 because a default-off safety gate is an absent one. Its
25925
+ // gating is where the comment above says gating belongs — inside the
25926
+ // detector: no CI observed, or a settled read, or no completion claim
25927
+ // ⇒ no finding.
25928
+ detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot2))
25929
+ ]) {
25893
25930
  if (f) findings.push(f);
25894
25931
  }
25895
25932
  if (findings.length === 0) return EXIT_ALLOW18;
@@ -25901,9 +25938,8 @@ function main42() {
25901
25938
  process.stderr.write(
25902
25939
  `turn-end-gate: REFUSED \u2014 this turn is not finished.
25903
25940
  ${lines.join("\n")}
25904
- Do the promised work now, correct the language, or run the verification the edit needs, then end the turn.
25941
+ Do the promised work now, correct the language, run the verification the edit needs, or read the CI verdict before claiming it \u2014 then end the turn.
25905
25942
  This turn will not be refused a second time.
25906
- Disable: hooks.turn_end_gate.enabled: false in .agent-settings.yml
25907
25943
  `
25908
25944
  );
25909
25945
  return EXIT_BLOCK4;
@@ -10210,14 +10210,18 @@ function load_agent_settings(options = {}) {
10210
10210
  _warn_removed_always_on_keys(merged);
10211
10211
  return merged;
10212
10212
  }
10213
- var REMOVED_ALWAYS_ON_KEYS = [
10214
- "subagents.enabled",
10215
- "subagents.auto",
10216
- "subagents.host_capabilities",
10217
- "subagents.budget_routing",
10218
- "ai_team.enabled"
10219
- ];
10220
- var _warnedRemovedAlwaysOnKeys = /* @__PURE__ */ new Set();
10213
+ var REMOVED_KEYS = /* @__PURE__ */ new Map([
10214
+ ["subagents.enabled", "always-on orchestration"],
10215
+ ["subagents.auto", "always-on orchestration"],
10216
+ ["subagents.host_capabilities", "always-on orchestration"],
10217
+ ["subagents.budget_routing", "always-on orchestration"],
10218
+ ["ai_team.enabled", "always-on orchestration"],
10219
+ ["hooks.turn_end_gate.enabled", "the turn-end gate is always armed"],
10220
+ ["hooks.turn_end_gate.promissory", "the turn-end gate is always armed"],
10221
+ ["hooks.turn_end_gate.language", "the turn-end gate is always armed"],
10222
+ ["hooks.turn_end_gate.verification", "the turn-end gate is always armed"]
10223
+ ]);
10224
+ var _warnedRemovedKeys = /* @__PURE__ */ new Set();
10221
10225
  function _readDottedSettingsPath(root, dotted) {
10222
10226
  let node = root;
10223
10227
  for (const part of dotted.split(".")) {
@@ -10229,15 +10233,15 @@ function _readDottedSettingsPath(root, dotted) {
10229
10233
  return node;
10230
10234
  }
10231
10235
  function _warn_removed_always_on_keys(merged) {
10232
- for (const key of REMOVED_ALWAYS_ON_KEYS) {
10233
- if (_warnedRemovedAlwaysOnKeys.has(key)) {
10236
+ for (const [key, reason] of REMOVED_KEYS) {
10237
+ if (_warnedRemovedKeys.has(key)) {
10234
10238
  continue;
10235
10239
  }
10236
10240
  if (_readDottedSettingsPath(merged, key) === void 0) {
10237
10241
  continue;
10238
10242
  }
10239
- _warnedRemovedAlwaysOnKeys.add(key);
10240
- process.stderr.write(`${key} was removed (always-on orchestration); ignored.
10243
+ _warnedRemovedKeys.add(key);
10244
+ process.stderr.write(`${key} was removed (${reason}); ignored.
10241
10245
  `);
10242
10246
  }
10243
10247
  }
@@ -15075,21 +15079,13 @@ var settingsSchema = external_exports.object({
15075
15079
  enabled: external_exports.boolean().default(false).describe(
15076
15080
  "PreToolUse code-graph nudge (ADR-124 Phase 4). Default off. When on AND a native code-graph cache or a consumer-shipped graph.json/SCIP index is present, warns once per session (never blocks) as the agent is about to Grep/Glob or Read a source file \u2014 query the graph first for who-calls/where-used/impact questions (or rebuild if stale, build if absent). Source G\u2019s strict block-first-read mode is deliberately un-ported."
15077
15081
  )
15078
- }).default({}),
15079
- turn_end_gate: external_exports.object({
15080
- enabled: external_exports.boolean().default(false).describe(
15081
- "Stop-slot turn-end gate (road-to-conformance-round5 Phase 3). Default off, and it is the only concern that can REFUSE a turn-end rather than a tool call \u2014 so it soaks behind an explicit opt-in before it binds. Round 5 measured the reason: both blocking carriers reached zero violations, neither advisory carrier did, and 19 wrong-language replies survived a pin that had fired 26-35 seconds earlier. A refused turn continues in the SAME turn and is never refused twice."
15082
- ),
15083
- promissory: external_exports.boolean().default(true).describe(
15084
- "Detector A of the turn-end gate: refuse a turn whose closing paragraph promises work not yet performed while asking the user nothing (20 measured occurrences). No fire rate is quoted here on purpose: the measured rate moved twice while the instrument itself was being corrected, so a number in a shipped description would be a moving target. Re-derive it with `measure-turn-end-gate --store <transcript dir>` against your own corpus before arming the gate. Inert while hooks.turn_end_gate.enabled is false."
15085
- ),
15086
- language: external_exports.boolean().default(true).describe(
15087
- "Detector B of the turn-end gate: refuse a turn whose reply is not in the pinned language (19 measured occurrences). Fenced code, inline code, quoted tool output, tables, URLs and paths are stripped first, and the remainder is then scored LEAD-FIRST by the same classifier that sets the pin \u2014 so the verdict usually comes from the reply opening, not from all of it. Sharing that classifier is deliberate: it stops the pin and the detector disagreeing about a turn. Accepted opt-in spellings for the switches below include YAML true/false and yes/no/on/off. Inert while hooks.turn_end_gate.enabled is false."
15088
- ),
15089
- verification: external_exports.boolean().default(true).describe(
15090
- "Detector C of the turn-end gate: refuse a turn that changed a file and then ran nothing that could have checked it \u2014 no test, type-check, lint or build call follows the LAST edit. Ordering is the point: a run before the final edit did not exercise it, which is the freshness clause verify-before-complete states. Read off the turn tool calls, not the prose, because a reply cannot say whether an edit was verified. Deliberately narrow on what counts as verification \u2014 a missed detection costs one unguarded turn, a false refusal teaches you to switch the gate off. No fire rate is quoted: none has been measured yet. Inert while hooks.turn_end_gate.enabled is false."
15091
- )
15092
15082
  }).default({})
15083
+ // `turn_end_gate` is deliberately ABSENT. The stop-slot turn-end gate is
15084
+ // always armed (2026-08-12) and has no settings surface: whether it
15085
+ // fires is decided by each detector's own trigger conditions, not by a
15086
+ // flag. A leftover `hooks.turn_end_gate.*` block from an older install
15087
+ // warns once on stderr and is ignored — see REMOVED_KEYS in
15088
+ // `src/scripts/_lib/agent_settings.ts`.
15093
15089
  }),
15094
15090
  decision_engine: external_exports.object({
15095
15091
  surface_traces: external_exports.boolean().default(false).describe(
@@ -9,7 +9,7 @@
9
9
  "homepage": "https://event4u-app.github.io/agent-config",
10
10
  "name": "@event4u/agent-config",
11
11
  "repository": "https://github.com/event4u-app/agent-config",
12
- "version": "9.36.0"
12
+ "version": "10.1.0"
13
13
  },
14
14
  "registries": [
15
15
  {
@@ -9,12 +9,12 @@
9
9
  "transport": {
10
10
  "type": "stdio"
11
11
  },
12
- "version": "9.36.0"
12
+ "version": "10.1.0"
13
13
  }
14
14
  ],
15
15
  "repository": {
16
16
  "source": "github",
17
17
  "url": "https://github.com/event4u-app/agent-config"
18
18
  },
19
- "version": "9.36.0"
19
+ "version": "10.1.0"
20
20
  }