@event4u/agent-config 9.27.0 → 9.28.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 (138) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +12 -11
  3. package/CHANGELOG.md +99 -0
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/analyze/conformance.md +24 -0
  6. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
  7. package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
  8. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  9. package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
  10. package/dist/agent-src/rules/context-hygiene.md +3 -1
  11. package/dist/agent-src/rules/council-availability.md +93 -0
  12. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  13. package/dist/agent-src/rules/delegation-policy.md +29 -5
  14. package/dist/agent-src/rules/design-fidelity.md +3 -0
  15. package/dist/agent-src/rules/evaluator-independence.md +2 -1
  16. package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
  17. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  18. package/dist/agent-src/rules/git-history-discipline.md +1 -1
  19. package/dist/agent-src/rules/history-discipline.md +3 -1
  20. package/dist/agent-src/rules/legal-safety-floor.md +1 -1
  21. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
  22. package/dist/agent-src/rules/onboarding-gate.md +3 -1
  23. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
  24. package/dist/agent-src/rules/role-mode-adherence.md +1 -1
  25. package/dist/agent-src/rules/scale-discipline.md +3 -1
  26. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  27. package/dist/agent-src/rules/session-canary.md +2 -1
  28. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  29. package/dist/agent-src/rules/token-budget-discipline.md +3 -1
  30. package/dist/agent-src/rules/tool-safety.md +5 -1
  31. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  32. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  33. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  34. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  35. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  36. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  37. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  38. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  39. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  40. package/dist/agent-src/templates/AGENTS.md +6 -10
  41. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  42. package/dist/cli/registry.js +7 -0
  43. package/dist/cli/registry.js.map +1 -1
  44. package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
  45. package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-2BE5I2MN.js} +8 -8
  46. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
  47. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
  48. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
  49. package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-PQSAQKHW.js} +466 -304
  50. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  51. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  52. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  53. package/dist/cli-delegate/cmd_conformance.js +7 -7
  54. package/dist/cli-delegate/cmd_converge.js +3 -3
  55. package/dist/cli-delegate/cmd_doctor.js +4 -4
  56. package/dist/cli-delegate/cmd_explain.js +80 -330
  57. package/dist/cli-delegate/cmd_export.js +5 -5
  58. package/dist/cli-delegate/cmd_fleet.js +3 -3
  59. package/dist/cli-delegate/cmd_handoff.js +2 -2
  60. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  61. package/dist/cli-delegate/cmd_migrate.js +3 -3
  62. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  63. package/dist/cli-delegate/cmd_preflight.js +1 -1
  64. package/dist/cli-delegate/cmd_prune.js +5 -5
  65. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  66. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  67. package/dist/cli-delegate/cmd_settings_get.js +272 -0
  68. package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
  69. package/dist/cli-delegate/cmd_settings_set.js +7 -2967
  70. package/dist/cli-delegate/cmd_sync.js +5 -5
  71. package/dist/cli-delegate/cmd_uninstall.js +5 -5
  72. package/dist/cli-delegate/cmd_update.js +4 -4
  73. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  74. package/dist/cli-delegate/cmd_validate.js +5 -5
  75. package/dist/cli-delegate/cmd_versions.js +4 -4
  76. package/dist/discovery/deprecation-report.md +1 -1
  77. package/dist/discovery/discovery-manifest.json +100 -40
  78. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  79. package/dist/discovery/discovery-manifest.summary.md +3 -3
  80. package/dist/discovery/orphan-report.md +1 -1
  81. package/dist/discovery/packs.json +7 -5
  82. package/dist/discovery/trust-report.md +11 -11
  83. package/dist/discovery/workspaces.json +6 -4
  84. package/dist/hooks/dispatch.js +9004 -6132
  85. package/dist/install/install.mjs +697 -507
  86. package/dist/mcp/registry-manifest.json +3 -3
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +127 -20
  89. package/dist/router.json +1 -1
  90. package/dist/scripts/_lib/agent_settings.js +80 -15
  91. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  92. package/docs/contracts/settings-classes.md +29 -8
  93. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  94. package/package.json +1 -1
  95. package/src/agent-src/templates/AGENTS.md +6 -10
  96. package/src/config/budgets.yml +29 -0
  97. package/src/config/evaluator-budgets.json +4 -4
  98. package/src/config/gate-coverage.yml +26 -0
  99. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  100. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  101. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  102. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  103. package/src/scripts/_dispatch.bash +57 -0
  104. package/src/scripts/_lib/agent_settings.ts +82 -16
  105. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  106. package/src/scripts/_lib/host_capability.ts +84 -1
  107. package/src/scripts/_lib/prompt_shape.ts +88 -0
  108. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  109. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  110. package/src/scripts/_lib/self_repair.ts +342 -0
  111. package/src/scripts/_lib/self_repair_store.ts +100 -0
  112. package/src/scripts/_lib/transcript_entry.ts +80 -0
  113. package/src/scripts/check_branch_freshness.ts +255 -0
  114. package/src/scripts/check_council_config_location.ts +125 -1
  115. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  116. package/src/scripts/config/profiles.ts +13 -3
  117. package/src/scripts/conformance_scan.ts +272 -24
  118. package/src/scripts/council_availability_hook.ts +188 -0
  119. package/src/scripts/council_cli.ts +126 -7
  120. package/src/scripts/hook_manifest.yaml +107 -26
  121. package/src/scripts/hooks/block_no_verify.ts +39 -2
  122. package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
  123. package/src/scripts/hooks/concern_registry.ts +10 -0
  124. package/src/scripts/hooks/delegation_nudge_hook.ts +450 -0
  125. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  126. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  127. package/src/scripts/install.ts +151 -0
  128. package/src/scripts/language_mirror_hook.ts +30 -37
  129. package/src/scripts/measure_scope_dedup.ts +19 -13
  130. package/src/scripts/onboarding_gate_hook.ts +40 -1
  131. package/src/scripts/orchestration_record.ts +4 -1
  132. package/src/scripts/preamble_byte_census.ts +9 -20
  133. package/src/scripts/report_carrier_divergence.ts +364 -0
  134. package/src/scripts/report_skill_activation.ts +257 -0
  135. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  136. package/src/scripts/routing_doctor.ts +77 -6
  137. package/src/scripts/self_repair_cli.ts +240 -0
  138. package/src/scripts/self_repair_hook.ts +241 -0
