@event4u/agent-config 14.14.0 → 14.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +63 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/pr/create.md +16 -1
  7. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  8. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  9. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  10. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  11. package/dist/cli/registry.js +1 -0
  12. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  13. package/dist/cli-delegate/cmd_conformance.js +513 -17
  14. package/dist/cli-delegate/cmd_doctor.js +1 -1
  15. package/dist/discovery/deprecation-report.md +1 -1
  16. package/dist/discovery/discovery-manifest.json +5 -5
  17. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  18. package/dist/discovery/discovery-manifest.summary.md +1 -1
  19. package/dist/discovery/dormancy-report.md +1 -1
  20. package/dist/discovery/orphan-report.md +1 -1
  21. package/dist/discovery/packs.json +2 -2
  22. package/dist/discovery/trust-report.md +1 -1
  23. package/dist/discovery/workspaces.json +2 -2
  24. package/dist/hooks/dispatch.js +1043 -390
  25. package/dist/mcp/registry-manifest.json +2 -2
  26. package/dist/mcp/server.json +2 -2
  27. package/dist/mcp/server.mjs +703 -115
  28. package/package.json +1 -1
  29. package/src/config/assurance-capability-registry.json +1 -1
  30. package/src/config/evaluator-budgets.json +4 -4
  31. package/src/config/gate-coverage.yml +62 -2
  32. package/src/config/gate-reachability-exemptions.json +1 -0
  33. package/src/config/gate-violation-baselines.json +4 -3
  34. package/src/config/rule-enforcement-baseline.json +0 -1
  35. package/src/config/web-launch-readiness.json +4 -2
  36. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  37. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  38. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  39. package/src/scripts/_dispatch.bash +11 -0
  40. package/src/scripts/_lib/branch_convergence.ts +186 -0
  41. package/src/scripts/_lib/context_observation.ts +132 -0
  42. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  43. package/src/scripts/_lib/experiment_binding.ts +95 -0
  44. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  45. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  46. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  47. package/src/scripts/_lib/release_highlights.ts +75 -10
  48. package/src/scripts/_lib/repeated_failure.ts +19 -2
  49. package/src/scripts/_lib/runtime_journal.ts +11 -4
  50. package/src/scripts/check_agent_artifact_location.ts +337 -0
  51. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  52. package/src/scripts/check_enforcement_coverage.ts +40 -1
  53. package/src/scripts/check_release_highlights.ts +17 -1
  54. package/src/scripts/check_web_launch_readiness.ts +310 -20
  55. package/src/scripts/evolution_lab.ts +53 -1
  56. package/src/scripts/generate_capability_matrix.ts +16 -9
  57. package/src/scripts/git_authorization_hook.ts +476 -6
  58. package/src/scripts/grade_target_readiness.ts +221 -2
  59. package/src/scripts/hook_effect_doctor.ts +47 -4
  60. package/src/scripts/hooks/block_unauthorized_git.ts +238 -9
  61. package/src/scripts/hooks/merge_impact.ts +207 -0
  62. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  63. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  64. package/src/scripts/install-hooks.sh +18 -0
  65. package/src/scripts/lint_code_comments.ts +106 -1
  66. package/src/scripts/release.ts +5 -17
  67. package/src/scripts/release_drill.ts +76 -1
  68. package/src/scripts/release_env.ts +31 -0
  69. package/src/scripts/release_publication.ts +125 -1
  70. package/src/scripts/roadmap_context.ts +15 -0
  71. package/src/scripts/skill_scout.ts +633 -0
  72. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -33747,6 +33747,115 @@ var init_rule_scope = __esm({
33747
33747
  }
33748
33748
  });
33749
33749
 
33750
+ // src/scripts/_cli/_doctor_strict.ts
33751
+ function eprint(msg) {
33752
+ process.stderr.write(`${msg}
33753
+ `);
33754
+ }
33755
+ function strictTripping(level) {
33756
+ return level === "warn" ? /* @__PURE__ */ new Set(["fail", "warn"]) : /* @__PURE__ */ new Set(["fail"]);
33757
+ }
33758
+ function canProceed(driftPresent, checks) {
33759
+ return !driftPresent && !checks.some((c) => c["status"] === "fail");
33760
+ }
33761
+ function validateStrictLevel(level) {
33762
+ if (!STRICT_LEVELS.has(level)) {
33763
+ throw new StrictLevelError(
33764
+ `argument --strict-level: invalid choice: '${level}' (choose from 'fail', 'warn')`
33765
+ );
33766
+ }
33767
+ }
33768
+ function strictExit(opts, checks, driftPresent) {
33769
+ if (!opts.strict) {
33770
+ return null;
33771
+ }
33772
+ const tripping = strictTripping(opts.strict_level);
33773
+ const tripped = checks.filter((c) => tripping.has(c["status"]));
33774
+ if (!driftPresent && tripped.length === 0) {
33775
+ return 0;
33776
+ }
33777
+ if (process.env[STRICT_KILL_SWITCH] === "1") {
33778
+ eprint(
33779
+ `\u26A0\uFE0F doctor --strict: ${String(tripped.length)} finding(s) at or above '${opts.strict_level}'` + (driftPresent ? " plus manifest drift" : "") + `, but ${STRICT_KILL_SWITCH}=1 suppressed the failing exit. The findings above still stand; unset it to restore the gate.`
33780
+ );
33781
+ return 0;
33782
+ }
33783
+ return 1;
33784
+ }
33785
+ function attachProceedFields(payload, driftPresent, checks) {
33786
+ const proceed = canProceed(driftPresent, checks);
33787
+ payload["can_proceed"] = proceed;
33788
+ payload["status"] = proceed ? checks.some((c) => c["status"] === "warn") ? "warn" : "ok" : "fail";
33789
+ }
33790
+ function validateStrictLevelOr(level, onError) {
33791
+ try {
33792
+ validateStrictLevel(level);
33793
+ } catch (e) {
33794
+ if (e instanceof StrictLevelError) onError(e.message);
33795
+ throw e;
33796
+ }
33797
+ }
33798
+ var StrictLevelError, STRICT_DEFAULT_LEVEL, STRICT_LEVELS, STRICT_KILL_SWITCH, STRICT_STORE_TRUE, STRICT_VALUE_FLAGS, STRICT_DEFAULTS, STRICT_USAGE;
33799
+ var init_doctor_strict = __esm({
33800
+ "src/scripts/_cli/_doctor_strict.ts"() {
33801
+ "use strict";
33802
+ StrictLevelError = class extends Error {
33803
+ };
33804
+ STRICT_DEFAULT_LEVEL = "fail";
33805
+ STRICT_LEVELS = /* @__PURE__ */ new Set(["fail", "warn"]);
33806
+ STRICT_KILL_SWITCH = "AGENT_CONFIG_DOCTOR_NO_FAIL";
33807
+ STRICT_STORE_TRUE = { "--strict": "strict" };
33808
+ STRICT_VALUE_FLAGS = { "--strict-level": "strict_level" };
33809
+ STRICT_DEFAULTS = { strict: false, strict_level: STRICT_DEFAULT_LEVEL };
33810
+ STRICT_USAGE = " [--strict] [--strict-level {fail,warn}]\n";
33811
+ }
33812
+ });
33813
+
33814
+ // src/scripts/_cli/_doctor_emit.ts
33815
+ function print(line = "") {
33816
+ process.stdout.write(`${line}
33817
+ `);
33818
+ }
33819
+ function _emit_text(project_root, missing, modified, foreign, tag_drift) {
33820
+ const total = missing.length + modified.length + foreign.length + tag_drift.length;
33821
+ if (total === 0) {
33822
+ print(`\u2705 doctor: manifest matches filesystem under ${project_root}`);
33823
+ return;
33824
+ }
33825
+ print(`\u26A0\uFE0F doctor: ${total} drift item(s) under ${project_root}`);
33826
+ const groups = [
33827
+ ["missing", missing],
33828
+ ["modified", modified],
33829
+ ["foreign", foreign],
33830
+ ["tag-drift", tag_drift]
33831
+ ];
33832
+ for (const [label, items] of groups) {
33833
+ if (items.length === 0) {
33834
+ continue;
33835
+ }
33836
+ print(`
33837
+ ${label} (${items.length}):`);
33838
+ for (const it of items) {
33839
+ const tool = it["tool"] || "?";
33840
+ print(` \xB7 [${tool}] ${it["path"]}`);
33841
+ if (label === "tag-drift") {
33842
+ const found = it["found"] || "(missing)";
33843
+ const expected = "expected" in it ? it["expected"] : PACKAGE_TAG_ID;
33844
+ print(` expected: ${expected}`);
33845
+ print(` found: ${found}`);
33846
+ }
33847
+ print(` fix: ${it["fix"]}`);
33848
+ }
33849
+ }
33850
+ }
33851
+ var PACKAGE_TAG_ID;
33852
+ var init_doctor_emit = __esm({
33853
+ "src/scripts/_cli/_doctor_emit.ts"() {
33854
+ "use strict";
33855
+ PACKAGE_TAG_ID = "event4u/agent-config";
33856
+ }
33857
+ });
33858
+
33750
33859
  // src/scripts/_cli/cmd_doctor.ts
33751
33860
  var cmd_doctor_exports = {};
