@event4u/agent-config 9.25.0 → 9.26.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 (80) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +87 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/analyze/conformance.md +2 -2
  5. package/dist/agent-src/commands/review/changes.md +6 -5
  6. package/dist/agent-src/commands/roadmap/next.md +194 -0
  7. package/dist/agent-src/commands/roadmap.md +12 -4
  8. package/dist/agent-src/contexts/execution/subagent-routing.md +4 -1
  9. package/dist/agent-src/rules/delegation-policy.md +3 -2
  10. package/dist/agent-src/rules/design-fidelity.md +17 -1
  11. package/dist/agent-src/rules/design-review-after-ui-write.md +116 -0
  12. package/dist/agent-src/rules/evaluator-independence.md +9 -3
  13. package/dist/agent-src/rules/session-canary.md +2 -0
  14. package/dist/agent-src/rules/settings-ask-protocol.md +176 -0
  15. package/dist/agent-src/rules/token-budget-discipline.md +6 -3
  16. package/dist/agent-src/rules/ui-audit-gate.md +3 -0
  17. package/dist/agent-src/rules/user-interaction.md +3 -2
  18. package/dist/agent-src/skills/recursive-verification/SKILL.md +13 -0
  19. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  20. package/dist/cli-delegate/cmd_route_audit.js +4 -1
  21. package/dist/cli-delegate/cmd_route_explain.js +4 -1
  22. package/dist/discovery/deprecation-report.md +1 -1
  23. package/dist/discovery/discovery-manifest.json +94 -23
  24. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  25. package/dist/discovery/discovery-manifest.summary.md +4 -4
  26. package/dist/discovery/orphan-report.md +1 -1
  27. package/dist/discovery/packs.json +11 -8
  28. package/dist/discovery/trust-report.md +3 -3
  29. package/dist/discovery/workspaces.json +9 -6
  30. package/dist/hooks/dispatch.js +355 -63
  31. package/dist/mcp/registry-manifest.json +3 -3
  32. package/dist/mcp/server.json +2 -2
  33. package/dist/router.json +1 -1
  34. package/dist/server/nicknameResolver.js +116 -0
  35. package/dist/server/nicknameResolver.js.map +1 -0
  36. package/dist/server/routes/ping.js +29 -11
  37. package/dist/server/routes/ping.js.map +1 -1
  38. package/dist/shared/interactiveContext.js +73 -0
  39. package/dist/shared/interactiveContext.js.map +1 -0
  40. package/dist/shared/nicknamePrefill.js +59 -0
  41. package/dist/shared/nicknamePrefill.js.map +1 -0
  42. package/dist/shared/settingsAsks.js +128 -0
  43. package/dist/shared/settingsAsks.js.map +1 -0
  44. package/dist/shared/settingsConsent.js +93 -0
  45. package/dist/shared/settingsConsent.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/config/agents-paths.yml +15 -0
  48. package/src/config/gate-coverage.yml +29 -1
  49. package/src/config/gate-violation-baselines.json +13 -7
  50. package/src/scripts/_cli/cmd_route_audit.ts +4 -1
  51. package/src/scripts/_cli/cmd_route_explain.ts +9 -1
  52. package/src/scripts/ai-video/stitch.sh +27 -9
  53. package/src/scripts/before_complete_hook.ts +23 -4
  54. package/src/scripts/bench_ab_v2_run.ts +272 -16
  55. package/src/scripts/bench_ab_v2_stats.ts +156 -3
  56. package/src/scripts/check_completion_review.ts +80 -3
  57. package/src/scripts/check_corpus_staleness.ts +692 -0
  58. package/src/scripts/check_gate_coverage.ts +67 -0
  59. package/src/scripts/check_hook_bundle_freshness.ts +183 -0
  60. package/src/scripts/conformance_scan.ts +47 -17
  61. package/src/scripts/dispatch_r2_reviewer.ts +31 -2
  62. package/src/scripts/git_authorization_hook.ts +75 -10
  63. package/src/scripts/hook_manifest.yaml +19 -0
  64. package/src/scripts/hooks/block_no_verify.ts +3 -1
  65. package/src/scripts/hooks/block_unauthorized_git.ts +133 -16
  66. package/src/scripts/hooks/design_slop_hook.ts +13 -1
  67. package/src/scripts/hooks/dispatch_hook.ts +97 -2
  68. package/src/scripts/hooks/evidence_independence.ts +40 -6
  69. package/src/scripts/language_mirror_hook.ts +200 -11
  70. package/src/scripts/lint_abstraction_thresholds.ts +41 -8
  71. package/src/scripts/lint_agents_layout.ts +2 -0
  72. package/src/scripts/lint_hook_manifest.ts +29 -0
  73. package/src/scripts/memory_learn_hook.ts +109 -6
  74. package/src/scripts/pr_url_reminder_hook.ts +17 -2
  75. package/src/scripts/release.ts +38 -1
  76. package/src/scripts/schemas/review-findings.schema.json +99 -0
  77. package/src/shared/interactiveContext.ts +98 -0
  78. package/src/shared/nicknamePrefill.ts +96 -0
  79. package/src/shared/settingsAsks.ts +176 -0
  80. package/src/shared/settingsConsent.ts +136 -0
@@ -15128,9 +15128,10 @@ var _VACUOUS_PATTERNS = [
15128
15128
  function isVacuousOutput(output) {
15129
15129
  const text = output.trim();
15130
15130
  if (!text) {
15131
- return true;
15131
+ return false;
15132
15132
  }
15133
- return _VACUOUS_PATTERNS.some((re) => re.test(text));
15133
+ const lines = text.split("\n").filter((l) => l.trim());
15134
+ return lines.every((l) => _VACUOUS_PATTERNS.some((re) => re.test(l)));
15134
15135
  }
15135
15136
  var _CI_POLL_RE = /\bgh\s+(?:pr\s+checks|run\s+(?:watch|list|view))\b/i;
15136
15137
  function isCiPoll(command) {
@@ -15142,7 +15143,16 @@ function pendingCount(output) {
15142
15143
  return Number(m[1]);
15143
15144
  }
15144
15145
  const rows = output.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
15145
- return rows ? rows.length : null;
15146
+ if (rows) {
15147
+ return rows.length;
15148
+ }
15149
+ if (/\bno checks reported\b/i.test(output)) {
15150
+ return null;
15151
+ }
15152
+ if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output)) {
15153
+ return 0;
15154
+ }
15155
+ return null;
15146
15156
  }