@@ -63,6 +63,8 @@ import { format_install_hints } from './ai_council/cli_hints.js';
63
63
  import {
64
64
  type AdvisorConfig,
65
65
  type CouncilConfig,
66
+ COUNCIL_CONFIG_ENV,
67
+ COUNCIL_CONFIG_USER_GLOBAL_REL,
66
68
  CouncilConfigError,
67
69
  load_council_config,
68
70
  resolve_api_key,
@@ -558,9 +560,22 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
558
560
 
559
561
  const ai = _isDict(settings) ? ((settings['ai_council'] as Dict) || {}) : {};
560
562
  if (!_pyBool(ai['enabled'])) {
563
+ // The file this message names is load-bearing, and it named the wrong one
564
+ // for months. Measured across 10 sessions in one consumer project: the
565
+ // agent reasoned from `.agent-settings.yml` six times, concluded no
566
+ // council existed, and substituted a weaker path — and one session
567
+ // "fixed" it by COPYING the user-global config into the project tree,
568
+ // because this message told it that was where the switch lived. An error
569
+ // from the authoritative tool is the strongest signal in the room; when
570
+ // it points at the wrong file it does not merely fail to help, it
571
+ // manufactures the wrong belief. Name the real path (ADR-104: always
572
+ // user-global, never project-local) and name the verb that reports it.
561
573
  throw new CouncilDisabledError(
562
- 'ai_council.enabled is false in .agent-settings.yml — ' +
563
- 'flip it on before invoking council:* commands.',
574
+ `enabled is false in the council config (${COUNCIL_CONFIG_USER_GLOBAL_REL} ` +
575
+ 'under the user-global agent-config root) — flip it on before invoking ' +
576
+ 'council:* commands. This config is NEVER project-local (ADR-104), so do ' +
577
+ 'not look for it, or create it, in the project tree. ' +
578
+ '`agent-config council:status` prints the resolved path.',
564
579
  );
565
580
  }
566
581
  const members_cfg = (ai['members'] as Dict) || {};
@@ -608,7 +623,8 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
608
623
  if (name in siblings) {
609
624
  throw new CouncilDisabledError(
610
625
  `--siblings targets member ${_pyReprStr(name)} but it is not ` +
611
- `enabled in .agent-settings.yml (ai_council.members.${name}.enabled).`,
626
+ `enabled in the council config (members.${name}.enabled in ` +
627
+ `${COUNCIL_CONFIG_USER_GLOBAL_REL} under the user-global root).`,
612
628
  );
613
629
  }
614
630
  continue;
@@ -714,8 +730,10 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
714
730
  );