33752
33861
  __export(cmd_doctor_exports, {
@@ -33758,7 +33867,7 @@ __export(cmd_doctor_exports, {
33758
33867
  MCP_BETA_GATES: () => MCP_BETA_GATES,
33759
33868
  MIN_PYTHON: () => MIN_PYTHON,
33760
33869
  NO_FRONTMATTER: () => NO_FRONTMATTER,
33761
- PACKAGE_TAG_ID: () => PACKAGE_TAG_ID,
33870
+ PACKAGE_TAG_ID: () => PACKAGE_TAG_ID2,
33762
33871
  REPAIR_IDS: () => REPAIR_IDS,
33763
33872
  STATUS_SYMBOLS: () => STATUS_SYMBOLS,
33764
33873
  _check_bridge_drift: () => _check_bridge_drift,
@@ -33956,10 +34065,10 @@ function yamlSafeLoad2(text) {
33956
34065
  return void 0;
33957
34066
  }
33958
34067
  }
33959
- function print(line = "") {
34068
+ function print2(line = "") {
33960
34069
  process16.stdout.write(line + "\n");
33961
34070
  }
33962
- function eprint(line = "") {
34071
+ function eprint2(line = "") {
33963
34072
  process16.stderr.write(line + "\n");
33964
34073
  }
33965
34074
  function _resolve_project_root(arg) {
@@ -34011,22 +34120,22 @@ function _detect_wrapper(project_root) {
34011
34120
  };
34012
34121
  }
34013
34122
  function _emit_trace_text(root, anchor, trace, origin) {
34014
- print(` \u{1F4CD} start: ${process16.cwd()}`);
34015
- print(` \u{1F4CD} origin: ${origin}`);
34123
+ print2(` \u{1F4CD} start: ${process16.cwd()}`);
34124
+ print2(` \u{1F4CD} origin: ${origin}`);
34016
34125
  if (trace.length > 0) {
34017
- print(" trace:");
34126
+ print2(" trace:");
34018
34127
  for (const record of trace) {
34019
34128
  const hit = record.hit;
34020
34129
  const symbol = hit ? "\u2705" : "\xB7";
34021
34130
  const tag = `[${record.pass}]`;
34022
34131
  const anchor_str = hit ? ` \u2192 ${hit}` : "";
34023
- print(` ${symbol} ${tag} ${record.ancestor}${anchor_str} (${record.reason})`);
34132
+ print2(` ${symbol} ${tag} ${record.ancestor}${anchor_str} (${record.reason})`);
34024
34133
  }
34025
34134
  }
34026
34135
  if (root !== null) {
34027
- print(` \u{1F4CD} resolved root: ${root} (anchor: ${anchor || "n/a"})`);
34136
+ print2(` \u{1F4CD} resolved root: ${root} (anchor: ${anchor || "n/a"})`);
34028
34137
  } else {
34029
- print(" \u26A0\uFE0F no anchor found in chain");
34138
+ print2(" \u26A0\uFE0F no anchor found in chain");
34030
34139
  }
34031
34140
  }
34032
34141
  function _emit_trace_json(root, anchor, trace, origin) {
@@ -34037,32 +34146,32 @@ function _emit_trace_json(root, anchor, trace, origin) {
34037
34146
  anchor,
34038
34147
  trace
34039
34148
  };
34040
- print(_jsonDumpsIndentAscii(payload, 2));
34149
+ print2(_jsonDumpsIndentAscii(payload, 2));
34041
34150
  }
34042
34151
  function _emit_context_text(ctx) {
34043
- print(` \u{1F4CD} project_root: ${ctx["project_root"]} (origin: ${ctx["origin"]})`);
34044
- print(` \u{1F4E6} install_mode: ${ctx["install_mode"]} (source: ${ctx["install_mode_source"]})`);
34152
+ print2(` \u{1F4CD} project_root: ${ctx["project_root"]} (origin: ${ctx["origin"]})`);
34153
+ print2(` \u{1F4E6} install_mode: ${ctx["install_mode"]} (source: ${ctx["install_mode_source"]})`);
34045
34154
  const env_pin = ctx["env_pin"];
34046
34155
  if (env_pin) {
34047
34156
  const marker = ctx["root_override"] ? " (--root override)" : "";
34048
- print(` \u{1F512} env_pin: ${env_pin}${marker}`);
34157
+ print2(` \u{1F512} env_pin: ${env_pin}${marker}`);
34049
34158
  } else {
34050
- print(" \u{1F512} env_pin: (unset)");
34159
+ print2(" \u{1F512} env_pin: (unset)");
34051
34160
  }
34052
34161
  const layers = ctx["settings_layers"] || [];
34053
34162
  if (layers.length > 0) {
34054
- print(` \u{1F4D1} settings layers (${layers.length}):`);
34163
+ print2(` \u{1F4D1} settings layers (${layers.length}):`);
34055
34164
  for (const layer of layers) {
34056
- print(` - ${layer}`);
34165
+ print2(` - ${layer}`);
34057
34166
  }
34058
34167
  } else {
34059
- print(" \u{1F4D1} settings layers: (none)");
34168
+ print2(" \u{1F4D1} settings layers: (none)");
34060
34169
  }
34061
34170
  const wrapper = ctx["wrapper"] || {};
34062
34171
  if (wrapper["exists"]) {
34063
- print(` \u{1F9E9} wrapper: ${wrapper["path"]} (embedded root: ${wrapper["embedded_root"]})`);
34172
+ print2(` \u{1F9E9} wrapper: ${wrapper["path"]} (embedded root: ${wrapper["embedded_root"]})`);
34064
34173
  } else {
34065
- print(` \u{1F9E9} wrapper: (not present at ${wrapper["path"]})`);
34174
+ print2(` \u{1F9E9} wrapper: (not present at ${wrapper["path"]})`);
34066
34175
  }
34067
34176
  }
34068
34177
  function _resolve_path(project_root, raw) {
@@ -34245,12 +34354,12 @@ function _classify2(records) {
34245
34354
  continue;
34246
34355
  }
34247
34356
  const tag = _read_inline_package_tag(target);
34248
- if (!(tag instanceof _Sentinel) && tag !== PACKAGE_TAG_ID) {
34357
+ if (!(tag instanceof _Sentinel) && tag !== PACKAGE_TAG_ID2) {
34249
34358
  tag_drift.push({
34250
34359
  tool: tool_id,
34251
34360
  path: String(target),
34252
34361
  kind,
34253
- expected: PACKAGE_TAG_ID,
34362
+ expected: PACKAGE_TAG_ID2,
34254
34363
  found: tag === null ? "" : tag,
34255
34364
  fix: _fix_hint("tag-drift", kind)
34256
34365
  });
@@ -34857,7 +34966,7 @@ function _check_stale_orphans() {
34857
34966
  continue;
34858
34967
  }
34859
34968
  const tag = _read_inline_package_tag(md);
34860
- if (tag instanceof _Sentinel || tag !== PACKAGE_TAG_ID) {
34969
+ if (tag instanceof _Sentinel || tag !== PACKAGE_TAG_ID2) {
34861
34970
  continue;
34862
34971
  }
34863
34972
  orphan_count += 1;
@@ -35885,17 +35994,17 @@ function _run_no_manifest(opts, project_root, origin, bridge_present) {
35885
35994
  if (opts.json) {
35886
35995
  _emit_json(project_root, [], [], [], [], checks, origin);
35887
35996
  } else if (opts.check === null) {
35888
- print(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
35997
+ print2(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
35889
35998
  if (bridge_present) {
35890
- print(
35999
+ print2(
35891
36000
  " \u2139\uFE0F global-only consumer: install marker present, no project lockfile (expected under ADR-020)"
35892
36001
  );
35893
- print(
36002
+ print2(
35894
36003
  " project-manifest checks are skipped \u2014 they apply only to project-local distributed tools"
35895
36004
  );
35896
36005
  } else {
35897
- eprint(` \u26A0\uFE0F no project lockfile and no consumer install marker at ${project_root}`);
35898
- eprint(
36006
+ eprint2(` \u26A0\uFE0F no project lockfile and no consumer install marker at ${project_root}`);
36007
+ eprint2(
35899
36008
  " run `agent-config init` (project install) or `agent-config refresh --project` (global-only consumer)"
35900
36009
  );
35901
36010
  }
@@ -35911,6 +36020,8 @@ function _run_no_manifest(opts, project_root, origin, bridge_present) {
35911
36020
  }
35912
36021
  return fail_check ? 1 : 0;
35913
36022
  }
36023
+ const sx = strictExit(opts, checks, false);
36024
+ if (sx !== null) return bridge_present ? sx : 2;
35914
36025
  if (opts.ci && bridge_present) {
35915
36026
  return fail_check ? 1 : 0;
35916
36027
  }
@@ -35929,66 +36040,36 @@ function _emit_json(project_root, missing, modified, foreign, tag_drift, checks
35929
36040
  }
35930
36041
  if (checks !== null) {
35931
36042
  payload["checks"] = checks;
36043
+ const drift = missing.length + modified.length + foreign.length + tag_drift.length;
36044
+ attachProceedFields(payload, drift > 0, checks);
35932
36045
  if (checks.some((c) => c["id"] === "detection")) {
35933
36046
  payload["detection"] = detectionJson(_detection_report(project_root));
35934
36047
  }
35935
36048
  }
35936
- print(_jsonDumpsIndentAscii(payload, 2));
36049
+ print2(_jsonDumpsIndentAscii(payload, 2));
35937
36050
  }
35938
36051
  function _emit_checks_text(checks) {
35939
36052
  if (checks.length === 0) {
35940
36053
  return;
35941
36054
  }
35942
- print("checks:");
36055
+ print2("checks:");
35943
36056
  for (const c of checks) {
35944
36057
  const sym = STATUS_SYMBOLS[c["status"]] ?? "?";
35945
- print(` ${sym} ${c["id"]}: ${c["message"]}`);
36058
+ print2(` ${sym} ${c["id"]}: ${c["message"]}`);
35946
36059
  if (c["status"] !== "ok" && c["remedy"]) {
35947
- print(` fix: ${c["remedy"]}`);
36060
+ print2(` fix: ${c["remedy"]}`);
35948
36061
  }
35949
36062
  }
35950
- print("");
36063
+ print2("");
35951
36064
  }
35952
36065
  function _emit_detection_text(project_root, checks) {
35953
36066
  if (!checks.some((c) => c["id"] === "detection")) {
35954
36067
  return;
35955
36068
  }
35956
36069
  for (const line of renderDetectionLines(_detection_report(project_root))) {
35957
- print(line);
35958
- }
35959
- print("");
35960
- }
35961
- function _emit_text(project_root, missing, modified, foreign, tag_drift) {
35962
- const total = missing.length + modified.length + foreign.length + tag_drift.length;
35963
- if (total === 0) {
35964
- print(`\u2705 doctor: manifest matches filesystem under ${project_root}`);
35965
- return;
35966
- }
35967
- print(`\u26A0\uFE0F doctor: ${total} drift item(s) under ${project_root}`);
35968
- const groups = [
35969
- ["missing", missing],
35970
- ["modified", modified],
35971
- ["foreign", foreign],
35972
- ["tag-drift", tag_drift]
35973
- ];
35974
- for (const [label, items] of groups) {
35975
- if (items.length === 0) {
35976
- continue;
35977
- }
35978
- print(`
35979
- ${label} (${items.length}):`);
35980
- for (const it of items) {
35981
- const tool = it["tool"] || "?";
35982
- print(` \xB7 [${tool}] ${it["path"]}`);
35983
- if (label === "tag-drift") {
35984
- const found = it["found"] || "(missing)";
35985
- const expected = "expected" in it ? it["expected"] : PACKAGE_TAG_ID;
35986
- print(` expected: ${expected}`);
35987
- print(` found: ${found}`);
35988
- }
35989
- print(` fix: ${it["fix"]}`);
35990
- }
36070
+ print2(line);
35991
36071
  }
36072
+ print2("");
35992
36073
  }
35993
36074
  function _argError9(msg) {
35994
36075
  process16.stderr.write(USAGE2);
@@ -36001,6 +36082,7 @@ function _parse3(argv) {
36001
36082
  project: null,
36002
36083
  json: false,
36003
36084
  ci: false,
36085
+ ...STRICT_DEFAULTS,
36004
36086
  check: null,
36005
36087
  trace_root: false,
36006
36088
  context: false,
@@ -36098,7 +36180,7 @@ function _run_context(opts) {
36098
36180
  [project_root, origin] = _resolve_project_root(opts.project);
36099
36181
  } catch (exc) {
36100
36182
  if (exc instanceof ProjectRootError) {
36101
- eprint(`\u274C doctor: ${exc.message}`);
36183
+ eprint2(`\u274C doctor: ${exc.message}`);
36102
36184
  return 2;
36103
36185
  }
36104
36186
  throw exc;
@@ -36115,7 +36197,7 @@ function _run_context(opts) {
36115
36197
  wrapper: _detect_wrapper(project_root)
36116
36198
  };
36117
36199
  if (opts.json) {
36118
- print(_jsonDumpsIndentAscii(ctx, 2));
36200
+ print2(_jsonDumpsIndentAscii(ctx, 2));
36119
36201
  } else {
36120
36202
  _emit_context_text(ctx);
36121
36203
  }
@@ -36137,7 +36219,7 @@ function _run_anatomy(opts) {
36137
36219
  economyError = err instanceof Error ? err.message : String(err);
36138
36220
  }
36139
36221
  if (opts.json) {
36140
- print(
36222
+ print2(
36141
36223
  _jsonDumpsIndentAscii(
36142
36224
  {
36143
36225
  preamble_byte_census: byteCensus,
@@ -36149,10 +36231,10 @@ function _run_anatomy(opts) {
36149
36231
  );
36150
36232
  return 0;
36151
36233
  }
36152
- print("\u2500\u2500 injection anatomy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
36153
- print(census.renderText(byteCensus));
36154
- print("");
36155
- print(
36234
+ print2("\u2500\u2500 injection anatomy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
36235
+ print2(census.renderText(byteCensus));
36236
+ print2("");
36237
+ print2(
36156
36238
  economyReport !== null ? economy.renderText(economyReport) : `dispatch-economy report unavailable: ${economyError}`
36157
36239
  );
36158
36240
  return 0;
@@ -36174,29 +36256,30 @@ function _run_repair(opts) {
36174
36256
  payload["action"] = "error";
36175
36257
  payload["error"] = osErrorStr2(exc);
36176
36258
  if (opts.json) {
36177
- print(_jsonDumpsIndentAscii(payload, 2));
36259
+ print2(_jsonDumpsIndentAscii(payload, 2));
36178
36260
  } else {
36179
- eprint(`\u274C doctor: could not remove ${state_pth}: ${osErrorStr2(exc)}`);
36261
+ eprint2(`\u274C doctor: could not remove ${state_pth}: ${osErrorStr2(exc)}`);
36180
36262
  }
36181
36263
  return 2;
36182
36264
  }
36183
36265
  }
36184
36266
  if (opts.json) {
36185
- print(_jsonDumpsIndentAscii(payload, 2));
36267
+ print2(_jsonDumpsIndentAscii(payload, 2));
36186
36268
  } else {
36187
36269
  if (payload["action"] === "remove") {
36188
- print(`\u2705 doctor: reset wizard-state (${state_pth})`);
36270
+ print2(`\u2705 doctor: reset wizard-state (${state_pth})`);
36189
36271
  } else {
36190
- print(`\u2705 doctor: nothing to repair (no wizard-state at ${state_pth})`);
36272
+ print2(`\u2705 doctor: nothing to repair (no wizard-state at ${state_pth})`);
36191
36273
  }
36192
36274
  }
36193
36275
  return 0;
36194
36276
  }
36195
- eprint(`\u274C doctor: unknown repair target ${pyRepr2(target)}`);
36277
+ eprint2(`\u274C doctor: unknown repair target ${pyRepr2(target)}`);
36196
36278
  return 2;
36197
36279
  }
36198
36280
  function main21(argv = null) {
36199
36281
  const opts = _parse3(argv !== null ? Array.from(argv) : process16.argv.slice(2));
36282
+ validateStrictLevelOr(opts.strict_level, _argError9);
36200
36283
  if (opts.ci) {
36201
36284
  opts.json = true;
36202
36285
  }
@@ -36218,7 +36301,7 @@ function main21(argv = null) {
36218
36301
  [project_root, origin] = _resolve_project_root(opts.project);
36219
36302
  } catch (exc) {
36220
36303
  if (exc instanceof ProjectRootError) {
36221
- eprint(`\u274C doctor: ${exc.message}`);
36304
+ eprint2(`\u274C doctor: ${exc.message}`);
36222
36305
  return 2;
36223
36306
  }
36224
36307
  throw exc;
@@ -36244,7 +36327,7 @@ function main21(argv = null) {
36244
36327
  _emit_json(project_root, missing, modified, foreign, tag_drift, checks, origin);
36245
36328
  } else {
36246
36329
  if (opts.check === null) {
36247
- print(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
36330
+ print2(` \u{1F4CD} project_root: ${project_root} (origin: ${origin})`);
36248
36331
  }
36249
36332
  _emit_checks_text(checks);
36250
36333
  _emit_detection_text(project_root, checks);
@@ -36261,6 +36344,8 @@ function main21(argv = null) {
36261
36344
  if (opts.ci) {
36262
36345
  return drift_present || fail_check ? 1 : 0;
36263
36346
  }
36347
+ const strict = strictExit(opts, checks, drift_present);
36348
+ if (strict !== null) return strict;
36264
36349
  return drift_present ? 1 : 0;
36265
36350
  }
36266
36351
  function _isCliEntry17() {
@@ -36289,7 +36374,7 @@ function _isCliEntry17() {
36289
36374
  return false;
36290
36375
  }
36291
36376
  }
36292
- var YAML6, _HERE18, ArgparseExit4, _Sentinel, NO_FRONTMATTER, _INSTALL_MODE_MARKER_REL, PACKAGE_TAG_ID, _FRONTMATTER_PACKAGE_RE, CHECK_IDS, GLOBAL_CHECK_IDS, MANIFEST_REQUIRED_CHECK_IDS, REPAIR_IDS, MCP_BETA_GATES, STATUS_SYMBOLS, MIN_PYTHON, _PLACEHOLDER_EMAIL_RE, _PLACEHOLDER_NAME_RE, _RULE_PROJECTION_DIRS, _check_python_runtime, _check_humanizer_runtime, _CLI_PROVIDER_META, _detection_memo, PROG4, USAGE2, _STORE_TRUE_FLAGS, _VALUE_FLAGS;
36377
+ var YAML6, _HERE18, ArgparseExit4, _Sentinel, NO_FRONTMATTER, _INSTALL_MODE_MARKER_REL, PACKAGE_TAG_ID2, _FRONTMATTER_PACKAGE_RE, CHECK_IDS, GLOBAL_CHECK_IDS, MANIFEST_REQUIRED_CHECK_IDS, REPAIR_IDS, MCP_BETA_GATES, STATUS_SYMBOLS, MIN_PYTHON, _PLACEHOLDER_EMAIL_RE, _PLACEHOLDER_NAME_RE, _RULE_PROJECTION_DIRS, _check_python_runtime, _check_humanizer_runtime, _CLI_PROVIDER_META, _detection_memo, PROG4, USAGE2, _STORE_TRUE_FLAGS, _VALUE_FLAGS;
36293
36378
  var init_cmd_doctor = __esm({
36294
36379
  "src/scripts/_cli/cmd_doctor.ts"() {
36295
36380
  "use strict";
@@ -36320,6 +36405,8 @@ var init_cmd_doctor = __esm({
36320
36405
  init_review_gate();
36321
36406
  init_package_root();
36322
36407
  init_rule_scope();
36408
+ init_doctor_strict();
36409
+ init_doctor_emit();
36323
36410
  _HERE18 = fileURLToPath31(import.meta.url);
36324
36411
  ArgparseExit4 = class extends Error {
36325
36412
  constructor(code) {
@@ -36332,7 +36419,7 @@ var init_cmd_doctor = __esm({
36332
36419
  };
36333
36420
  NO_FRONTMATTER = new _Sentinel();
36334
36421
  _INSTALL_MODE_MARKER_REL = "agents/.agent-state/install-mode.txt";
36335
- PACKAGE_TAG_ID = "event4u/agent-config";
36422
+ PACKAGE_TAG_ID2 = "event4u/agent-config";
36336
36423
  _FRONTMATTER_PACKAGE_RE = /^package:\s*(.+?)\s*$/m;
36337
36424
  CHECK_IDS = [
36338
36425
  "scope",
@@ -36427,17 +36514,17 @@ var init_cmd_doctor = __esm({
36427
36514
  _detection_memo = null;
36428
36515
  PROG4 = "agent-config doctor";
36429
36516
  USAGE2 = `usage: ${PROG4} [-h] [--project PROJECT] [--json] [--ci] [--check ID]
36430
- [--trace-root] [--context] [--anatomy]
36431
- [--repair ID]
36432
- `;
36517
+ ` + STRICT_USAGE + " [--trace-root] [--context] [--anatomy]\n [--repair ID]\n";
36433
36518
  _STORE_TRUE_FLAGS = {
36434
36519
  "--json": "json",
36435
36520
  "--ci": "ci",
36521
+ ...STRICT_STORE_TRUE,
36436
36522
  "--trace-root": "trace_root",
36437
36523
  "--context": "context",
36438
36524
  "--anatomy": "anatomy"
36439
36525
  };
36440
36526
  _VALUE_FLAGS = {
36527
+ ...STRICT_VALUE_FLAGS,
36441
36528
  "--project": "project",
36442
36529
  "--check": "check",
36443
36530
  "--repair": "repair"
@@ -37202,6 +37289,65 @@ function splitFences(prompt) {
37202
37289
  function _commandLines(fence) {
37203
37290
  return fence.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(remote:|error:|hint:|fatal:|warning:|To |!\s|\s*\^)/i.test(l)).map((l) => l.replace(/^[$>#]\s*/, "")).filter((l) => /^(git|gh|npm|pnpm|yarn|task)\b/.test(l));
37204
37291
  }
37292
+ function extractMergeTargets(prompt) {
37293
+ const out = /* @__PURE__ */ new Set();
37294
+ const re = /(?:#|\bpr[- ]?|\bpull[- ]request\s+#?)(\d{1,7})\b/giu;
37295
+ let m;
37296
+ while ((m = re.exec(prompt)) !== null) {
37297
+ const n = Number.parseInt(m[1], 10);
37298
+ if (Number.isSafeInteger(n) && n > 0) {
37299
+ out.add(n);
37300
+ }
37301
+ }
37302
+ return [...out].sort((a, b) => a - b);
37303
+ }
37304
+ function isRevocation(prompt) {
37305
+ return REVOKE_RE.test(prompt);
37306
+ }
37307
+ function _grantId(session_id, at, targets) {
37308
+ return `${sessionSlug(session_id) || "nosession"}:${at}:${targets.join(",")}`;
37309
+ }
37310
+ function foldGrants(prior, prompt, authorized, session_id, now) {
37311
+ if (isRevocation(prompt)) {
37312
+ return [];
37313
+ }
37314
+ const carried = prior.filter((g) => g.consumed.length < g.targets.length);
37315
+ if (!authorized.includes("pr-merge")) {
37316
+ return carried;
37317
+ }
37318
+ const targets = extractMergeTargets(prompt);
37319
+ if (targets.length === 0) {
37320
+ return carried;
37321
+ }
37322
+ const at = now.toISOString();
37323
+ const already = new Set(carried.flatMap((g) => g.targets));
37324
+ const fresh = targets.filter((t) => !already.has(t));
37325
+ if (fresh.length === 0) {
37326
+ return carried;
37327
+ }
37328
+ return [
37329
+ ...carried,
37330
+ {
37331
+ id: _grantId(session_id, at, fresh),
37332
+ op: "pr-merge",
37333
+ targets: fresh,
37334
+ consumed: [],
37335
+ granted_at: at,
37336
+ evidence: prompt.trim().slice(0, 120)
37337
+ }
37338
+ ];
37339
+ }
37340
+ function negatedBefore(text, index, matched = "") {
37341
+ let start = 0;
37342
+ for (const mark of [".", "!", "?", "\n"]) {
37343
+ const at = text.lastIndexOf(mark, index - 1);
37344
+ if (at + 1 > start) {
37345
+ start = at + 1;
37346
+ }
37347
+ }
37348
+ const before = text.slice(Math.max(start, index - 30), index);
37349
+ return NEGATION_WORD.test(before) || NEGATION_WORD.test(matched);
37350
+ }
37205
37351
  function classifyAuthorization(prompt) {
37206
37352
  const { prose, fences } = splitFences(prompt);
37207
37353
  const authorized = /* @__PURE__ */ new Set();
@@ -37209,7 +37355,7 @@ function classifyAuthorization(prompt) {
37209
37355
  const instruction = prose.split("\n").filter((l) => !OUTPUT_LINE.test(l)).join("\n");
37210
37356
  for (const { op, re } of isInterrogative(instruction) ? [] : PHRASES) {
37211
37357
  const m = re.exec(instruction);
37212
- if (m) {
37358
+ if (m && !negatedBefore(instruction, m.index, m[0])) {
37213
37359
  authorized.add(op);
37214
37360
  evidence[op] = `prose: "${m[0]}"`;
37215
37361
  }
@@ -37231,6 +37377,68 @@ function classifyAuthorization(prompt) {
37231
37377
  }
37232
37378
  return { authorized: [...authorized], evidence };
37233
37379
  }
37380
+ function readGrants(consumer_root, session_id) {
37381
+ try {
37382
+ const raw = fs65.readFileSync(path66.join(consumer_root, ledgerFileFor(session_id)), "utf8");
37383
+ const decoded = JSON.parse(raw);
37384
+ if (!_isObject4(decoded) || !Array.isArray(decoded["grants"])) {
37385
+ return [];
37386
+ }
37387
+ const owner = typeof decoded["session_id"] === "string" ? decoded["session_id"] : "";
37388
+ if (session_id && owner && owner !== session_id) {
37389
+ return [];
37390
+ }
37391
+ return decoded["grants"].flatMap((g) => {
37392
+ if (!_isObject4(g)) return [];
37393
+ const op = g["op"];
37394
+ if (typeof op !== "string" || !ALL_OPS.includes(op)) return [];
37395
+ const targets = Array.isArray(g["targets"]) ? g["targets"] : [];
37396
+ const consumed = Array.isArray(g["consumed"]) ? g["consumed"] : [];
37397
+ return [
37398
+ {
37399
+ id: String(g["id"] ?? ""),
37400
+ op,
37401
+ targets: targets.filter((t) => typeof t === "number"),
37402
+ consumed: consumed.filter((t) => typeof t === "number"),
37403
+ granted_at: String(g["granted_at"] ?? ""),
37404
+ evidence: String(g["evidence"] ?? "")
37405
+ }
37406
+ ];
37407
+ });
37408
+ } catch {
37409
+ return [];
37410
+ }
37411
+ }
37412
+ function consumeGrantTarget(consumer_root, session_id, target) {
37413
+ const file = path66.join(consumer_root, ledgerFileFor(session_id));
37414
+ let decoded;
37415
+ try {
37416
+ decoded = JSON.parse(fs65.readFileSync(file, "utf8"));
37417
+ } catch {
37418
+ return;
37419
+ }
37420
+ if (!_isObject4(decoded) || !Array.isArray(decoded["grants"])) {
37421
+ return;
37422
+ }
37423
+ let touched = false;
37424
+ for (const g of decoded["grants"]) {
37425
+ if (!_isObject4(g) || !Array.isArray(g["targets"]) || !Array.isArray(g["consumed"])) {
37426
+ continue;
37427
+ }
37428
+ const targets = g["targets"];
37429
+ const consumed = g["consumed"];
37430
+ if (targets.includes(target) && !consumed.includes(target)) {
37431
+ consumed.push(target);
37432
+ touched = true;
37433
+ }
37434
+ }
37435
+ if (touched) {
37436
+ try {
37437
+ atomic_write_json(file, decoded);
37438
+ } catch {
37439
+ }
37440
+ }
37441
+ }
37234
37442
  function takePending(consumer_root, session_id, now) {
37235
37443
  const file = path66.join(consumer_root, pendingFileFor(session_id));
37236
37444
  let raw;
@@ -37299,12 +37507,24 @@ function run2(stdin_text, options) {
37299
37507
  authorized.push(pending.op);
37300
37508
  evidence[pending.op] = `confirmation of the refused \`${pending.op}\` (refused ${pending.refused_at}): "${prompt.trim().slice(0, 40)}"`;
37301
37509
  }
37510
+ const now = /* @__PURE__ */ new Date();
37302
37511
  const ledger = {
37303
37512
  session_id: typeof envelope["session_id"] === "string" ? envelope["session_id"] : "",
37304
- detected_at: (/* @__PURE__ */ new Date()).toISOString(),
37513
+ detected_at: now.toISOString(),
37305
37514
  authorized,
37306
37515
  evidence,
37307
- prompt_chars: prompt.length
37516
+ prompt_chars: prompt.length,
37517
+ // Grants survive this write; `authorized` does not. That asymmetry IS the
37518
+ // decision. A bare operation name stays one-shot exactly as `commit-policy`
37519
+ // requires, and only an authorization that also froze its objects earns a
37520
+ // life longer than the turn. See ADR-252.
37521
+ grants: foldGrants(
37522
+ readGrants(options.consumer_root, session),
37523
+ prompt,
37524
+ authorized,
37525
+ session,
37526
+ now
37527
+ )
37308
37528
  };
37309
37529
  try {
37310
37530
  atomic_write_json(path66.join(options.consumer_root, ledgerFileFor(session)), ledger);
@@ -37345,7 +37565,7 @@ function _isCliEntry19() {
37345
37565
  return false;
37346
37566
  }
37347
37567
  }
37348
- var EXIT_ALLOW2, STATE_FILE2, STATE_DIR2, PENDING_DIR, PENDING_FILE, PENDING_MAX_AGE_MS, ALL_OPS, PHRASES, OUTPUT_LINE, PASTED_COMMANDS;
37568
+ var EXIT_ALLOW2, STATE_FILE2, STATE_DIR2, PENDING_DIR, PENDING_FILE, PENDING_MAX_AGE_MS, ALL_OPS, PHRASES, OUTPUT_LINE, PASTED_COMMANDS, REVOKE_RE, NEGATION_WORD;
37349
37569
  var init_git_authorization_hook = __esm({
37350
37570
  "src/scripts/git_authorization_hook.ts"() {
37351
37571
  "use strict";
@@ -37368,12 +37588,39 @@ var init_git_authorization_hook = __esm({
37368
37588
  "pr-merge-auto",
37369
37589
  "tag",
37370
37590
  "release",
37371
- "publish"
37591
+ "publish",
37592
+ "unpublish",
37593
+ "deprecate",
37594
+ "release-asset",
37595
+ "protection",
37596
+ "workflow-toggle",
37597
+ "repo-lifecycle",
37598
+ "review-approve",
37599
+ "force-push",
37600
+ "worktree-remove",
37601
+ "clean-ignored",
37602
+ "tag-force",
37603
+ "rebase",
37604
+ "reset-hard",
37605
+ "clean",
37606
+ "stash-drop",
37607
+ "branch-delete",
37608
+ "close"
37372
37609
  ];
37373
37610
  PHRASES = [
37374
37611
  { op: "commit", re: /\b(commit(e|et|te|ten)?|committe|einchecken)\b/i },
37375
37612
  { op: "push", re: /\b(push(e|en|t)?|hochladen|hochschieben|raufschieben)\b/i },
37376
- { op: "branch", re: /\b(branch|feature-branch|zweig)\b/i },
37613
+ // A CREATION VERB IS REQUIRED, as for `pr-create` above and for the same
37614
+ // reason. The bare noun could not tell an authorization from a mention, and
37615
+ // the mention is the dangerous half: "den branch nicht löschen" names the
37616
+ // object precisely in order to forbid acting on it, yet authorized branch
37617
+ // creation — the negation trails a bare-noun match, so neither the
37618
+ // look-behind nor the span check in `negatedBefore` can reach it. Widened
37619
+ // 2026-09-03 alongside the destructive-coverage work that measured it.
37620
+ {
37621
+ op: "branch",
37622
+ re: /\b(erstell(e|en)?|mach(e|en)?|leg(e|en)?\s+an|anleg(e|en)?|(er|be)?(ö|oe)ffne|open|create|new|neuer?|starte?|checkout|wechsl?e|switch)\b[^.\n]{0,30}\b(branch|feature-branch|zweig)\b|\b(branch|feature-branch|zweig)\b[^.\n]{0,25}\b(erstellen|anlegen|aufmachen|(ö|oe)ffnen|starten)\b/i
37623
+ },
37377
37624
  // A creation verb is required — "schau dir den PR an" is not "open a PR".
37378
37625
  {
37379
37626
  op: "pr-create",
@@ -37410,7 +37657,111 @@ var init_git_authorization_hook = __esm({
37410
37657
  op: "release",
37411
37658
  re: /\b(releasen?|ver(ö|oe)ffentlich(e|en))\b(?!\s*[- ]?(notes?|branch|candidate|pr\b|datum|date|zweig))/i
37412
37659
  },
37413
- { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i }
37660
+ { op: "publish", re: /\b(publish(e|en)?|publiziere[n]?)\b/i },
37661
+ // ── Authorization vocabulary for the ops added 2026-09-03.
37662
+ //
37663
+ // NON-NEGOTIABLE, and the reason it ships in the same change as the new BLOCK
37664
+ // ops: a blocked operation with no phrase that authorizes it is a dead end.
37665
+ // The guard refuses, the user says "doch, mach das", the classifier records
37666
+ // nothing, and the refusal repeats forever. Every op the guard can block must
37667
+ // have a sentence that unblocks it, or the user's override is a claim rather
37668
+ // than a mechanism.
37669
+ //
37670
+ // Tight on purpose. Under-matching costs one confirmation; over-matching
37671
+ // authorizes something the user did not ask for, so each pattern demands the
37672
+ // object noun rather than a bare verb wherever the verb is ordinary German.
37673
+ //
37674
+ // `unpublish` before nothing in particular — `\bpublish\b` does not match
37675
+ // inside "unpublish" (no word boundary between `n` and `p`), so the two never
37676
+ // collide.
37677
+ // TWO SHAPES EVERY GERMAN PATTERN BELOW MUST CARRY, both learned from tests
37678
+ // that went red rather than from inspection:
37679
+ //
37680
+ // 1. THE IMPERATIVE DROPS ITS `-e`. "entfern die worktree", "archivier das
37681
+ // repo", "deaktivier den workflow" are what people type; `entfern(e|en)`
37682
+ // matches none of them. Every verb ending is therefore `(e|en)?`.
37683
+ // 2. WORD ORDER GOES BOTH WAYS. "archivier das repo" and "das repo
37684
+ // archivieren" are one instruction, and a pattern anchored on the noun
37685
+ // first sees only the second.
37686
+ //
37687
+ // The object noun stays REQUIRED in every pattern that has one. That is what
37688
+ // keeps the loosened verb endings from over-authorizing: "lösch" alone
37689
+ // authorizes nothing, "lösch den branch" authorizes a branch deletion.
37690
+ { op: "unpublish", re: /\bunpublish(e|en)?\b/i },
37691
+ {
37692
+ op: "deprecate",
37693
+ re: /\bdeprecate(d|n)?\b|\bals\s+veraltet\s+markier(e|en)?\b/i
37694
+ },
37695
+ {
37696
+ op: "release-asset",
37697
+ re: /\brelease[-\s]?asset\b|\b(clobber|(ü|ue)berschreib(e|en)?|ersetz(e|en)?|replace|austausch(e|en)?)\b[^.\n]{0,25}\b(asset|artefakt|artifact|tarball|binary)\b|\b(asset|artefakt|artifact)\b[^.\n]{0,25}\b((ü|ue)berschreiben|ersetzen|austauschen|replace)\b/i
37698
+ },
37699
+ // A VERB IS REQUIRED, like `tag` and `release` above and for the same reason.
37700
+ // A bare noun cannot tell an authorization from a mention, and here the
37701
+ // mention is the dangerous half: "die branch-protection nicht anfassen" names
37702
+ // the object precisely in order to forbid touching it. Measured — this was the
37703
+ // one row of the negation corpus that neither the look-behind nor the
37704
+ // span check could reach, because the negation trails a bare-noun match.
37705
+ {
37706
+ op: "protection",
37707
+ re: /\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|deaktivier(e|en)?|nimm|weg|remove|delete|disable|drop|lift)\b[^.\n]{0,30}\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b|\b(branch[-\s]?protection|protection[-\s]?rule|schutzregel|ruleset)\b[^.\n]{0,30}\b(entfernen|l(ö|oe)schen|deaktivieren|abschalten|remove|delete|disable|drop|lift|weg)\b/i
37708
+ },
37709
+ {
37710
+ op: "workflow-toggle",
37711
+ re: /\bworkflow\b[^.\n]{0,25}\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b|\b(deaktivier(e|en)?|aktivier(e|en)?|disable|enable|abschalt(e|en)?|ausschalt(e|en)?)\b[^.\n]{0,25}\bworkflow\b/i
37712
+ },
37713
+ {
37714
+ op: "repo-lifecycle",
37715
+ re: /\b(repo|repository)\b[^.\n]{0,30}\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b|\b(archivier(e|en)?|archive|l(ö|oe)sch(e|en)?|delete)\b[^.\n]{0,25}\b(repo|repository)\b|\b(repo|repository)\b[^.\n]{0,30}\bauf\s+(privat|public|öffentlich|private)\b/i
37716
+ },
37717
+ {
37718
+ op: "review-approve",
37719
+ re: /\b(approve|genehmig(e|en)?|freigeb(e|en)?|abnehm(e|en)?)\b[^.\n]{0,30}\b(review|pr|pull[-\s]request)\b|\b(review|pr|pull[-\s]request)\b[^.\n]{0,20}\b(approve|approven|genehmigen|freigeben)\b/i
37720
+ },
37721
+ {
37722
+ op: "force-push",
37723
+ re: /\bforce[-\s]?push(e|en|st)?\b|--force(-with-lease)?\b|\bmit\s+gewalt\s+push(e|en)?\b/i
37724
+ },
37725
+ {
37726
+ op: "worktree-remove",
37727
+ re: /\bworktree\b[^.\n]{0,25}\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete|weg(werfen|r(ä|ae)umen))\b|\b(entfern(e|en)?|l(ö|oe)sch(e|en)?|remove|delete)\b[^.\n]{0,25}\bworktree\b/i
37728
+ },
37729
+ // The `-x` flag is the whole distinction: without it `git clean` spares
37730
+ // ignored files, with it `.env` and local certificates go too.
37731
+ {
37732
+ op: "clean-ignored",
37733
+ re: /\bgit\s+clean\b[^.\n]{0,20}-[A-Za-z]*x|\b(ignorierte|ignored)\b[^.\n]{0,25}\b(dateien|files)\b[^.\n]{0,25}\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|remove|delete)\b/i
37734
+ },
37735
+ {
37736
+ op: "tag-force",
37737
+ re: /\btag\b[^.\n]{0,25}\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?|umh(ä|ae)ng(e|en)?|force|move)\b|\b(verschieb(e|en)?|(ü|ue)berschreib(e|en)?)\b[^.\n]{0,20}\btag\b/i
37738
+ },
37739
+ { op: "rebase", re: /\brebase(n|st|d)?\b/i },
37740
+ {
37741
+ op: "reset-hard",
37742
+ re: /\breset\s+--hard\b|\bhard\s+reset\b|\bhart\s+zur(ü|ue)cksetz(e|en)?\b/i
37743
+ },
37744
+ { op: "clean", re: /\bgit\s+clean\b/i },
37745
+ {
37746
+ op: "stash-drop",
37747
+ re: /\bstash\b[^.\n]{0,25}\b(drop|clear|verwerf(e|en)?|l(ö|oe)sch(e|en)?)\b|\b(verwerf(e|en)?|l(ö|oe)sch(e|en)?|drop)\b[^.\n]{0,20}\bstash\b/i
37748
+ },
37749
+ {
37750
+ op: "branch-delete",
37751
+ re: /\b(l(ö|oe)sch(e|en)?|entfern(e|en)?|delete|remove)\b[^.\n]{0,25}\b(branch|branches|zweig)\b|\b(branch|branches|zweig)\b[^.\n]{0,25}\b(l(ö|oe)schen|entfernen|delete|remove)\b/i
37752
+ },
37753
+ {
37754
+ op: "close",
37755
+ re: /\b(schlie(ß|ss)(e|en)?|close)\b[^.\n]{0,30}\b(pr|pull[-\s]request|issue|ticket)\b|\b(pr|pull[-\s]request|issue|ticket)\b[^.\n]{0,20}\b(schlie(ß|ss)en|close)\b/i
37756
+ },
37757
+ // Enabling auto-merge is its own op on the guard side, so it needs its own
37758
+ // sentence. Without one it is the dead end this block exists to prevent: the
37759
+ // plain merge phrase authorizes `pr-merge` and never `pr-merge-auto`, so the
37760
+ // refusal would have repeated forever.
37761
+ {
37762
+ op: "pr-merge-auto",
37763
+ re: /\bauto[-\s]?merge\b|\bmerge\b[^.\n]{0,20}\bautomatisch\b|\bautomatisch\b[^.\n]{0,20}\bmerg(e|en)\b/i
37764
+ }
37414
37765
  ];
37415
37766
  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;
37416
37767
  PASTED_COMMANDS = [
@@ -37433,12 +37784,107 @@ var init_git_authorization_hook = __esm({
37433
37784
  { op: "commit", re: /\bgit\s+commit\b/i },
37434
37785
  { op: "branch", re: /\bgit\s+(checkout\s+-b|switch\s+-c|branch)\b/i }
37435
37786
  ];
37787
+ REVOKE_RE = /\b(stop|stopp|halt|abbrechen|abbruch|cancel|revoke|widerruf(en)?|zur(ü|ue)ck(nehmen|ziehen)|hold\s+(everything|on|the\s+merge)|warte|wait)\b|(?:\b(nicht|kein(e|en)?|no|not|dont|don't|never|niemals|nie)\b[^.!?\n]{0,30}\b(merg(e|en|ing)|zusammenf(ü|ue)hren)\b)/iu;
37788
+ NEGATION_WORD = /\b(nicht|nichts|kein(e|en|em|er|es)?|niemals|nie|no|not|dont|don't|never|without|ohne)\b/i;
37436
37789
  if (_isCliEntry19()) {
37437
37790
  process.exit(main23());
37438
37791
  }
37439
37792
  }
37440
37793
  });
37441
37794
 
37795
+ // src/scripts/hooks/merge_impact.ts
37796
+ import { execFileSync as execFileSync2 } from "node:child_process";
37797
+ function classifyDiff(patch) {
37798
+ if (patch.trim() === "") {
37799
+ return { verdict: "undecidable", markers: [], reason: "empty diff" };
37800
+ }
37801
+ const markers = [];
37802
+ for (const { label, re } of DESTRUCTIVE_MARKERS) {
37803
+ if (re.test(patch)) {
37804
+ markers.push(label);
37805
+ }
37806
+ }
37807
+ const bump = MAJOR_BUMP.exec(patch);
37808
+ if (bump && bump[1] !== bump[2]) {
37809
+ markers.push(`major version bump ${bump[1]}.x to ${bump[2]}.x`);
37810
+ }
37811
+ const filesChanged = (patch.match(/^diff --git /gm) ?? []).length;
37812
+ if (markers.length > 0) {
37813
+ return { verdict: "destructive", markers, filesChanged };
37814
+ }
37815
+ return { verdict: "additive", markers: [], filesChanged };
37816
+ }
37817
+ function fetchPatch(pr, opts = {}) {
37818
+ const run6 = opts.exec ?? execFileSync2;
37819
+ const cwd = opts.cwd ?? process.cwd();
37820
+ const capture = (file, args) => run6(file, args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
37821
+ for (const ref of [`refs/pull/${pr}/head`, `pull/${pr}/head`]) {
37822
+ try {
37823
+ const base = capture("git", ["merge-base", "HEAD", ref]).trim();
37824
+ if (base) {
37825
+ const patch = capture("git", ["diff", `${base}...${ref}`]);
37826
+ if (patch.trim() !== "") {
37827
+ return { patch, source: "local" };
37828
+ }
37829
+ }
37830
+ } catch {
37831
+ }
37832
+ }
37833
+ if (opts.allowNetwork === false) {
37834
+ return { patch: "", source: "none", reason: "no local ref and network reads disabled" };
37835
+ }
37836
+ try {
37837
+ const patch = capture("gh", ["pr", "diff", String(pr), "--patch"]);
37838
+ if (patch.trim() !== "") {
37839
+ return { patch, source: "gh" };
37840
+ }
37841
+ return { patch: "", source: "none", reason: "gh returned an empty diff" };
37842
+ } catch {
37843
+ return { patch: "", source: "none", reason: "no local ref, and gh pr diff failed" };
37844
+ }
37845
+ }
37846
+ function analyseMergeImpact(pr, opts = {}) {
37847
+ const got = fetchPatch(pr, opts);
37848
+ if (got.source === "none") {
37849
+ return { verdict: "undecidable", markers: [], ...got.reason ? { reason: got.reason } : {} };
37850
+ }
37851
+ return classifyDiff(got.patch);
37852
+ }
37853
+ function describeImpact(pr, impact) {
37854
+ const say = `Say "merge #${pr}" to authorize it.`;
37855
+ switch (impact.verdict) {
37856
+ case "additive":
37857
+ return `Impact scan: PR #${pr} touches ${impact.filesChanged ?? 0} file(s) and trips no destructive marker \u2014 no schema drop, no removed export or route, no major bump, no BREAKING CHANGE. Additive as far as the diff can show, which is not the same as safe to merge: the base has moved since review and merging may trigger a deploy. ${say}`;
37858
+ case "destructive":
37859
+ return `Impact scan: PR #${pr} is NOT additive \u2014 ${impact.markers.join("; ")}. Read the diff before authorizing. ${say}`;
37860
+ default:
37861
+ return `Impact scan: could not read the diff for PR #${pr} (${impact.reason ?? "unknown"}), so nothing is known about what it changes. ${say}`;
37862
+ }
37863
+ }
37864
+ var DESTRUCTIVE_MARKERS, MAJOR_BUMP;
37865
+ var init_merge_impact = __esm({
37866
+ "src/scripts/hooks/merge_impact.ts"() {
37867
+ "use strict";
37868
+ DESTRUCTIVE_MARKERS = [
37869
+ // Schema removal. Matched on ADDED lines only — a migration that *removes* a
37870
+ // dropColumn call is the opposite of destructive, and matching the raw patch
37871
+ // would score it the same way.
37872
+ { label: "migration drops a column or table", re: /^\+.*\b(dropColumn|dropTable|dropIfExists|removeColumn|drop_column|drop_table)\b/im },
37873
+ { label: "raw DROP or TRUNCATE in a migration", re: /^\+.*\b(DROP\s+(TABLE|COLUMN|INDEX|CONSTRAINT)|TRUNCATE\s+TABLE)\b/im },
37874
+ // The author said so themselves. Conventional Commits marks a breaking change
37875
+ // with a `!` before the colon or a body trailer.
37876
+ { label: "author marked a BREAKING CHANGE", re: /^\+.*BREAKING[ -]CHANGE/m },
37877
+ // A removed export is the shape most likely to break an importer.
37878
+ { label: "an exported symbol was removed", re: /^-\s*export\s+(default\s+|const\s+|function\s+|class\s+|interface\s+|type\s+|async\s+)/m },
37879
+ { label: "a public PHP or Python symbol was removed", re: /^-\s*(public\s+function|def\s+[a-z_]+\s*\()/m },
37880
+ // Route removal breaks callers that never imported anything.
37881
+ { label: "a route or endpoint was removed", re: /^-.*\b(Route::(get|post|put|patch|delete)|@(Get|Post|Put|Patch|Delete)Mapping|app\.(get|post|put|patch|delete)\s*\()/m },
37882
+ { label: "a whole file was deleted", re: /^deleted file mode /m }
37883
+ ];
37884
+ MAJOR_BUMP = /^-\s*"version"\s*:\s*"(\d+)\.[^"]*"[\s\S]{0,200}?^\+\s*"version"\s*:\s*"(\d+)\./m;
37885
+ }
37886
+ });
37887
+
37442
37888
  // src/scripts/hooks/block_unauthorized_git.ts
37443
37889
  import fs66 from "node:fs";
37444
37890
  import path67 from "node:path";
@@ -37629,7 +38075,24 @@ function commandOp(command) {
37629
38075
  }
37630
38076
  return found;
37631
38077
  }
38078
+ function mergeTargetOf(command) {
38079
+ const gh = /\bgh\s+pr\s+merge\s+(?:-{1,2}\S+\s+)*?(\d{1,7})\b/i.exec(command);
38080
+ if (gh) {
38081
+ return Number.parseInt(gh[1], 10);
38082
+ }
38083
+ const api = /\/pulls\/(\d{1,7})\/merge\b/i.exec(command);
38084
+ return api ? Number.parseInt(api[1], 10) : null;
38085
+ }
38086
+ function grantCovers(grants, op, target) {
38087
+ if (op !== "pr-merge" || target === null) {
38088
+ return null;
38089
+ }
38090
+ return grants.find(
38091
+ (g) => g.op === "pr-merge" && g.targets.includes(target) && !g.consumed.includes(target)
38092
+ ) ?? null;
38093
+ }
37632
38094
  function _loadLedger(consumer_root, session_id, now) {
38095
+ const grants = readGrants(consumer_root, session_id);
37633
38096
  const candidates = [ledgerFileFor(session_id)];
37634
38097
  if (!candidates.includes(STATE_FILE2)) {
37635
38098
  candidates.push(STATE_FILE2);
@@ -37637,10 +38100,10 @@ function _loadLedger(consumer_root, session_id, now) {
37637
38100
  for (const rel of candidates) {
37638
38101
  const found = _readLedgerFile(path67.join(consumer_root, rel), session_id, now);
37639
38102
  if (found !== null) {
37640
- return found;
38103
+ return { ...found, grants };
37641
38104
  }
37642
38105
  }
37643
- return { authorized: /* @__PURE__ */ new Set(), present: false };
38106
+ return { authorized: /* @__PURE__ */ new Set(), present: false, grants };
37644
38107
  }
37645
38108
  function _readLedgerFile(file, session_id, now) {
37646
38109
  try {
@@ -37664,7 +38127,7 @@ function _readLedgerFile(file, session_id, now) {
37664
38127
  }
37665
38128
  return null;
37666
38129
  }
37667
- function decide(command, ledger) {
38130
+ function decide(command, ledger, impact) {
37668
38131
  if (command === null) {
37669
38132
  return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
37670
38133
  }
@@ -37672,9 +38135,14 @@ function decide(command, ledger) {
37672
38135
  if (op === null || ledger.authorized.has(op)) {
37673
38136
  return { exit: EXIT_ALLOW3, stdout: "", stderr: "" };
37674
38137
  }
38138
+ const target = mergeTargetOf(command);
38139
+ if (grantCovers(ledger.grants ?? [], op, target) && target !== null) {
38140
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", consumedTarget: target };
38141
+ }
37675
38142
  const shown = command.length > 120 ? `${command.slice(0, 117)}\u2026` : command;
37676
38143
  if (BLOCK_OPS.has(op)) {
37677
- const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}. Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
38144
+ const scan2 = op === "pr-merge" && target !== null && impact ? ` ${impact}` : "";
38145
+ const reason = `Blocked: \`${op}\` with no authorization in this turn's prompt. This operation is irreversible and non-destructive-by-default already declares it never-autonomous. Command: ${shown}.${scan2} Ask the user, in one numbered-options block, and run it only after they answer this turn.`;
37678
38146
  return { exit: EXIT_BLOCK, stdout: "", stderr: `${reason}
37679
38147
  `, blockedOp: op };
37680
38148
  }
@@ -37702,10 +38170,24 @@ function run3(stdin_text, options) {
37702
38170
  }
37703
38171
  }
37704
38172
  const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
37705
- const decision = decide(
37706
- extractCommand(envelope),
37707
- _loadLedger(options.consumer_root, session_id, Date.now())
37708
- );
38173
+ const command = extractCommand(envelope);
38174
+ const ledger = _loadLedger(options.consumer_root, session_id, Date.now());
38175
+ let impact;
38176
+ const stage1 = command === null ? null : commandOp(command);
38177
+ const stage2Target = command === null ? null : mergeTargetOf(command);
38178
+ if (stage1 === "pr-merge" && stage2Target !== null && !ledger.authorized.has("pr-merge") && !grantCovers(ledger.grants ?? [], "pr-merge", stage2Target)) {
38179
+ try {
38180
+ impact = describeImpact(
38181
+ stage2Target,
38182
+ analyseMergeImpact(stage2Target, { cwd: options.consumer_root })
38183
+ );
38184
+ } catch {
38185
+ }
38186
+ }
38187
+ const decision = decide(command, ledger, impact);
38188
+ if (decision.consumedTarget !== void 0) {
38189
+ consumeGrantTarget(options.consumer_root, session_id, decision.consumedTarget);
38190
+ }
37709
38191
  if (decision.blockedOp) {
37710
38192
  try {
37711
38193
  atomic_write_json(path67.join(options.consumer_root, pendingFileFor(session_id)), {
@@ -37762,6 +38244,7 @@ var init_block_unauthorized_git = __esm({
37762
38244
  "src/scripts/hooks/block_unauthorized_git.ts"() {
37763
38245
  "use strict";
37764
38246
  init_hook_stdin();
38247
+ init_merge_impact();
37765
38248
  init_state_io();
37766
38249
  init_git_authorization_hook();
37767
38250
  EXIT_ALLOW3 = 0;
@@ -37773,13 +38256,52 @@ var init_block_unauthorized_git = __esm({
37773
38256
  "pr-merge",
37774
38257
  // Enabling auto-merge commits the outcome to a condition the agent does not
37775
38258
  // control, so it is blocked on the same terms as the merge it schedules.
37776
- "pr-merge-auto"
38259
+ "pr-merge-auto",
38260
+ // ── Added 2026-09-03. Every one is already never-autonomous in the
38261
+ // `non-destructive-by-default` prose; the measurement that drove it is in the
38262
+ // `GitOp` union's own note — 17 of 25 borderline operations classified as
38263
+ // NOTHING, including three worse than anything the guard already caught.
38264
+ /** The undo that is more destructive than the act: lockfiles break. */
38265
+ "unpublish",
38266
+ /** Metadata only, and every install on earth starts warning. */
38267
+ "deprecate",
38268
+ /** Replaces bytes consumers already hold a checksum for. */
38269
+ "release-asset",
38270
+ /** Deletes the guard rather than the data — everything after is unguarded. */
38271
+ "protection",
38272
+ /** No data lost; the gate that protects everything downstream is off. */
38273
+ "workflow-toggle",
38274
+ /** Reach outside the repository, and mostly one-way. */
38275
+ "repo-lifecycle",
38276
+ /** No code moves, and an armed auto-merge is released. */
38277
+ "review-approve",
38278
+ /** Discards commits that landed after the last fetch, on a shared ref. */
38279
+ "force-push",
38280
+ /** Destroys uncommitted work belonging to a session this one cannot see. */
38281
+ "worktree-remove",
38282
+ /** `-x` is the flag that takes `.env` and local certificates. */
38283
+ "clean-ignored"
37777
38284
  ]);
37778
38285
  WARN_OPS = /* @__PURE__ */ new Set([
37779
38286
  "commit",
37780
38287
  "push",
37781
38288
  "pr-create",
37782
- "branch"
38289
+ "branch",
38290
+ // ── Added 2026-09-03, deliberately at warn rather than block.
38291
+ //
38292
+ // Each is destructive in some reading, and each is also routine enough that
38293
+ // blocking it would be the friction that gets the whole guard routed around —
38294
+ // the failure mode this estate has already recorded twice. `reset-hard` is the
38295
+ // sharpest case and stays here for a decidable reason: the Hard Floor's
38296
+ // qualifier is "past unpushed work", and the command text cannot say whether
38297
+ // the work was pushed.
38298
+ "tag-force",
38299
+ "rebase",
38300
+ "reset-hard",
38301
+ "clean",
38302
+ "stash-drop",
38303
+ "branch-delete",
38304
+ "close"
37783
38305
  ]);
37784
38306
  G = "(?:-[A-Za-z-]+(?:=\\S+)?(?:\\s+\\S+)?\\s+)*";
37785
38307
  P = "^(?:\\S*\\/)?";
@@ -37819,6 +38341,72 @@ var init_block_unauthorized_git = __esm({
37819
38341
  op: "pr-create",
37820
38342
  re: new RegExp(`${P}gh\\s+pr\\s+create\\b|${ghApiWrite("\\/pulls\\b").source}`, "i")
37821
38343
  },
38344
+ // ── Added 2026-09-03. Placement inside this table is as load-bearing as the
38345
+ // patterns: the loop takes the MOST SEVERE match per segment, but within one
38346
+ // op the first pattern wins, so anything more specific than a plain `git push`
38347
+ // has to sit above it.
38348
+ // `npm unpublish` is NOT reached by the publish pattern — `\bpublish\b` needs
38349
+ // a word boundary that `unpublish` does not provide between `n` and `p`. That
38350
+ // is why it classified as nothing for as long as it did.
38351
+ { op: "unpublish", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}unpublish\\b`, "i") },
38352
+ { op: "deprecate", re: new RegExp(`${P}(npm|pnpm|yarn)\\s+${G}deprecate\\b`, "i") },
38353
+ {
38354
+ op: "release-asset",
38355
+ re: new RegExp(`${P}gh\\s+release\\s+(upload|edit|delete)\\b`, "i")
38356
+ },
38357
+ {
38358
+ op: "protection",
38359
+ re: new RegExp(
38360
+ `${P}gh\\s+api\\b(?=[^\\n]*(?:protection|rulesets?)\\b)(?=[^\\n]*(?:-X|--method)\\s+(?:DELETE|PUT|PATCH|POST)\\b)|${P}gh\\s+ruleset\\s+delete\\b`,
38361
+ "i"
38362
+ )
38363
+ },
38364
+ {
38365
+ op: "workflow-toggle",
38366
+ re: new RegExp(`${P}gh\\s+workflow\\s+(disable|enable)\\b`, "i")
38367
+ },
38368
+ {
38369
+ op: "repo-lifecycle",
38370
+ re: new RegExp(
38371
+ `${P}gh\\s+repo\\s+(archive|delete|unarchive)\\b|${P}gh\\s+repo\\s+edit\\b(?=[^\\n]*--visibility\\b)`,
38372
+ "i"
38373
+ )
38374
+ },
38375
+ {
38376
+ op: "review-approve",
38377
+ re: new RegExp(`${P}gh\\s+pr\\s+review\\b(?=[^\\n]*--approve\\b)`, "i")
38378
+ },
38379
+ // Above `push`, and both spellings: `--force` and `--force-with-lease`. The
38380
+ // "safe force" is in the same class here because what it is safe against is a
38381
+ // stale local view, not a collaborator who pushed after your fetch.
38382
+ {
38383
+ op: "force-push",
38384
+ re: new RegExp(`${P}git\\s+${G}push\\b[^\\n;|&]*(--force\\b|--force-with-lease\\b|\\s-f\\b)`, "i")
38385
+ },
38386
+ {
38387
+ op: "worktree-remove",
38388
+ re: new RegExp(`${P}git\\s+worktree\\s+remove\\b[^\\n;|&]*(--force\\b|\\s-f\\b)`, "i")
38389
+ },
38390
+ // `-x` (or `-X`) anywhere in the flag cluster is what reaches ignored files.
38391
+ {
38392
+ op: "clean-ignored",
38393
+ re: new RegExp(`${P}git\\s+clean\\b[^\\n;|&]*-[A-Za-z]*x`, "i")
38394
+ },
38395
+ { op: "tag-force", re: new RegExp(`${P}git\\s+tag\\b[^\\n;|&]*(-f\\b|--force\\b)`, "i") },
38396
+ { op: "rebase", re: new RegExp(`${P}git\\s+${G}rebase\\b`, "i") },
38397
+ { op: "reset-hard", re: new RegExp(`${P}git\\s+${G}reset\\b[^\\n;|&]*--hard\\b`, "i") },
38398
+ { op: "clean", re: new RegExp(`${P}git\\s+clean\\b`, "i") },
38399
+ { op: "stash-drop", re: new RegExp(`${P}git\\s+stash\\s+(drop|clear)\\b`, "i") },
38400
+ // Remote deletion and the local `-D` are one op: both remove a ref, and the
38401
+ // remote form additionally closes any open PR pointing at it.
38402
+ {
38403
+ op: "branch-delete",
38404
+ re: new RegExp(
38405
+ `${P}git\\s+branch\\b[^\\n;|&]*(-D\\b|--delete\\b)|${P}git\\s+${G}push\\b[^\\n;|&]*(--delete\\b|\\s:\\S)`,
38406
+ "i"
38407
+ )
38408
+ },
38409
+ { op: "close", re: new RegExp(`${P}gh\\s+(pr|issue)\\s+close\\b`, "i") },
37822
38410
  { op: "push", re: new RegExp(`${P}git\\s+${G}push\\b`, "i") },
37823
38411
  { op: "commit", re: new RegExp(`${P}git\\s+${G}commit\\b`, "i") },
37824
38412
  { op: "branch", re: new RegExp(`${P}git\\s+${G}(checkout\\s+-b|switch\\s+-c)\\b`, "i") }
@@ -42161,11 +42749,11 @@ import * as path80 from "node:path";
42161
42749
  import process21 from "node:process";
42162
42750
  import { spawnSync as spawnSync12 } from "node:child_process";
42163
42751
  import { fileURLToPath as fileURLToPath46, pathToFileURL as pathToFileURL36 } from "node:url";
42164
- function print2(s = "") {
42752
+ function print3(s = "") {
42165
42753
  process21.stdout.write(`${s}
42166
42754
  `);
42167
42755
  }
42168
- function eprint2(s = "") {
42756
+ function eprint3(s = "") {
42169
42757
  process21.stderr.write(`${s}
42170
42758
  `);
42171
42759
  }
@@ -42549,14 +43137,14 @@ function _parse4(argv) {
42549
43137
  function main36(argv = null) {
42550
43138
  const opts = _parse4(argv !== null ? Array.from(argv) : process21.argv.slice(2));
42551
43139
  if (opts.funnel) {
42552
- print2(render6(buildFunnel()));
43140
+ print3(render6(buildFunnel()));
42553
43141
  return 0;
42554
43142
  }
42555
43143
  let project_root;
42556
43144
  try {
42557
43145
  [project_root] = _resolve_project_root(opts.project);
42558
43146
  } catch (exc) {
42559
- eprint2(`\u274C conformance: ${exc.message}`);
43147
+ eprint3(`\u274C conformance: ${exc.message}`);
42560
43148
  return 2;
42561
43149
  }
42562
43150
  const manifest_pth = manifest_path(project_root);
@@ -42585,7 +43173,7 @@ function main36(argv = null) {
42585
43173
  const failed = allChecks.some((c) => c["status"] === "fail") || driftCount > 0;
42586
43174
  appendConformanceReport(conformanceChecks, project_root);
42587
43175
  if (opts.json) {
42588
- print2(
43176
+ print3(
42589
43177
  _jsonDumpsIndentAscii(
42590
43178
  {
42591
43179
  project_root,
@@ -42598,20 +43186,20 @@ function main36(argv = null) {
42598
43186
  )
42599
43187
  );
42600
43188
  } else {
42601
- print2(` \u{1F4CD} project_root: ${project_root}`);
42602
- print2("conformance:");
43189
+ print3(` \u{1F4CD} project_root: ${project_root}`);
43190
+ print3("conformance:");
42603
43191
  for (const c of allChecks) {
42604
43192
  const sym = STATUS_SYMBOLS[c["status"]] ?? "?";
42605
- print2(` ${sym} ${c["id"]}: ${c["message"]}`);
43193
+ print3(` ${sym} ${c["id"]}: ${c["message"]}`);
42606
43194
  if (c["status"] !== "ok" && c["remedy"]) {
42607
- print2(` fix: ${c["remedy"]}`);
43195
+ print3(` fix: ${c["remedy"]}`);
42608
43196
  }
42609
43197
  }
42610
43198
  if (driftCount > 0) {
42611
- print2(` \u274C drift: ${driftCount} drifted file(s) \u2014 run \`agent-config doctor\` for detail`);
43199
+ print3(` \u274C drift: ${driftCount} drifted file(s) \u2014 run \`agent-config doctor\` for detail`);
42612
43200
  }
42613
- print2("");
42614
- print2(
43201
+ print3("");
43202
+ print3(
42615
43203
  failed ? "\u274C conformance: red \u2014 the OS is not fully installed/firing here" : "\u2705 conformance: green \u2014 installed and firing"
42616
43204
  );
42617
43205
  }