15147
15157
  function _extract_output(payload) {
15148
15158
  for (const key of ["tool_response", "toolResponse", "output", "stdout", "result"]) {
@@ -15834,15 +15844,53 @@ if (_isCliEntry11()) {
15834
15844
  import fs22 from "node:fs";
15835
15845
  import path21 from "node:path";
15836
15846
  import { fileURLToPath as fileURLToPath16, pathToFileURL as pathToFileURL12 } from "node:url";
15847
+
15848
+ // src/shared/settingsClasses.ts
15849
+ function isConservativeDefault(value) {
15850
+ if (value === null || value === false || value === "" || value === 0) {
15851
+ return true;
15852
+ }
15853
+ if (Array.isArray(value)) {
15854
+ return value.length === 0;
15855
+ }
15856
+ if (typeof value === "object" && value !== null) {
15857
+ return Object.keys(value).length === 0;
15858
+ }
15859
+ return false;
15860
+ }
15861
+
15862
+ // src/shared/settingsConsent.ts
15863
+ var HUMAN_SOURCES = ["jit-answer", "gui", "manual"];
15864
+ function consentVerdict(query) {
15865
+ if (query.cls !== "B") {
15866
+ return "not-a-consent-key";
15867
+ }
15868
+ if (isConservativeDefault(query.value)) {
15869
+ return "withheld-default";
15870
+ }
15871
+ if (query.handEdited === true) {
15872
+ return "granted";
15873
+ }
15874
+ if (query.source === void 0) {
15875
+ return "withheld-unrecorded";
15876
+ }
15877
+ if (!HUMAN_SOURCES.includes(query.source)) {
15878
+ return "withheld-machine-inferred";
15879
+ }
15880
+ return "granted";
15881
+ }
15882
+
15883
+ // src/scripts/memory_learn_hook.ts
15837
15884
  var SETTINGS_FILE4 = ".agent-settings.yml";
15838
15885
  var BUDGET_MS = 2e3;
15839
- function enabled(root) {
15886
+ var LEARN_KEY_CLASS = "B";
15887
+ function readLearnValue(root) {
15840
15888
  const p = path21.join(root, SETTINGS_FILE4);
15841
15889
  let text;
15842
15890
  try {
15843
15891
  text = fs22.readFileSync(p, "utf8");
15844
15892
  } catch {
15845
- return false;
15893
+ return void 0;
15846
15894
  }
15847
15895
  let inMemory = false;
15848
15896
  for (const rawLine of text.split("\n")) {
@@ -15860,7 +15908,17 @@ function enabled(root) {
15860
15908
  if (m) return m[1] === "true";
15861
15909
  }
15862
15910
  }
15863
- return false;
15911
+ return void 0;
15912
+ }
15913
+ function learnConsent(root) {
15914
+ return consentVerdict({
15915
+ cls: LEARN_KEY_CLASS,
15916
+ value: readLearnValue(root) ?? false,
15917
+ handEdited: true
15918
+ });
15919
+ }
15920
+ function enabled(root) {
15921
+ return learnConsent(root) === "granted";
15864
15922
  }
15865
15923
  function runLearn(root, nowIso, budgetMs = BUDGET_MS) {
15866
15924
  const started = Date.now();
@@ -17199,6 +17257,10 @@ var _PR_URL = /https?:\/\/github\.com\/[^\s"'`<>]+\/pull\/\d+/i;
17199
17257
  function _isObject4(v) {
17200
17258
  return typeof v === "object" && v !== null && !Array.isArray(v);
17201
17259
  }
17260
+ function _unwrap(envelope) {
17261
+ const inner = envelope["payload"];
17262
+ return _isObject4(inner) ? inner : envelope;
17263
+ }
17202
17264
  function _extractCommand(payload) {
17203
17265
  const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
17204
17266
  const tool = typeof toolRaw === "string" ? toolRaw : null;
@@ -17251,11 +17313,12 @@ function main21() {
17251
17313
  if (!_isObject4(envelope)) {
17252
17314
  return EXIT_ALLOW3;
17253
17315
  }
17254
- const [tool, command] = _extractCommand(envelope);
17316
+ const inner = _unwrap(envelope);
17317
+ const [tool, command] = _extractCommand(inner);
17255
17318
  if (!_isPrCreate(tool, command)) {
17256
17319
  return EXIT_ALLOW3;
17257
17320
  }
17258
- const match = _PR_URL.exec(_toolOutput(envelope));
17321
+ const match = _PR_URL.exec(_toolOutput(inner));
17259
17322
  if (match === null) {
17260
17323
  return EXIT_ALLOW3;
17261
17324
  }
@@ -17472,16 +17535,39 @@ import fs33 from "node:fs";
17472
17535
  import path33 from "node:path";
17473
17536
  import { fileURLToPath as fileURLToPath25, pathToFileURL as pathToFileURL20 } from "node:url";
17474
17537
  var EXIT_ALLOW5 = 0;
17538
+ var EXIT_WARN3 = 2;
17475
17539
  var STATE_FILE5 = path33.join("agents", "state", "language-mirror.json");
17476
17540
  function _isObject6(v) {
17477
17541
  return typeof v === "object" && v !== null && !Array.isArray(v);
17478
17542
  }
17479
- var DE_MARKERS = /\b(der|die|das|den|dem|und|ist|sind|nicht|kein|keine|mach|mache|bitte|warum|wieso|weshalb|weiter|kannst|sollst|soll|musst|muss|hast|habe|haben|wir|ich|du|dann|noch|auch|schon|wenn|aber|alle|mit|ohne|damit|dass|wurde|werden|gibt|nochmal|erstell|erstelle|erstelle|fehler|regeln|arbeite|analysiere|fixe|nimm|lege|prüfe|pruefe|schau|zeig|mir|dir|sich|eine|einen|einem|einer|vom|zum|zur|beim|im|am)\b|[äöüÄÖÜß]/gi;
17543
+ var DE_MARKERS = /\b(der|die|das|den|dem|und|ist|sind|nicht|kein|keine|mach|mache|bitte|warum|wieso|weshalb|weiter|kannst|sollst|soll|musst|muss|hast|habe|haben|wir|ich|du|dann|noch|auch|schon|wenn|aber|alle|mit|ohne|damit|dass|wurde|werden|gibt|nochmal|erstell|erstelle|erstelle|fehler|regeln|arbeite|analysiere|fixe|nimm|lege|prüfe|pruefe|schau|zeig|mir|dir|sich|eine|einen|einem|einer|vom|zum|zur|beim|im)\b|[äöüÄÖÜß]/gi;
17480
17544
  var EN_MARKERS = /\b(the|and|is|are|was|were|not|please|why|because|should|would|could|there|these|those|with|from|about|which|that|this|will|have|has|been|they|them|their|your|our|what|when|where|make|need|want|does|doesn't|don't|it's|let's)\b/gi;
17481
17545
  var MIN_MARKERS = 2;
17546
+ function instructionText(prompt) {
17547
+ const OUTPUT_HEAD = /^\s*(To\s+\S+|remote:|error:|fatal:|hint:|warning:|npm ERR!|Error:|Traceback|Exception|[A-Za-z]*Error\b|at\s+\S+:\d+|\s{4,}\S|[-+]{3}\s|@@\s|\$\s|>\s|\||\d+\s*\|)/i;
17548
+ const kept = [];
17549
+ let inPaste = false;
17550
+ for (const line of prompt.replace(/```[\s\S]*?```/g, "\n\n").split("\n")) {
17551
+ if (!line.trim()) {
17552
+ inPaste = false;
17553
+ kept.push(line);
17554
+ continue;
17555
+ }
17556
+ if (OUTPUT_HEAD.test(line)) {
17557
+ inPaste = true;
17558
+ continue;
17559
+ }
17560
+ if (inPaste) {
17561
+ continue;
17562
+ }
17563
+ kept.push(line);
17564
+ }
17565
+ return kept.join("\n");
17566
+ }
17482
17567
  function classify(prompt) {
17483
- const de = (prompt.match(DE_MARKERS) ?? []).length;
17484
- const en = (prompt.match(EN_MARKERS) ?? []).length;
17568
+ const text = instructionText(prompt);
17569
+ const de = (text.match(DE_MARKERS) ?? []).length;
17570
+ const en = (text.match(EN_MARKERS) ?? []).length;
17485
17571
  if (de < MIN_MARKERS && en < MIN_MARKERS) {
17486
17572
  return { language: "und", de_markers: de, en_markers: en };
17487
17573
  }
@@ -17491,8 +17577,44 @@ var LANGUAGE_NAME = {
17491
17577
  de: "German (Deutsch)",
17492
17578
  en: "English"
17493
17579
  };
17494
- function pinText(language) {
17580
+ var LOCALE_ENV_KEYS = ["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"];
17581
+ function systemLocaleVerdict(env = process.env) {
17582
+ for (const key of LOCALE_ENV_KEYS) {
17583
+ const raw = (env[key] ?? "").trim();
17584
+ if (!raw) {
17585
+ continue;
17586
+ }
17587
+ const first = raw.split(":")[0] ?? "";
17588
+ const primary = (first.split(/[._@-]/)[0] ?? "").toLowerCase();
17589
+ if (primary === "c" || primary === "posix" || !primary) {
17590
+ continue;
17591
+ }
17592
+ if (primary === "de") {
17593
+ return "de";
17594
+ }
17595
+ if (primary === "en") {
17596
+ return "en";
17597
+ }
17598
+ return "und";
17599
+ }
17600
+ return "und";
17601
+ }
17602
+ function noticeText(language, source) {
17603
+ const name = LANGUAGE_NAME[language];
17604
+ if (source === "system-locale") {
17605
+ return `Reply language for this turn: ${name} \u2014 the prompt carried no language markers, so this fell back to the system locale. Write in another language and the next turn follows the prompt instead.`;
17606
+ }
17607
+ return `Reply language for this turn: ${name}.`;
17608
+ }
17609
+ function pinText(language, source = "prompt") {
17495
17610
  const name = LANGUAGE_NAME[language];
17611
+ if (source === "system-locale") {
17612
+ return `<language-pin>
17613
+ No language markers were found in this turn's prompt and no language was pinned yet, so the mirror target falls back to the environment's locale: ${name}. Use it for EVERY user-visible token this turn.
17614
+
17615
+ This is the WEAKER of the two provenances \u2014 it reflects the machine, not the user's own words. The first prompt that carries real markers replaces it. Do not treat it as a standing preference, and do not argue with the user about it if they write in something else.
17616
+ </language-pin>`;
17617
+ }
17496
17618
  return `<language-pin>
17497
17619
  The user submitted this turn's prompt in ${name}. That is the mirror target for EVERY user-visible token of your replies this turn \u2014 including the short inter-tool lines between tool calls, headings, table cells, bullets, status lines, and the recommendation label under any numbered-options block.
17498
17620
 
@@ -17508,12 +17630,27 @@ function _loadState(target) {
17508
17630
  return {};
17509
17631
  }
17510
17632
  }
17511
- function nextState(previous, classification, prompt_chars, session_id, now) {
17633
+ function nextState(previous, classification, prompt_chars, session_id, now, locale = "und") {
17512
17634
  if (classification.language === "und") {
17513
- return null;
17635
+ if (previous.language === "de" || previous.language === "en") {
17636
+ return null;
17637
+ }
17638
+ if (locale === "und") {
17639
+ return null;
17640
+ }
17641
+ return {
17642
+ language: locale,
17643
+ source: "system-locale",
17644
+ detected_at: now,
17645
+ prompt_chars,
17646
+ de_markers: classification.de_markers,
17647
+ en_markers: classification.en_markers,
17648
+ session_id
17649
+ };
17514
17650
  }
17515
17651
  return {
17516
17652
  language: classification.language,
17653
+ source: "prompt",
17517
17654
  detected_at: now,
17518
17655
  prompt_chars,
17519
17656
  de_markers: classification.de_markers,
@@ -17551,8 +17688,19 @@ function run7(stdin_text, options) {
17551
17688
  const target = path33.join(options.consumer_root, STATE_FILE5);
17552
17689
  const previous = _loadState(target);
17553
17690
  const classification = classify(prompt);
17554
- const next = nextState(previous, classification, prompt.length, session_id, (/* @__PURE__ */ new Date()).toISOString());
17691
+ const next = nextState(
17692
+ previous,
17693
+ classification,
17694
+ prompt.length,
17695
+ session_id,
17696
+ (/* @__PURE__ */ new Date()).toISOString(),
17697
+ // Injectable so the suite can pin a locale instead of inheriting the
17698
+ // machine's — a test whose verdict depends on the developer's `LANG` is a
17699
+ // test that passes here and fails in CI for reasons unrelated to the diff.
17700
+ systemLocaleVerdict(options.env ?? process.env)
17701
+ );
17555
17702
  const effective = next?.language ?? previous.language;
17703
+ const effectiveSource = next?.source ?? (previous.source === "system-locale" ? "system-locale" : "prompt");
17556
17704
  if (next) {
17557
17705
  try {
17558
17706
  atomic_write_json(target, next);
@@ -17561,9 +17709,10 @@ function run7(stdin_text, options) {
17561
17709
  }
17562
17710
  if (effective === "de" || effective === "en") {
17563
17711
  process.stdout.write(
17564
- `${JSON.stringify({ decision: "warn", reason: `Reply language for this turn: ${LANGUAGE_NAME[effective]}.`, additional_context: pinText(effective) })}
17712
+ `${JSON.stringify({ decision: "warn", reason: noticeText(effective, effectiveSource), additional_context: pinText(effective, effectiveSource) })}
17565
17713
  `
17566
17714
  );
17715
+ return EXIT_WARN3;
17567
17716
  }
17568
17717
  return EXIT_ALLOW5;
17569
17718
  }
@@ -17614,15 +17763,42 @@ function _isObject7(v) {
17614
17763
  return typeof v === "object" && v !== null && !Array.isArray(v);
17615
17764
  }
17616
17765
  var PHRASES = [
17617
- { op: "commit", re: /\b(commit(e|et|ten|ted|ting)?|committe|einchecken)\b/i },
17618
- { op: "push", re: /\b(push(e|en|ed|ing)?|hochladen|hochschieben|raufschieben)\b/i },
17619
- { op: "branch", re: /\b(branch(e|es)?|zweig|feature-branch|erstelle einen branch)\b/i },
17620
- { op: "pr-create", re: /\b(pull[- ]request|pr\b|mach(e|en)? (einen|nen) pr|erstelle .{0,20}pr)\b/i },
17621
- { op: "pr-merge", re: /\b(merge[nrd]?|mergen|zusammenführen|zusammenfuehren|reinmergen|gemerged)\b/i },
17622
- { op: "tag", re: /\b(tag(ge|gen|ged|ging)?|version(iere|ieren)?)\b/i },
17623
- { op: "release", re: /\b(release[nd]?|releasen|veröffentlich(e|en|ung)|veroeffentlich(e|en))\b/i },
17624
- { op: "publish", re: /\b(publish(e|en|ed|ing)?|npm publish|publiziere[n]?)\b/i }
17766
+ { op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
17767
+ { op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
17768
+ { op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
17769
+ // A creation verb is required — "schau dir den PR an" is not "open a PR".
17770
+ {
17771
+ op: "pr-create",
17772
+ re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|(er)?(ö|oe)ffne|open|create|raise|aufmachen)\b[^.\n]{0,30}\b(pr|pull[- ]request)\b|\b(pr|pull[- ]request)\b[^.\n]{0,20}\b(erstellen|aufmachen|anlegen|(er)?(ö|oe)ffnen)\b/i
17773
+ },
17774
+ // `merge` as an ACTION, never as the noun in "merge conflict" / "merge commit".
17775
+ { op: "pr-merge", re: /\b(merge|merg(e|en|st|t)|zusammenf(ü|ue)hren|reinmergen)\b(?!\s*[- ]?(conflict|konflikt|commit|base|queue|state|status))/i },
17776
+ // A tag is an ACTION here — bare "Tag" is the German word for day, and
17777
+ // "Version" is an ordinary noun. Both authorized a BLOCK op before this.
17778
+ { op: "tag", re: /\b(tagge(n|st)?|tag\s+(setzen|anlegen|erstellen)|git\s+tag|--tags|--follow-tags)\b/i },
17779
+ // "die release notes sind falsch" is a noun phrase, not an authorization.
17780
+ {
17781
+ op: "release",
17782
+ re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
17783
+ },
17784
+ { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
17625
17785
  ];
17786
+ 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;
17787
+ function isInterrogative(prose) {
17788
+ const t = prose.trim();
17789
+ if (!t) {
17790
+ return false;
17791
+ }
17792
+ const hasImperative = /\b(mach|mache|bitte|leg|lege|erstell|erstelle|f(ü|ue)hr|f(ü|ue)hre|setz|setze|starte|los|jetzt|go ahead|do it|ja[,.]?\s|ok[,.]?\s)\b/i.test(
17793
+ t
17794
+ );
17795
+ if (hasImperative) {
17796
+ return false;
17797
+ }
17798
+ return /\?\s*$/.test(t) || /^(was|wie|warum|wieso|weshalb|wann|wer|welche[rs]?|wo|ist|sind|kann|kannst|k(ö|oe)nnen|soll|sollen|darf|d(ü|ue)rfen|why|what|how|when|which|who|is|are|can|could|should|does|do)\b/i.test(
17799
+ t
17800
+ );
17801
+ }
17626
17802
  var PASTED_COMMANDS = [
17627
17803
  { op: "publish", re: /\bnpm\s+publish\b/i },
17628
17804
  { op: "tag", re: /\bgit\s+push\s+[^\n]*--tags\b|\bgit\s+tag\s+-a\b/i },
@@ -17648,14 +17824,18 @@ function classifyAuthorization(prompt) {
17648
17824
  const { prose, fences } = splitFences(prompt);
17649
17825
  const authorized = /* @__PURE__ */ new Set();
17650
17826
  const evidence = {};
17651
- for (const { op, re } of PHRASES) {
17652
- const m = re.exec(prose);
17827
+ const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
17828
+ for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
17829
+ const m = re.exec(instruction);
17653
17830
  if (m) {
17654
17831
  authorized.add(op);
17655
17832
  evidence[op] = `prose: "${m[0]}"`;
17656
17833
  }
17657
17834
  }
17658
17835
  for (const fence of fences) {
17836
+ if (fence.split("\n").some((l) => OUTPUT_LINE.test(l))) {
17837
+ continue;
17838
+ }
17659
17839
  for (const line of _commandLines(fence)) {
17660
17840
  for (const { op, re } of PASTED_COMMANDS) {
17661
17841
  if (re.test(line)) {
@@ -17747,7 +17927,7 @@ import fs35 from "node:fs";
17747
17927
  import path35 from "node:path";
17748
17928
  import { fileURLToPath as fileURLToPath27, pathToFileURL as pathToFileURL22 } from "node:url";
17749
17929
  var EXIT_ALLOW7 = 0;
17750
- var EXIT_BLOCK = 2;
17930
+ var EXIT_BLOCK = 1;
17751
17931
  function _isObject8(v) {
17752
17932
  return typeof v === "object" && v !== null && !Array.isArray(v);
17753
17933
  }
@@ -17763,15 +17943,22 @@ var WARN_OPS = /* @__PURE__ */ new Set([
17763
17943
  "pr-create",
17764
17944
  "branch"
17765
17945
  ]);
17946
+ var G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
17766
17947
  var COMMAND_OPS = [
17767
- { op: "publish", re: /\b(npm|pnpm|yarn)\s+publish\b/i },
17768
- { op: "release", re: /\bgh\s+release\s+create\b/i },
17769
- { op: "tag", re: /\bgit\s+push\b[^\n;|&]*--tags\b|\bgit\s+push\b[^\n;|&]*\brefs\/tags\//i },
17770
- { op: "pr-merge", re: /\bgh\s+pr\s+merge\b/i },
17771
- { op: "pr-create", re: /\bgh\s+pr\s+create\b/i },
17772
- { op: "push", re: /\bgit\s+push\b/i },
17773
- { op: "commit", re: /\bgit\s+commit\b/i },
17774
- { op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c)\b/i }
17948
+ { op: "publish", re: new RegExp(`\\b(npm|pnpm|yarn)\\s+${G}publish\\b`, "i") },
17949
+ { op: "release", re: /\bgh\s+release\s+create\b|\bgh\s+api\b[^\n]*\/releases\b/i },
17950
+ {
17951
+ op: "tag",
17952
+ re: new RegExp(
17953
+ `\\bgit\\s+${G}push\\b[^\\n;|&]*(--tags|--follow-tags|refs\\/tags\\/)`,
17954
+ "i"
17955
+ )
17956
+ },
17957
+ { op: "pr-merge", re: /\bgh\s+pr\s+merge\b|\bgh\s+api\b[^\n]*\/pulls\/\d+\/merge\b/i },
17958
+ { op: "pr-create", re: /\bgh\s+pr\s+create\b|\bgh\s+api\b[^\n]*-X\s+POST[^\n]*\/pulls\b/i },
17959
+ { op: "push", re: new RegExp(`\\bgit\\s+${G}push\\b`, "i") },
17960
+ { op: "commit", re: new RegExp(`\\bgit\\s+${G}commit\\b`, "i") },
17961
+ { op: "branch", re: new RegExp(`\\bgit\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
17775
17962
  ];
17776
17963
  var COMMAND_TOOLS3 = /* @__PURE__ */ new Set([
17777
17964
  "launch-process",
@@ -17800,19 +17987,62 @@ function extractCommand(envelope) {
17800
17987
  }
17801
17988
  return null;
17802
17989
  }
17990
+ function invokedSegments(command, depth = 0) {
17991
+ if (depth > 3) {
17992
+ return [];
17993
+ }
17994
+ const out = [];
17995
+ const withoutHeredocs = command.replace(
17996
+ /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[\s\S]*?^\s*\2\s*$/gm,
17997
+ "<<HEREDOC"
17998
+ );
17999
+ for (const raw of withoutHeredocs.split(/\n|;|&&|\|\||\||&/)) {
18000
+ let seg = raw.trim();
18001
+ if (!seg) {
18002
+ continue;
18003
+ }
18004
+ seg = seg.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+)+/, "");
18005
+ const wrapped = /^(?:\S*\/)?(?:ba|z|k)?sh\s+-[a-z]*c\s+(?:"([^"]*)"|'([^']*)'|(\S+))/.exec(seg);
18006
+ if (wrapped) {
18007
+ out.push(...invokedSegments(wrapped[1] ?? wrapped[2] ?? wrapped[3] ?? "", depth + 1));
18008
+ continue;
18009
+ }
18010
+ out.push(seg);
18011
+ }
18012
+ return out;
18013
+ }
17803
18014
  function commandOp(command) {
17804
- for (const { op, re } of COMMAND_OPS) {
17805
- if (re.test(command)) {
17806
- return op;
18015
+ let found = null;
18016
+ for (const seg of invokedSegments(command)) {
18017
+ if (!/^(?:\S*\/)?(git|gh|npm|pnpm|yarn)\b/.test(seg)) {
18018
+ continue;
18019
+ }
18020
+ for (const { op, re } of COMMAND_OPS) {
18021
+ if (re.test(seg)) {
18022
+ if (BLOCK_OPS.has(op)) {
18023
+ return op;
18024
+ }
18025
+ found ??= op;
18026
+ break;
18027
+ }
17807
18028
  }
17808
18029
  }
17809
- return null;
18030
+ return found;
17810
18031
  }
17811
- function _loadLedger(consumer_root) {
18032
+ var LEDGER_MAX_AGE_MS = 30 * 60 * 1e3;
18033
+ function _loadLedger(consumer_root, session_id, now) {
17812
18034
  try {
17813
18035
  const raw = fs35.readFileSync(path35.join(consumer_root, STATE_FILE6), "utf8");
17814
18036
  const decoded = JSON.parse(raw);
17815
18037
  if (_isObject8(decoded) && Array.isArray(decoded["authorized"])) {
18038
+ const ledgerSession = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
18039
+ if (session_id && ledgerSession && ledgerSession !== session_id) {
18040
+ return { authorized: /* @__PURE__ */ new Set(), present: false };
18041
+ }
18042
+ const at = Date.parse(String(decoded["detected_at"] ?? ""));
18043
+ if (Number.isFinite(at) && now - at > LEDGER_MAX_AGE_MS) {
18044
+ return { authorized: /* @__PURE__ */ new Set(), present: false };
18045
+ }
17816
18046
  return {
17817
18047
  authorized: new Set(decoded["authorized"].filter(Boolean)),
17818
18048
  present: true
@@ -17859,7 +18089,11 @@ function run9(stdin_text, options) {
17859
18089
  return EXIT_ALLOW7;
17860
18090
  }
17861
18091
  }
17862
- const decision = decide(extractCommand(envelope), _loadLedger(options.consumer_root));
18092
+ const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
18093
+ const decision = decide(
18094
+ extractCommand(envelope),
18095
+ _loadLedger(options.consumer_root, session_id, Date.now())
18096
+ );
17863
18097
  if (decision.stdout) {
17864
18098
  process.stdout.write(decision.stdout);
17865
18099
  }
@@ -17911,7 +18145,7 @@ import fs36 from "node:fs";
17911
18145
  import path36 from "node:path";
17912
18146
  import { fileURLToPath as fileURLToPath28, pathToFileURL as pathToFileURL23 } from "node:url";
17913
18147
  var EXIT_ALLOW8 = 0;
17914
- var EXIT_BLOCK2 = 2;
18148
+ var EXIT_BLOCK2 = 1;
17915
18149
  var STATE_FILE7 = path36.join("agents", "state", "evidence-dispatch.json");
17916
18150
  function _isObject9(v) {
17917
18151
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -17926,8 +18160,18 @@ var DISPATCH_TOOLS = /* @__PURE__ */ new Set([
17926
18160
  "run_subagent",
17927
18161
  "Subagent"
17928
18162
  ]);
17929
- var EVALUATION_RE = /\b(review|reviewer|audit|auditor|judge|verdict|blind[- ]pass|blind review|adversarial|critique|assess(ment)?|verify (my|the) (work|change|diff|implementation)|find (any )?(bugs|issues|defects|problems) in (my|the))\b/i;
18163
+ var EVALUATION_RE = /\b(review|reviewer|audit|auditor|judge|verdict|blind[- ]pass|blind review|adversarial|critique|assess(ment)?|verify (my|the) (work|change|diff|implementation)|find (any )?(bugs|issues|defects|problems) in (my|the)|pr(ü|ue)f(e|ung)?|begutachte|gutachten|bewerte|(gegen)?check(e)?|durchsicht|review(e|iere))\b/i;
17930
18164
  var PRELOADED_VERDICT_RE = [
18165
+ // German — the operator writes German, and an English-only list meant no
18166
+ // German dispatch was ever screened at all.
18167
+ /\bich erwarte (hier )?(keine|nichts)\b/i,
18168
+ /\b(du )?(wirst|solltest) (hier )?nichts finden\b/i,
18169
+ /\bbest(ä|ae)tige (nur|kurz)\b/i,
18170
+ /\b(ist|sollte) (wahrscheinlich|vermutlich|sicher) (sauber|korrekt|in ordnung|fine)\b/i,
18171
+ /\bkeine findings? (erwartet|zu erwarten)\b/i,
18172
+ // English paraphrases of the audited construct.
18173
+ /\bi am confident there is nothing\b/i,
18174
+ /\ba clean pass is the likely\b/i,
17931
18175
  /\bno[- ]findings? (is|are) (expected|welcome|fine|acceptable|the likely)/i,
17932
18176
  /\b(you )?(should|will|probably) find (nothing|no (issues|problems|bugs|findings))/i,
17933
18177
  /\bconfirm (that )?there (are|is) no\b/i,
@@ -17968,6 +18212,18 @@ function extractDispatch(envelope) {
17968
18212
  }
17969
18213
  return [tool, ""];
17970
18214
  }
18215
+ function _ledgerStamp(consumer_root) {
18216
+ try {
18217
+ const raw = fs36.readFileSync(
18218
+ path36.join(consumer_root, "agents", "state", "git-authorization.json"),
18219
+ "utf8"
18220
+ );
18221
+ const d = JSON.parse(raw);
18222
+ return typeof d["detected_at"] === "string" ? d["detected_at"] : "";
18223
+ } catch {
18224
+ return "";
18225
+ }
18226
+ }
17971
18227
  function _load(target) {
17972
18228
  try {
17973
18229
  const decoded = JSON.parse(fs36.readFileSync(target, "utf8"));
@@ -18028,7 +18284,7 @@ function run10(stdin_text, options) {
18028
18284
  const target = path36.join(options.consumer_root, STATE_FILE7);
18029
18285
  const state = _load(target);
18030
18286
  const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
18031
- const turnMarker = typeof envelope["turn_id"] === "string" ? envelope["turn_id"] : session_id;
18287
+ const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
18032
18288
  if (state.session_id !== turnMarker) {
18033
18289
  state.session_id = turnMarker;
18034
18290
  state.turn_started_at = (/* @__PURE__ */ new Date()).toISOString();
@@ -18335,7 +18591,7 @@ function _check_command(cmd) {
18335
18591
  if (/\bgit\b/.test(cmd)) {
18336
18592
  return [
18337
18593
  true,
18338
- "command parse failed (shlex) on a git-containing command \u2014 fail-closed (git-history-discipline)"
18594
+ "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."
18339
18595
  ];
18340
18596
  }
18341
18597
  return [false, ""];
@@ -18728,7 +18984,7 @@ import { fileURLToPath as fileURLToPath31, pathToFileURL as pathToFileURL26 } fr
18728
18984
  var _HERE5 = fileURLToPath31(import.meta.url);
18729
18985
  var EXIT_ALLOW9 = 0;
18730
18986
  var EXIT_BLOCK3 = 1;
18731
- var EXIT_WARN3 = 2;
18987
+ var EXIT_WARN4 = 2;
18732
18988
  var SESSION_ENTRY_CAP = 20;
18733
18989
  var SESSION_WARN_AT = 5;
18734
18990
  function _isObject11(v) {
@@ -18895,7 +19151,7 @@ function main29() {
18895
19151
  if (d.action === "warn") {
18896
19152
  process.stdout.write(`${JSON.stringify({ decision: "warn", reason: d.reason })}
18897
19153
  `);
18898
- return EXIT_WARN3;
19154
+ return EXIT_WARN4;
18899
19155
  }
18900
19156
  }
18901
19157
  return EXIT_ALLOW9;
@@ -19037,7 +19293,7 @@ function detectRtkCached(options = {}) {
19037
19293
  // src/scripts/hooks/rtk_wrap_hook.ts
19038
19294
  var SETTINGS_FILE6 = ".agent-settings.yml";
19039
19295
  var EXIT_ALLOW10 = 0;
19040
- var EXIT_WARN4 = 2;
19296
+ var EXIT_WARN5 = 2;
19041
19297
  var _RTK_PROGRAMS = /* @__PURE__ */ new Set([
19042
19298
  "git",
19043
19299
  "phpstan",
@@ -19180,7 +19436,7 @@ function main30() {
19180
19436
  const reason = `rtk is installed and wraps verbose CLI output (upstream reports 60\u201390% fewer output tokens). Re-run wrapped: \`rtk ${command.trim()}\`. (Skip for completeness-critical output like \`git diff\`.)`;
19181
19437
  process.stdout.write(`${_jsonReason(reason)}
19182
19438
  `);
19183
- return EXIT_WARN4;
19439
+ return EXIT_WARN5;
19184
19440
  }
19185
19441
  function _isCliEntry26() {
19186
19442
  if (true) {
@@ -19797,7 +20053,7 @@ if (invokedDirectly) main31();
19797
20053
  // src/scripts/hooks/design_slop_hook.ts
19798
20054
  var SETTINGS_FILE7 = ".agent-settings.yml";
19799
20055
  var EXIT_ALLOW11 = 0;
19800
- var EXIT_WARN5 = 2;
20056
+ var EXIT_WARN6 = 2;
19801
20057
  var DEGRADE_AFTER = 3;
19802
20058
  var UI_EXT = /\.(html|htm|css|scss|sass|less|vue|svelte|astro|jsx|tsx)$/i;
19803
20059
  function isObject(v) {
@@ -19833,7 +20089,12 @@ function enabled2(root) {
19833
20089
  }
19834
20090
  return false;
19835
20091
  }
19836
- function extract(envelope) {
20092
+ function _unwrap2(envelope) {
20093
+ const inner = envelope["payload"];
20094
+ return isObject(inner) ? inner : envelope;
20095
+ }
20096
+ function extract(outer) {
20097
+ const envelope = _unwrap2(outer);
19837
20098
  const ti = envelope["tool_input"] ?? envelope["toolInput"] ?? envelope["input"];
19838
20099
  if (!isObject(ti)) return null;
19839
20100
  const fileVal = ti["file_path"] ?? ti["path"] ?? ti["filePath"];
@@ -19909,7 +20170,7 @@ function main32() {
19909
20170
  const lines = surfaced.map((f) => `${f.catalogId} (${f.rule}) ${f.file}:${f.line} \u2014 ${f.message}`).join("; ");
19910
20171
  const reason = "\u26A0\uFE0F Anti-slop: high-severity aesthetic tell(s) in the proposed UI \u2014 " + lines + ". Rebuttable: declare intent in DESIGN.md, add design-slop-disable-next-line, or proceed if deliberate (this is a flag, not a block).";
19911
20172
  process.stdout.write(JSON.stringify({ decision: "warn", reason }) + "\n");
19912
- return EXIT_WARN5;
20173
+ return EXIT_WARN6;
19913
20174
  }
19914
20175
  function _isCliEntry27() {
19915
20176
  if (true) {
@@ -20071,7 +20332,7 @@ function pickSource(verdicts) {
20071
20332
  // src/scripts/hooks/code_graph_nudge_hook.ts
20072
20333
  var SETTINGS_FILE8 = ".agent-settings.yml";
20073
20334
  var EXIT_ALLOW12 = 0;
20074
- var EXIT_WARN6 = 2;
20335
+ var EXIT_WARN7 = 2;
20075
20336
  var CODE_EXT = /\.(php|ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
20076
20337
  function isObject2(v) {
20077
20338
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -20186,7 +20447,7 @@ function main33() {
20186
20447
  latch(root, session);
20187
20448
  process.stdout.write(`${JSON.stringify({ decision: "warn", reason })}
20188
20449
  `);
20189
- return EXIT_WARN6;
20450
+ return EXIT_WARN7;
20190
20451
  }
20191
20452
  function isCliEntry10() {
20192
20453
  if (true) {
@@ -20292,7 +20553,7 @@ var REPO_ROOT = path45.resolve(
20292
20553
  var MANIFEST_PATH = path45.join(REPO_ROOT, "src", "scripts", "hook_manifest.yaml");
20293
20554
  var EXIT_ALLOW13 = 0;
20294
20555
  var EXIT_BLOCK4 = 1;
20295
- var EXIT_WARN7 = 2;
20556
+ var EXIT_WARN8 = 2;
20296
20557
  var EVENT_VOCABULARY = /* @__PURE__ */ new Set([
20297
20558
  "session_start",
20298
20559
  "session_end",
@@ -20306,7 +20567,7 @@ var EVENT_VOCABULARY = /* @__PURE__ */ new Set([
20306
20567
  var _SEVERITY_BY_EXIT = {
20307
20568
  [EXIT_ALLOW13]: "allow",
20308
20569
  [EXIT_BLOCK4]: "block",
20309
- [EXIT_WARN7]: "warn"
20570
+ [EXIT_WARN8]: "warn"
20310
20571
  };
20311
20572
  function _isObject13(v) {
20312
20573
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -20345,6 +20606,31 @@ function _load_yaml3(p) {
20345
20606
  const data = (0, import_yaml6.parse)(text, { version: "1.1" });
20346
20607
  return _isObject13(data) ? data : {};
20347
20608
  }
20609
+ function _payload_tool_name(envelope) {
20610
+ const payload = envelope["payload"];
20611
+ if (!_isObject13(payload)) {
20612
+ return "";
20613
+ }
20614
+ const raw = payload["tool_name"];
20615
+ return typeof raw === "string" ? raw : "";
20616
+ }
20617
+ function _concern_matches_tool(concern, tool_name) {
20618
+ const raw = concern["tools"];
20619
+ if (raw === void 0 || raw === null) {
20620
+ return true;
20621
+ }
20622
+ if (!Array.isArray(raw) || raw.length === 0) {
20623
+ return true;
20624
+ }
20625
+ const names = raw.filter((t) => typeof t === "string");
20626
+ if (names.length === 0 || names.includes("*")) {
20627
+ return true;
20628
+ }
20629
+ if (tool_name === "") {
20630
+ return true;
20631
+ }
20632
+ return names.includes(tool_name);
20633
+ }
20348
20634
  function _resolve_concerns(manifest, platform, event) {
20349
20635
  const platformsRaw = manifest["platforms"];
20350
20636
  const platforms = _isObject13(platformsRaw) ? platformsRaw : {};
@@ -20679,8 +20965,8 @@ function _reduce(rcs) {
20679
20965
  if (rcs.some((rc) => rc === EXIT_BLOCK4)) {
20680
20966
  return EXIT_BLOCK4;
20681
20967
  }
20682
- if (rcs.some((rc) => rc === EXIT_WARN7)) {
20683
- return EXIT_WARN7;
20968
+ if (rcs.some((rc) => rc === EXIT_WARN8)) {
20969
+ return EXIT_WARN8;
20684
20970
  }
20685
20971
  return EXIT_ALLOW13;
20686
20972
  }
@@ -20750,7 +21036,7 @@ function _write_feedback(envelope, session_id, entries, final_rc, started_at) {
20750
21036
  function _record_rule_trips(envelope, entries) {
20751
21037
  if (is_replay_mode()) return;
20752
21038
  const tripped = entries.filter(
20753
- (e) => e["exit_code"] === EXIT_BLOCK4 || e["exit_code"] === EXIT_WARN7
21039
+ (e) => e["exit_code"] === EXIT_BLOCK4 || e["exit_code"] === EXIT_WARN8
20754
21040
  );
20755
21041
  if (tripped.length === 0) return;
20756
21042
  try {
@@ -20771,7 +21057,7 @@ function _record_rule_trips(envelope, entries) {
20771
21057
  const prev = concerns[name] ?? { block: 0, warn: 0 };
20772
21058
  concerns[name] = {
20773
21059
  block: (Number(prev["block"]) || 0) + (e["exit_code"] === EXIT_BLOCK4 ? 1 : 0),
20774
- warn: (Number(prev["warn"]) || 0) + (e["exit_code"] === EXIT_WARN7 ? 1 : 0),
21060
+ warn: (Number(prev["warn"]) || 0) + (e["exit_code"] === EXIT_WARN8 ? 1 : 0),
20775
21061
  last_trip: today
20776
21062
  };
20777
21063
  }
@@ -20917,7 +21203,11 @@ function main34(argv) {
20917
21203
  const feedback_entries = [];
20918
21204
  const context_blocks = [];
20919
21205
  const concern_messages = [];
21206
+ const tool_name = _payload_tool_name(envelope);
20920
21207
  for (const concern of concerns) {
21208
+ if (!_concern_matches_tool(concern, tool_name)) {
21209
+ continue;
21210
+ }
20921
21211
  const concern_started = _now_iso3();
20922
21212
  const { rc: rawRcResult, stderr: stderr_text, stdout: stdout_text, duration_ms } = _run_concern(concern, envelope);
20923
21213
  let rc = rawRcResult;
@@ -20937,13 +21227,15 @@ function main34(argv) {
20937
21227
  `dispatch_hook: concern '${String(concern["name"])}' is declared severity: advisory but returned BLOCK \u2014 downgraded to warn.
20938
21228
  `
20939
21229
  );
20940
- rc = EXIT_WARN7;
21230
+ rc = EXIT_WARN8;
20941
21231
  }
20942
21232
  rcs.push(rc);
20943
21233
  const reply = _parse_concern_stdout(stdout_text);
20944
21234
  const stated = typeof reply["reason"] === "string" && reply["reason"].trim() ? reply["reason"].trim() : stderr_text.trim();
20945
- if (stated) {
20946
- concern_messages.push({ rc, text: stated });
21235
+ const extra = typeof reply["additional_context"] === "string" && reply["additional_context"].trim() ? reply["additional_context"].trim() : "";
21236
+ const message = [stated, extra].filter(Boolean).join("\n\n");
21237
+ if (message) {
21238
+ concern_messages.push({ rc, text: message });
20947
21239
  }
20948
21240
  if (args.event === "session_start" && rc === EXIT_ALLOW13 && typeof reply["context"] === "string" && reply["context"].trim()) {
20949
21241
  context_blocks.push(reply["context"].trim());