715
731
  }
716
732
  throw new CouncilDisabledError(
717
- 'no council member has `enabled: true` — enable at least one in ' +
718
- '.agent-settings.yml under ai_council.members.*.',
733
+ 'no council member has `enabled: true` — enable at least one under ' +
734
+ `members.* in ${COUNCIL_CONFIG_USER_GLOBAL_REL} (user-global root; ` +
735
+ 'never the project tree, ADR-104). `agent-config council:status` ' +
736
+ 'prints the resolved path.',
719
737
  );
720
738
  }
721
739
  return members;
@@ -1855,6 +1873,95 @@ function _emit_shadow_slo_banner(): void {
1855
1873
  }
1856
1874
  }
1857
1875
 
1876
+ /**
1877
+ * `council:status` — answer "is a council configured, and from where" without
1878
+ * spending anything, so no caller has to infer it from a filename.
1879
+ *
1880
+ * WHY THIS EXISTS. Measured 2026-08-08: an agent working in a consumer project
1881
+ * announced "Kein Council konfiguriert (keine `.agent-settings.yml`)" and
1882
+ * substituted a weaker subagent fan-out. The council was configured the whole
1883
+ * time, user-globally. The inference was wrong twice over — the council config
1884
+ * has not lived in `.agent-settings.yml` since the Phase-0 migration, and per
1885
+ * ADR-104 the project tree is **never** searched, so the presence or absence of
1886
+ * any project file says nothing at all about council availability.
1887
+ *
1888
+ * The guidance was not missing: `/council default` states this emphatically in
1889
+ * four places. But a command file only loads when the command is invoked, and
1890
+ * the agent was deciding *whether a capability existed* — a question it reached
1891
+ * before invoking anything. The always-loaded layer says "when the council is
1892
+ * enabled" and never says how to find out.
1893
+ *
1894
+ * So the fix is not more prose. It is a check that costs nothing to run and
1895
+ * leaves nothing to infer. Exit is always 0 — this reports a state, it does not
1896
+ * gate — and the verdict line is machine-greppable in both directions.
1897
+ */
1898
+ export function cmd_status(args: Args, opts: { env?: Record<string, string | undefined> } = {}): number {
1899
+ const env = opts.env ?? process.env;
1900
+ const override = env[COUNCIL_CONFIG_ENV];
1901
+ const path_ = resolve_config_path(REPO_ROOT, { env: env as never });
1902
+ const exists = fs.existsSync(String(path_));
1903
+ const provenance = override ? `${COUNCIL_CONFIG_ENV} override` : 'user-global';
1904
+
1905
+ let cfg: CouncilConfig | null = null;
1906
+ let parse_error: string | null = null;
1907
+ if (exists) {
1908
+ try {
1909
+ cfg = load_council_config(path_);
1910
+ } catch (exc) {
1911
+ parse_error = exc instanceof Error ? exc.message : String(exc);
1912
+ }
1913
+ }
1914
+
1915
+ const enabledMembers = cfg === null ? [] : [...cfg.members.entries()].filter(([, m]) => m.enabled);
1916
+ // "Configured" deliberately means: a readable file whose `enabled` is true and
1917
+ // which has at least one enabled member. A file that parses but enables
1918
+ // nothing is the case a caller most needs told apart from a missing file,
1919
+ // because only one of the two is fixed by writing a config.
1920
+ const configured = cfg !== null && cfg.enabled && enabledMembers.length > 0;
1921
+
1922
+ if (_getattr(args, 'json', false)) {
1923
+ _stdout(
1924
+ `${JSON.stringify({
1925
+ configured,
1926
+ path: String(path_),
1927
+ exists,
1928
+ provenance,
1929
+ enabled: cfg?.enabled ?? null,
1930
+ members_total: cfg === null ? null : cfg.members.size,
1931
+ members_enabled: cfg === null ? null : enabledMembers.length,
1932
+ member_names: enabledMembers.map(([n]) => n),
1933
+ parse_error,
1934
+ project_tree_consulted: false,
1935
+ })}\n`,
1936
+ );
1937
+ return 0;
1938
+ }
1939
+
1940
+ _stdout(`council:status · ${configured ? 'CONFIGURED' : 'NOT CONFIGURED'}\n`);
1941
+ _stdout(` config path ${String(path_)}\n`);
1942
+ _stdout(` resolved by ${provenance}\n`);
1943
+ _stdout(` file exists ${exists ? 'yes' : 'no'}\n`);
1944
+ if (parse_error !== null) {
1945
+ _stdout(` parse error ${parse_error}\n`);
1946
+ }
1947
+ if (cfg !== null) {
1948
+ _stdout(` enabled ${cfg.enabled ? 'yes' : 'no'}\n`);
1949
+ _stdout(` members ${enabledMembers.length} enabled of ${cfg.members.size}\n`);
1950
+ if (enabledMembers.length > 0) {
1951
+ _stdout(` ${enabledMembers.map(([n]) => n).join(', ')}\n`);
1952
+ }
1953
+ }
1954
+ _stdout('\n');
1955
+ _stdout(' The project tree is NEVER consulted (ADR-104). No project file —\n');
1956
+ _stdout(' `.agent-settings.yml` included — indicates council availability in\n');
1957
+ _stdout(' either direction. Do not infer it from one; run this instead.\n');
1958
+ if (!configured) {
1959
+ _stdout(`\n To configure: write ${COUNCIL_CONFIG_USER_GLOBAL_REL} under the user-global\n`);
1960
+ _stdout(' agent-config root, with `enabled: true` and at least one enabled member.\n');
1961
+ }
1962
+ return 0;
1963
+ }
1964
+
1858
1965
  function _apply_solo_dispatch(members: ExternalAIClient[]): [ExternalAIClient[], string | null] {
1859
1966
  let cfg: CouncilConfig;
1860
1967
  try {
@@ -2826,13 +2933,15 @@ interface Args {
2826
2933
  // shadow-report
2827
2934
  log: string | null;
2828
2935
  window_days: number;
2936
+ // status
2937
+ json: boolean;
2829
2938
  }
2830
2939
 
2831
- const _SUBCOMMANDS = ['estimate', 'run', 'debate', 'render', 'replay', 'quota', 'shadow-report'];
2940
+ const _SUBCOMMANDS = ['estimate', 'run', 'debate', 'render', 'replay', 'quota', 'shadow-report', 'status'];
2832
2941
 
2833
2942
  const _TOP_USAGE =
2834
2943
  `usage: ${_PROG} [-h]\n` +
2835
- ` {estimate,run,debate,render,replay,quota,shadow-report}\n` +
2944
+ ` {estimate,run,debate,render,replay,quota,shadow-report,status}\n` +
2836
2945
  ` ...\n`;
2837
2946
 
2838
2947
  function _topError(message: string): never {
@@ -2886,6 +2995,7 @@ function _defaultArgs(): Args {
2886
2995
  reset: null,
2887
2996
  log: null,
2888
2997
  window_days: 7,
2998
+ json: false,
2889
2999
  };
2890
3000
  }
2891
3001
 
@@ -3009,6 +3119,12 @@ function _specsFor(cmd: string): { positionals: string[]; opts: OptSpec[]; requi
3009
3119
  ],
3010
3120
  requiredOpts: [],
3011
3121
  };
3122
+ case 'status':
3123
+ return {
3124
+ positionals: [],
3125
+ opts: [{ flag: '--json', takesValue: false, apply: (o) => (o.json = true) }],
3126
+ requiredOpts: [],
3127
+ };
3012
3128
  default:
3013
3129
  return { positionals: [], opts: [], requiredOpts: [] };
3014
3130
  }
@@ -3213,6 +3329,9 @@ function main(argv: string[] | null = null): number {
3213
3329
  if (args.cmd === 'shadow-report') {
3214
3330
  return cmd_shadow_report(args);
3215
3331
  }
3332
+ if (args.cmd === 'status') {
3333
+ return cmd_status(args);
3334
+ }
3216
3335
  } catch (exc) {
3217
3336
  if (exc instanceof CouncilDisabledError) {
3218
3337
  _stderr(`❌ council:${args.cmd}: ${exc.message}\n`);
@@ -319,6 +319,29 @@ concerns:
319
319
  args: []
320
320
  fail_closed: false
321
321
  severity: advisory
322
+ # Council-availability fact. Carries whether a council is configured and from
323
+ # where, because the always-loaded layer refers to the council conditionally
324
+ # and never says how to check. Measured 2026-08-08: an agent announced "kein
325
+ # Council konfiguriert (keine .agent-settings.yml)" and substituted a weaker
326
+ # path; the council was configured user-globally the whole time, and the
327
+ # project tree is never searched for it (ADR-104). Advisory: supplies a fact,
328
+ # never blocks.
329
+ council-availability:
330
+ script: src/scripts/council_availability_hook.ts
331
+ args: []
332
+ fail_closed: false
333
+ severity: advisory
334
+ # Self-repair intake (road-to-self-repair Phase 1). Two slots, one queue:
335
+ # user_prompt_submit opens a record when the user states the agent worked
336
+ # wrongly and injects the open-queue line; stop runs the deterministic
337
+ # detectors over the finished turn and records silently (a reminder fired
338
+ # after the reply cannot shape it). ADVISORY by construction — it records,
339
+ # it never gates; the outward step is a separate, user-run CLI command.
340
+ self-repair:
341
+ script: src/scripts/self_repair_hook.ts
342
+ args: []
343
+ fail_closed: false
344
+ severity: advisory
322
345
  # Shared per-session register (road-to-parallel-session-coordination) — writes
323
346
  # this session into <git-common-dir>/agent-sessions/<session_id>.json so every
324
347
  # other worktree of the repo can see it, and on session_start injects the live
@@ -351,22 +374,70 @@ concerns:
351
374
  args: []
352
375
  fail_closed: false
353
376
  severity: advisory
377
+ # road-to-orchestrator-discipline-carriers Phase 3 (F6) — deterministic
378
+ # PostToolUse orchestration-telemetry capture. Fires only on an Agent/Task
379
+ # tool-use completion (the two observed subagent-dispatch tool names) and
380
+ # appends one `orchestration_record` line — no model step, replacing the
381
+ # measured 0.27% model-carried capture rate for future dispatches. Silent
382
+ # on every path (never writes to stdout); exit 0 always, including a
383
+ # malformed envelope or a write failure. fail_closed: false.
384
+ orchestration-record:
385
+ script: src/scripts/hooks/orchestration_record_hook.ts
386
+ args: []
387
+ fail_closed: false
388
+ severity: advisory
389
+ tools: [Agent, Task]
390
+ # road-to-orchestrator-discipline-carriers Phase 4 (F3-lite) — conditional
391
+ # delegation nudge. Extracts cheap structural signals from the SUBMITTED
392
+ # prompt (enumerated file lists, "for each"/"alle …" shapes, explicit slice
393
+ # counts, ordered-plan markers, multi-deliverable conjunctions), classifies
394
+ # via `classifyTask`, and — only on a positive do-in-parallel/do-in-steps
395
+ # verdict — reports `{decision:"warn", reason, additional_context}` at exit
396
+ # 2, mirroring `language-mirror`'s shipped delivery pattern so the verdict
397
+ # line actually reaches `hookSpecificOutput.additionalContext` on the
398
+ # verified `claude` platform. Silence is the anti-canary default: no
399
+ # enumerated signal → no injection at all. Never blocks the turn — see the
400
+ # hook's file header for the `host_semantics.emitFor` proof. fail_closed:
401
+ # false.
402
+ delegation-nudge:
403
+ script: src/scripts/hooks/delegation_nudge_hook.ts
404
+ args: []
405
+ fail_closed: false
406
+ severity: advisory
407
+ # road-to-orchestrator-discipline-carriers Phase 5 (F4-lite) — end-review
408
+ # nudge. Fires ONLY when the session mutated tracked+untracked, non-doc
409
+ # files beyond MUTATION_LINE_THRESHOLD (50 lines; untracked files added
410
+ # post-review, F6) AND no reviewer ran this session (scanned from the
411
+ # Stop payload's `transcript_path`) AND this session has not already
412
+ # fired once (F2 — a once-per-session gate; state under
413
+ # agents/runtime/state/end-review-nudge/, gitignored). On fire, reports
414
+ # `{decision:"warn", reason, additional_context}` at exit 2 (same delivery
415
+ # pattern as `language-mirror`/`delegation-nudge` above) and appends one
416
+ # `review_skipped` telemetry line — hook-carried, not model-carried — so a
417
+ # future blocking decision has a measured distribution to cite. Never
418
+ # blocks the turn — see the hook's file header for the `host_semantics.
419
+ # emitFor` proof. fail_closed: false.
420
+ end-review-nudge:
421
+ script: src/scripts/hooks/end_review_nudge_hook.ts
422
+ args: []
423
+ fail_closed: false
424
+ severity: advisory
354
425
 
355
426
  platforms:
356
427
  augment:
357
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
428
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
358
429
  session_end: [chat-history, memory-learn, session-register]
359
- stop: [chat-history, hot-context, verify-before-complete, session-register]
430
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
360
431
  pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
361
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
432
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
362
433
 
363
434
  claude:
364
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
435
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
365
436
  session_end: [chat-history, memory-learn, session-register]
366
- stop: [chat-history, hot-context, verify-before-complete, team-review-gate, session-register]
367
- user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, session-register]
437
+ stop: [chat-history, hot-context, verify-before-complete, team-review-gate, end-review-nudge, self-repair, session-register]
438
+ user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, delegation-nudge, git-authorization, session-canary, self-repair, session-register]
368
439
  pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
369
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
440
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
370
441
 
371
442
  # Cowork — the Claude desktop app's local-agent-mode runtime, built
372
443
  # on top of the Claude Code CLI. Same lifecycle vocabulary, same
@@ -383,12 +454,22 @@ platforms:
383
454
  # Decision matrix + upstream blockers tracked in
384
455
  # agents/settings/contexts/chat-history-platform-hooks.md § Cowork.
385
456
  cowork:
386
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
457
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
387
458
  session_end: [chat-history, memory-learn, session-register]
388
- stop: [chat-history, hot-context, verify-before-complete, session-register]
389
- user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, session-register]
459
+ # end-review-nudge and delegation-nudge are DELIBERATELY NOT bound on
460
+ # this platform (F10, review) — cowork-dispatcher.sh's own header states
461
+ # it "always exit[s] 0" and its dispatch line discards the dispatcher's
462
+ # exit code AND stdout unconditionally (`>/dev/null 2>&1 || true`). Both
463
+ # concerns' delivery depends entirely on `additional_context` reaching
464
+ # the model via stdout at the verified exit-2 warn path — a delivery this
465
+ # trampoline makes structurally impossible regardless of which platform
466
+ # is later verified to forward it. Binding them here would be dead
467
+ # weight: the concern still runs (git diff, transcript scan, classifier),
468
+ # still costs the dispatch, and its entire output is thrown away.
469
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
470
+ user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
390
471
  pre_tool_use: [block-no-verify, block-unauthorized-git, evidence-independence, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
391
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
472
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
392
473
 
393
474
  # Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
394
475
  # IDE and CLI; `~/.cursor/hooks.json` (user) is opt-in via
@@ -398,11 +479,11 @@ platforms:
398
479
  # IDE-only — CLI-only users fall back to /checkpoint per
399
480
  # agents/settings/contexts/chat-history-platform-hooks.md.
400
481
  cursor:
401
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
482
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
402
483
  session_end: [chat-history, memory-learn, session-register]
403
- stop: [chat-history, hot-context, verify-before-complete, session-register]
404
- user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, session-register]
405
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
484
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
485
+ user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
486
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
406
487
 
407
488
  # Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
408
489
  # (project, no file extension, must be executable per Cline docs) or
@@ -413,11 +494,11 @@ platforms:
413
494
  # both map to session_start. TaskCancel maps to stop because the
414
495
  # session is interrupted with partial state (mirrors Augment Stop).
415
496
  cline:
416
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
497
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
417
498
  session_end: [chat-history, memory-learn, session-register]
418
- stop: [chat-history, hot-context, verify-before-complete, session-register]
419
- user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, session-register]
420
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
499
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
500
+ user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
501
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
421
502
 
422
503
  # Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
423
504
  # (project) or `~/.codeium/windsurf/hooks.json` (user). Cascade has
@@ -432,11 +513,11 @@ platforms:
432
513
  # surface to record verification commands; documented limitation).
433
514
  # minimal-safe-diff is omitted entirely on Windsurf for the same reason.
434
515
  windsurf:
435
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, profile-staleness, wrapper-freshness, surface-probe, session-register]
436
- stop: [chat-history, hot-context, verify-before-complete, session-register]
516
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, profile-staleness, wrapper-freshness, surface-probe, session-register]
517
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
437
518
  # language-mirror IS bound here: unlike minimal-safe-diff it needs only the
438
519
  # user_prompt_submit slot, which Windsurf has.
439
- user_prompt_submit: [chat-history, verify-before-complete, language-mirror, git-authorization, session-canary, session-register]
520
+ user_prompt_submit: [chat-history, verify-before-complete, language-mirror, git-authorization, session-canary, self-repair, session-register]
440
521
 
441
522
  # Phase 7.8 — Gemini CLI. Hooks live at `.gemini/settings.json`
442
523
  # (project) or `~/.gemini/settings.json` (user). Per Gemini docs
@@ -449,11 +530,11 @@ platforms:
449
530
  # turn-check semantics. AfterAgent fires when the agent loop ends
450
531
  # — this is our `stop` slot.
451
532
  gemini:
452
- session_start: [chat-history, hot-context, handoff-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
533
+ session_start: [chat-history, hot-context, handoff-context, session-canary, council-availability, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe, session-register]
453
534
  session_end: [chat-history, memory-learn, session-register]
454
- stop: [chat-history, hot-context, verify-before-complete, session-register]
455
- user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, session-register]
456
- post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
535
+ stop: [chat-history, hot-context, verify-before-complete, self-repair, session-register]
536
+ user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff, language-mirror, git-authorization, session-canary, self-repair, session-register]
537
+ post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder, orchestration-record]
457
538
 
458
539
  # Phase 7.9 — Copilot has no hook surface. Concerns route through
459
540
  # rule-only fallback; the dispatcher silently no-ops on --platform copilot.
@@ -20,12 +20,21 @@
20
20
  * 2 — warn (not used by this guard)
21
21
  *
22
22
  * No ALLOW_NO_VERIFY-style env bypass is provided. See src/rules/git-history-discipline.md
23
+ *
24
+ * WHAT THIS GUARD DOES NOT SEE. Command substitutions ARE now checked — see
25
+ * `_check_command` for why they were not, and why the well-formed case was the
26
+ * unprotected one. What remains uncovered is an invocation whose command word
27
+ * only exists at runtime (`P=commit; git $P --no-verify` via indirection, or a
28
+ * word composed by `xargs`). Those execute under bash and are not classified
29
+ * here. Stated rather than left implied: this file's own round-5 defect was a
30
+ * docstring that described coverage it did not have.
23
31
  */
24
32
 
25
33
  import * as fs from 'node:fs';
26
34
  import { fileURLToPath, pathToFileURL } from 'node:url';
27
35
  import * as path from 'node:path';
28
36
  import { readHookStdin } from './hook_stdin.js';
37
+ import { substitutionPayloads } from './block_unauthorized_git.js';
29
38
 
30
39
  const _HERE = fileURLToPath(import.meta.url);
31
40
 
@@ -384,8 +393,36 @@ export function _looks_like_git_invocation(cmd: string, depth = 0): boolean {
384
393
  return false;
385
394
  }
386
395
 
387
- /** Return (blocked, reason). Fail-closed on parse error for git commands. */
388
- function _check_command(cmd: string): [boolean, string] {
396
+ /**
397
+ * Return (blocked, reason). Fail-closed on parse error for git commands.
398
+ *
399
+ * COMMAND SUBSTITUTIONS ARE CHECKED SEPARATELY, and the reason they were not is
400
+ * the shape of the hole: this file does split on `$(` and backtick, but only
401
+ * inside `_looks_like_git_invocation`, which runs exclusively in the
402
+ * shlex-failure branch. A well-formed `echo "$(git commit --no-verify)"` parses
403
+ * cleanly, so shlex succeeds, the substitution stays inside one token, and
404
+ * `_git_base` never sees git. The more parseable the command, the less it was
405
+ * protected. Classification inside the payload is by command position, so a
406
+ * substitution that INVOKES git is checked and one that merely names it in an
407
+ * argument is not — matching what `block_unauthorized_git` does one level up.
408
+ *
409
+ * Heredoc bodies are removed before extraction: a backtick or `$(` inside a
410
+ * commit message being written to a file is data, and feeding it back through
411
+ * this function is exactly the false positive the round-5 repair closed.
412
+ */
413
+ function _check_command(cmd: string, depth = 0): [boolean, string] {
414
+ if (depth <= 3) {
415
+ const withoutHeredocs = cmd.replace(
416
+ /<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1[\s\S]*?^\s*\2\s*$/gm,
417
+ '<<HEREDOC',
418
+ );
419
+ for (const payload of substitutionPayloads(withoutHeredocs)) {
420
+ const [blocked, reason] = _check_command(payload, depth + 1);
421
+ if (blocked) {
422
+ return [true, reason];
423
+ }
424
+ }
425
+ }
389
426
  let tokens: string[];
390
427
  try {
391
428
  tokens = shlexSplit(cmd);