@event4u/agent-config 16.1.0 → 16.2.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 (217) 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 +237 -248
  5. package/dist/agent-src/commands/roadmap/process-full.md +1 -1
  6. package/dist/agent-src/commands/work.md +4 -0
  7. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  8. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  9. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  10. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  11. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  12. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  13. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
  14. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  15. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  16. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  17. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  18. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  19. package/dist/agent-src/templates/roadmaps.md +30 -0
  20. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  21. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  22. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  23. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  24. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  25. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  26. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  27. package/dist/cli/python/knowledge_ingest.js +1 -1
  28. package/dist/cli/python/workspace_drive.js +1 -1
  29. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  30. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
  31. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
  32. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
  33. package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
  34. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  35. package/dist/cli-delegate/cmd_conformance.js +902 -782
  36. package/dist/cli-delegate/cmd_doctor.js +2 -2
  37. package/dist/cli-delegate/cmd_export.js +2 -2
  38. package/dist/cli-delegate/cmd_handoff.js +4 -4
  39. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  40. package/dist/cli-delegate/cmd_prune.js +2 -2
  41. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  42. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  43. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  44. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  45. package/dist/cli-delegate/cmd_sync.js +2 -2
  46. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  47. package/dist/cli-delegate/cmd_validate.js +2 -2
  48. package/dist/discovery/deprecation-report.md +1 -1
  49. package/dist/discovery/discovery-manifest.json +10 -10
  50. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  51. package/dist/discovery/discovery-manifest.summary.md +1 -1
  52. package/dist/discovery/dormancy-report.md +1 -1
  53. package/dist/discovery/orphan-report.md +1 -1
  54. package/dist/discovery/packs.json +2 -2
  55. package/dist/discovery/trust-report.md +1 -1
  56. package/dist/discovery/workspaces.json +2 -2
  57. package/dist/hooks/dispatch.js +6666 -2581
  58. package/dist/hooks/dispatch.sha256 +1 -0
  59. package/dist/install/install.mjs +848 -733
  60. package/dist/mcp/registry-manifest.json +2 -2
  61. package/dist/mcp/server.json +2 -2
  62. package/dist/mcp/server.mjs +2747 -1510
  63. package/dist/server/schemas/settings.js +23 -0
  64. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  65. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  66. package/dist/ui/index.html +2 -2
  67. package/docs/contracts/settings-classes.md +6 -4
  68. package/package.json +4 -4
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  73. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  74. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  75. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  76. package/src/config/agent-settings.template.yml +29 -0
  77. package/src/config/ci-local-parity.yml +18 -0
  78. package/src/config/continuity-surface.json +2 -2
  79. package/src/config/gate-coverage.yml +234 -3
  80. package/src/config/gate-violation-baselines.json +7 -3
  81. package/src/config/hook-latency-budget.json +21 -0
  82. package/src/config/hook-token-budget.json +2 -2
  83. package/src/config/metric-registry.yml +114 -0
  84. package/src/config/placeholder-drift-budget.json +10 -0
  85. package/src/config/routing-coverage-seed.json +18 -4
  86. package/src/scripts/_cli/cmd_doctor.ts +5 -19
  87. package/src/scripts/_cli/doctor_execution.ts +100 -17
  88. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  89. package/src/scripts/_lib/body_portable.ts +102 -0
  90. package/src/scripts/_lib/cc_transcript.ts +132 -1
  91. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  92. package/src/scripts/_lib/forge_protection.ts +114 -0
  93. package/src/scripts/_lib/forge_reader.ts +558 -0
  94. package/src/scripts/_lib/host_env_write.ts +111 -0
  95. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  96. package/src/scripts/_lib/obligations.ts +119 -8
  97. package/src/scripts/_lib/probe_inputs.ts +271 -0
  98. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  99. package/src/scripts/_lib/release_highlights.ts +2 -1
  100. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  101. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  102. package/src/scripts/_lib/structural_hiding.ts +622 -0
  103. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  104. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  105. package/src/scripts/_lib/verification_command.ts +324 -0
  106. package/src/scripts/_lib/verification_evidence.ts +647 -0
  107. package/src/scripts/_lib/verify_clause.ts +280 -0
  108. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  109. package/src/scripts/before_complete_hook.ts +340 -23
  110. package/src/scripts/bench_hook_latency.ts +412 -5
  111. package/src/scripts/cache_realization_report.ts +61 -1
  112. package/src/scripts/check_argument_hint.ts +287 -0
  113. package/src/scripts/check_claims.ts +125 -1
  114. package/src/scripts/check_enforcement_coverage.ts +38 -9
  115. package/src/scripts/check_enforcement_matrix.ts +245 -3
  116. package/src/scripts/check_finding_dispositions.ts +22 -3
  117. package/src/scripts/check_generator_sync.ts +71 -0
  118. package/src/scripts/check_host_docs_digest.ts +786 -0
  119. package/src/scripts/check_host_format_column.ts +559 -0
  120. package/src/scripts/check_input_references.ts +228 -0
  121. package/src/scripts/check_inputs_parity.ts +235 -0
  122. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  123. package/src/scripts/check_kill_switch_table.ts +294 -0
  124. package/src/scripts/check_placeholder_drift.ts +221 -0
  125. package/src/scripts/check_read_surface_coverage.ts +515 -0
  126. package/src/scripts/check_release_holds.ts +2 -1
  127. package/src/scripts/check_routing_coverage.ts +378 -11
  128. package/src/scripts/check_static_parity.ts +17 -0
  129. package/src/scripts/ci_settle.ts +145 -19
  130. package/src/scripts/closure_scan.ts +115 -1
  131. package/src/scripts/compile_hook_manifest.ts +60 -16
  132. package/src/scripts/external_sources_denylist.json +29 -4
  133. package/src/scripts/hook_manifest.json +1 -1
  134. package/src/scripts/hook_manifest.yaml +42 -5
  135. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  136. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  137. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  138. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  139. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  140. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  141. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  142. package/src/scripts/hooks/concern_registry.ts +2 -0
  143. package/src/scripts/hooks/concern_timings.ts +94 -0
  144. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  145. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  146. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  147. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  148. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  149. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  150. package/src/scripts/hooks/evidence_independence.ts +1 -2
  151. package/src/scripts/hooks/exit_codes.ts +128 -0
  152. package/src/scripts/hooks/host_lowering.json +1 -0
  153. package/src/scripts/hooks/host_lowering.ts +128 -3
  154. package/src/scripts/hooks/host_lowering.yaml +262 -46
  155. package/src/scripts/hooks/host_semantics.ts +30 -2
  156. package/src/scripts/hooks/injection_budget.ts +9 -2
  157. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  158. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  159. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  160. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  161. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  162. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  163. package/src/scripts/hooks/permission_gate.ts +1 -2
  164. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  165. package/src/scripts/hooks/replay_hook.ts +2 -1
  166. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  167. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  168. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  169. package/src/scripts/hooks/rule_inject_hook.ts +8 -5
  170. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  171. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  172. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  173. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  174. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  175. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  176. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  177. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  178. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  179. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  180. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  181. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  182. package/src/scripts/install.ts +3 -4
  183. package/src/scripts/lint_deny_text.ts +419 -0
  184. package/src/scripts/lint_exit_codes.ts +262 -0
  185. package/src/scripts/lint_hook_manifest.ts +55 -0
  186. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  187. package/src/scripts/lint_skill_descriptions.ts +1 -1
  188. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  189. package/src/scripts/mcp_server/prompts.ts +142 -1
  190. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  191. package/src/scripts/measure_turn_end_gate.ts +193 -0
  192. package/src/scripts/memory_lookup.ts +1 -1
  193. package/src/scripts/prepack-check.mjs +53 -0
  194. package/src/scripts/release_publication.ts +13 -1
  195. package/src/scripts/report_invocation_surface.ts +383 -0
  196. package/src/scripts/report_obligation_carriers.ts +699 -0
  197. package/src/scripts/report_obligation_writer_split.ts +236 -0
  198. package/src/scripts/report_skill_menu_census.ts +208 -0
  199. package/src/scripts/report_standing_payload_by_host.ts +1 -1
  200. package/src/scripts/roadmap_verify_share.ts +213 -0
  201. package/src/scripts/schemas/command.schema.json +44 -0
  202. package/src/scripts/schemas/review-findings.schema.json +4 -0
  203. package/src/scripts/schemas/skill.schema.json +44 -0
  204. package/src/scripts/self_review_gate.ts +359 -10
  205. package/src/scripts/semantic_parity_census.ts +314 -0
  206. package/src/scripts/session_eol_report.ts +54 -1
  207. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  208. package/src/scripts/trigger_eval_grandfather.json +0 -2
  209. package/src/scripts/trigger_eval_rotation.ts +122 -27
  210. package/src/scripts/ui_conformance_probe.ts +156 -4
  211. package/src/scripts/update_prices.ts +85 -3
  212. package/src/scripts/write_bundle_digest.mjs +74 -0
  213. package/src/server/schemas/settings.ts +31 -0
  214. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  215. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  216. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  217. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -0,0 +1,94 @@
1
+ import fs from "node:fs";
2
+
3
+ /**
4
+ * concern_timings — the per-concern latency sink the bench reads.
5
+ *
6
+ * Step 3.2 of `road-to-a-kernel-that-guards-its-plumbing`. The dispatcher has
7
+ * always timed each concern — `_run_concern` returns a duration and the
8
+ * feedback record carries it — but nothing could READ that distribution: the
9
+ * feedback dir keeps one file per concern and every dispatch OVERWRITES it, so
10
+ * the only survivor is the last run. A p95 needs every sample, and the bench
11
+ * harness that produces the samples runs with `AGENT_CONFIG_REPLAY=1`, where
12
+ * the feedback write returns before writing anything at all.
13
+ *
14
+ * So this sink is separate from the feedback dir on purpose, and deliberately
15
+ * not derived from it:
16
+ *
17
+ * - it APPENDS, so N runs leave N samples rather than one;
18
+ * - it is written in replay mode, because the measurement harness is the
19
+ * caller that needs it;
20
+ * - it is off unless the variable is set, so the cost on a real dispatch is
21
+ * one `process.env` lookup.
22
+ *
23
+ * It carries a concern name, an integer, an event and a platform. There is no
24
+ * field that can hold a payload, a path or a prompt — the same shape-level
25
+ * privacy floor `payload_stubs` keeps one row up.
26
+ *
27
+ * It lives in its own file rather than inside `dispatch_hook.ts` because that
28
+ * file sits against the 1,500-line source budget, and `check_source_size_budget`
29
+ * refused the inline version.
30
+ */
31
+
32
+ /** Environment variable naming the JSONL sink. Registered in the kill-switch table. */
33
+ export const TIMINGS_SINK_ENV = "AGENT_CONFIG_HOOK_TIMINGS";
34
+
35
+ /**
36
+ * One measurement.
37
+ *
38
+ * MICROseconds, not milliseconds. The dispatcher's `duration_ms` is floored,
39
+ * and since the in-process fast path landed every registry concern finishes in
40
+ * well under a millisecond — so the floored field reads exactly `0` for all of
41
+ * them. That was invisible while nothing consumed the number and stops being
42
+ * invisible the moment a p95 is reported and a timeout derived from it.
43
+ */
44
+ export interface ConcernTimingSample {
45
+ concern: string;
46
+ duration_us: number;
47
+ }
48
+
49
+ /**
50
+ * Append one JSONL row per concern, when the sink is armed.
51
+ *
52
+ * Never throws and never blocks the dispatch: a measurement sink that can fail
53
+ * a hook would be a new way for the plumbing to refuse, which is the opposite
54
+ * of what this roadmap is for.
55
+ */
56
+ export function writeConcernTimings(
57
+ event: string,
58
+ platform: string,
59
+ samples: readonly ConcernTimingSample[],
60
+ sinkPath: string | undefined = process.env[TIMINGS_SINK_ENV],
61
+ ): void {
62
+ if (!sinkPath) {
63
+ return;
64
+ }
65
+ try {
66
+ const lines: string[] = [];
67
+ for (const sample of samples) {
68
+ // A concern the dispatcher never timed is OMITTED rather than recorded
69
+ // as 0. A zero here would read as "instantaneous" and would be
70
+ // indistinguishable from a real sub-millisecond run — the exact
71
+ // substitution the report's `not_measured` requirement forbids one layer
72
+ // up, reintroduced at the source instead of at the report.
73
+ if (!Number.isFinite(sample.duration_us)) {
74
+ continue;
75
+ }
76
+ lines.push(
77
+ JSON.stringify({
78
+ concern: sample.concern,
79
+ // Rounded HERE rather than at the call site: the caller holds a
80
+ // float millisecond reading and the sink is the one place that has
81
+ // to decide the recorded unit.
82
+ duration_us: Math.round(sample.duration_us),
83
+ event,
84
+ platform,
85
+ }),
86
+ );
87
+ }
88
+ if (lines.length > 0) {
89
+ fs.appendFileSync(sinkPath, lines.join("\n") + "\n", "utf-8");
90
+ }
91
+ } catch {
92
+ // Non-fatal by construction — see the header.
93
+ }
94
+ }
@@ -102,15 +102,14 @@ import { classifyLadder, type LadderRung } from "../_lib/judgment_ladder.js";
102
102
  import { resolveSessionRole } from "../_lib/session_role.js";
103
103
  import { resolveSubagentRouting, type Tier } from "../_lib/subagent_routing.js";
104
104
  import { isSyntheticPrompt } from "../_lib/prompt_shape.js";
105
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
105
106
 
106
- const EXIT_ALLOW = 0;
107
107
  // Severity is taken from the EXIT CODE, not from the `decision` field in the
108
108
  // stdout payload — mirrors `language_mirror_hook.ts`'s own note (found the
109
109
  // same way: by tracing delivery, not by re-reading the unit tests). A warn
110
110
  // verdict is reported at exit 2 so `host_semantics.emitFor` reduces it to
111
111
  // severity `"warn"` and forwards `additional_context`; see the file header
112
112
  // for why this exit code never actually blocks the turn.
113
- const EXIT_WARN = 2;
114
113
 
115
114
  type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
116
115
  type JsonObject = { [k: string]: JsonValue };
@@ -57,12 +57,13 @@ import path from 'node:path';
57
57
  import { fileURLToPath, pathToFileURL } from 'node:url';
58
58
 
59
59
  import { appendDischarge, stamp } from '../_lib/obligations.js';
60
+ import { compareInputs } from '../_lib/probe_inputs.js';
61
+ import { trigger_matches, type Trigger } from '../_lib/router_match.js';
60
62
  import { isUiPath, isUiTreePath } from '../_lib/ui_surface.js';
61
63
  import { loadDesignContext, scanFile } from '../lint_design_slop.js';
62
64
  import { readHookStdin } from './hook_stdin.js';
65
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
63
66
 
64
- const EXIT_ALLOW = 0;
65
- const EXIT_WARN = 2;
66
67
  const SETTINGS_FILE = '.agent-settings.yml';
67
68
  const STATE_REL = path.join('agents', 'runtime', 'state', 'design-pass-hook.json');
68
69
  const AUDIT_REL = path.join('agents', 'runtime', 'state', 'ui-audit.json');
@@ -150,27 +151,94 @@ export interface ConformanceVerdict {
150
151
  * write is the noise that gets a carrier switched off — Risk 1 of the parent
151
152
  * roadmap. Absent is still reported honestly whenever the pass speaks for
152
153
  * another reason.
154
+ *
155
+ * The ONE exception is a turn that actually touched a provided design
156
+ * handover. There, the probe is the evidence the turn was supposed to
157
+ * produce, and its absence is the noteworthy kind.
153
158
  */
154
159
  noteworthy: boolean;
155
160
  line: string;
156
161
  }
157
162
 
158
- export function conformanceVerdict(root: string): ConformanceVerdict {
163
+ export interface ConformanceOptions {
164
+ /**
165
+ * Whether a provided design handover was among the files this turn wrote.
166
+ * Decided by `handoverTouched` from the shipped routing triggers, never by a
167
+ * matcher invented here.
168
+ */
169
+ handoverDetected?: boolean;
170
+ }
171
+
172
+ /**
173
+ * The file-based half of `src/rules/design-fidelity.md`'s `triggers:` set — the
174
+ * shipped answer to "is this file a provided design handover?".
175
+ *
176
+ * COPIED AS A CONSTANT, MATCHED BY THE SHIPPED MATCHER. Reading and parsing the
177
+ * rule's frontmatter here would put a YAML load on `post_tool_use`, the hottest
178
+ * slot this concern binds, and would depend on a rule path that differs between
179
+ * this repository and an installed consumer. The drift that a constant invites
180
+ * is closed by a test instead: `design_pass.test.ts` asserts this list equals the
181
+ * rule's file-based triggers in BOTH directions, so a trigger added to the rule
182
+ * and not here fails CI.
183
+ *
184
+ * THE HONEST REACH. Only the two HTML patterns are reachable through this
185
+ * carrier. `.claude/design-system/` names JSON and token files, which
186
+ * `isUiSurface` does not admit, so `main` returns before the verdict is
187
+ * computed. Widening `isUiSurface` to admit them would change the UI-turn
188
+ * denominator that the consultation-rate measurement is defined against, which
189
+ * is a different decision than this one. The prefix stays for parity with the
190
+ * rule and is stated as unreachable rather than quietly dropped.
191
+ */
192
+ export const HANDOVER_TRIGGERS: readonly Trigger[] = [
193
+ { file_pattern: '*design.html' },
194
+ { file_pattern: '*.dc.html' },
195
+ { path_prefix: '.claude/design-system/' },
196
+ ];
197
+
198
+ /** True when any of the paths this turn wrote is a provided design handover. */
199
+ export function handoverTouched(paths: readonly string[]): boolean {
200
+ if (!paths.length) return false;
201
+ return HANDOVER_TRIGGERS.some((t) => trigger_matches(t, '', paths, null));
202
+ }
203
+
204
+ export function conformanceVerdict(root: string, opts: ConformanceOptions = {}): ConformanceVerdict {
159
205
  let raw: string;
160
206
  try {
161
207
  raw = fs.readFileSync(path.join(root, CONFORMANCE_REL), 'utf-8');
162
208
  } catch {
163
- return {
164
- noteworthy: false,
165
- line: 'ui-conformance: absent — no probe artefact for this change (run `ui_conformance_probe --target <file> --reference <file>`). Absent is not clean.',
166
- };
209
+ const base =
210
+ 'ui-conformance: absent — no probe artefact for this change (run `ui_conformance_probe --target <file> --reference <file>`). Absent is not clean.';
211
+ return opts.handoverDetected
212
+ ? {
213
+ noteworthy: true,
214
+ line: `${base} A provided design handover was touched this turn, so this is the absence worth naming.`,
215
+ }
216
+ : { noteworthy: false, line: base };
167
217
  }
168
218
  try {
169
219
  const a = JSON.parse(raw) as {
170
220
  structure_gate?: string;
221
+ inputs?: unknown;
171
222
  dimensions?: { dimension: string; status: string; findings: number | null; reason?: string }[];
172
- findings?: unknown[];
223
+ findings?: { dimension?: string; probe_id?: string; expected?: string; observed?: string }[];
173
224
  };
225
+
226
+ // Before anything is counted. A finding count measured against inputs
227
+ // that have since moved is the misleading half of a stale artefact, so
228
+ // the count is withheld rather than printed beside a warning.
229
+ const inputs = compareInputs(a.inputs, (p) => (path.isAbsolute(p) ? p : path.join(root, p)));
230
+ if (inputs.verdict === 'moved') {
231
+ return {
232
+ noteworthy: true,
233
+ line: [
234
+ `ui-conformance: stale — ${inputs.moved.join(', ')} changed since the probe ran, so its findings ` +
235
+ 'describe inputs that no longer exist. The count is withheld rather than reported against ' +
236
+ 'them; re-run `ui_conformance_probe --target <file> --reference <file>`.',
237
+ ' (shadow verdict: reported, never enforced — this pass does not block on it)',
238
+ ].join('\n'),
239
+ };
240
+ }
241
+
174
242
  const rows = a.dimensions ?? [];
175
243
  const exercised = rows.filter((r) => r.status === 'exercised');
176
244
  const notApplicable = rows.filter((r) => r.status !== 'exercised');
@@ -188,6 +256,29 @@ export function conformanceVerdict(root: string): ConformanceVerdict {
188
256
  if (a.structure_gate === 'stopped') {
189
257
  parts.push(' · structure gate stopped on at least one node — style comparison was skipped for it');
190
258
  }
259
+ // The added-element half. This line already said "Absent is not clean"
260
+ // about a missing artefact; the probe's structure dimension could only
261
+ // ever show the omit half of "never omit or add an element", so this is
262
+ // the one number the verdict could not print. Derived from the findings
263
+ // the probe already writes — no new artefact field is read.
264
+ const addedElements = (a.findings ?? []).filter(
265
+ (f) => f.dimension === 'structure' && f.expected === 'absent' && f.observed === 'present',
266
+ );
267
+ if (addedElements.length) {
268
+ parts.push(
269
+ ` · ${addedElements.length} element(s) present in the target and absent from the reference: ` +
270
+ `${addedElements.map((f) => f.probe_id ?? '?').join(', ')} — added, not ported`,
271
+ );
272
+ }
273
+ parts.push(
274
+ inputs.verdict === 'unchanged'
275
+ ? ' · inputs: unchanged since the probe ran'
276
+ : // Unknown is a version skew, not evidence of movement — and
277
+ // equally not evidence of freshness. Saying so is the whole
278
+ // point: an artefact from before the digests existed must read
279
+ // neither stale nor fresh.
280
+ ` · inputs: unknown — ${inputs.reason}`,
281
+ );
191
282
  parts.push(' (shadow verdict: reported, never enforced — this pass does not block on it)');
192
283
  return {
193
284
  noteworthy: (a.findings ?? []).length > 0 || a.structure_gate === 'stopped' || notApplicable.length > 0,
@@ -456,7 +547,7 @@ function main(): number {
456
547
  // be silent — a behavioural finding is exactly the thing no static scan can
457
548
  // see — but it cannot make it refuse. `result.blocked` above is computed
458
549
  // without it and the exit code below is unchanged.
459
- const conformance = conformanceVerdict(root);
550
+ const conformance = conformanceVerdict(root, { handoverDetected: handoverTouched(targets) });
460
551
 
461
552
  if (
462
553
  !result.findings.length &&
@@ -26,10 +26,9 @@ import { type DesignContext } from "../design_slop_rules.js";
26
26
  import { UI_EXT } from "../_lib/ui_surface.js";
27
27
  import { loadDesignContext, scanFile } from "../lint_design_slop.js";
28
28
  import { readHookStdin } from "./hook_stdin.js";
29
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
29
30
 
30
31
  const SETTINGS_FILE = ".agent-settings.yml";
31
- const EXIT_ALLOW = 0;
32
- const EXIT_WARN = 2;
33
32
  const DEGRADE_AFTER = 3; // surfaces of the same file::rule signature before going silent
34
33
 
35
34
  type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
@@ -34,6 +34,10 @@ import * as tty from "node:tty";
34
34
  import { fileURLToPath, pathToFileURL } from "node:url";
35
35
  import { parse as parseYaml } from "yaml";
36
36
 
37
+ import { runBundleIntegrityGate } from "./bundle_integrity.js";
38
+ import { writeConcernTimings, type ConcernTimingSample } from "./concern_timings.js";
39
+ import { tableFingerprint } from "./table_fingerprint.js";
40
+
37
41
  import { hardenedSpawnEnv } from "../_lib/spawn_env.js";
38
42
  import {
39
43
  atomic_write_json,
@@ -59,6 +63,8 @@ import { _py_json_dumps } from './py_json_dumps.js';
59
63
  import { _fallback_yaml } from './fallback_yaml.js';
60
64
  import { detectSurface } from '../_lib/surface.js';
61
65
  import { recordCapture, recordOpportunity } from "../_lib/collector_denominator.js";
66
+ import { EXIT_ALLOW, EXIT_BLOCK, EXIT_WARN, EXIT_USAGE } from './exit_codes.js';
67
+ export { EXIT_ALLOW, EXIT_BLOCK, EXIT_WARN };
62
68
  export { _fallback_yaml } from './fallback_yaml.js';
63
69
 
64
70
  // Free-form JSON values flow through every helper here; a documented
@@ -84,10 +90,6 @@ const REPO_ROOT = path.resolve(
84
90
  );
85
91
  const MANIFEST_PATH = path.join(REPO_ROOT, "src", "scripts", "hook_manifest.yaml");
86
92
 
87
- export const EXIT_ALLOW = 0;
88
- export const EXIT_BLOCK = 1;
89
- export const EXIT_WARN = 2;
90
-
91
93
  // Per Council Round 2 (Q3): `agent_error` covers agent-level crashes
92
94
  // that are not concern-triggered, so chat-history can checkpoint
93
95
  // partial sessions on abnormal exit.
@@ -206,12 +208,7 @@ export function _parse_concern_stdout(stdout_text: string): JsonObject {
206
208
  * correctness.
207
209
  */
208
210
  export function _manifest_fingerprint(text: string): string {
209
- let h = 0x811c9dc5;
210
- for (let i = 0; i < text.length; i += 1) {
211
- h ^= text.charCodeAt(i);
212
- h = Math.imul(h, 0x01000193) >>> 0;
213
- }
214
- return `${h.toString(16)}:${String(text.length)}`;
211
+ return tableFingerprint(text);
215
212
  }
216
213
 
217
214
  export function _load_yaml(p: string): JsonObject {
@@ -251,12 +248,10 @@ export function _load_yaml(p: string): JsonObject {
251
248
  return _isObject(data) ? data : {};
252
249
  }
253
250
 
254
-
255
251
  interface ConcernDef extends JsonObject {
256
252
  name: string;
257
253
  }
258
254
 
259
-
260
255
  /**
261
256
  * Per-concern tool filter — the `tools:` key.
262
257
  *
@@ -424,7 +419,6 @@ export function _resolve_concerns(
424
419
  return out;
425
420
  }
426
421
 
427
-
428
422
  /**
429
423
  * Write the raw stdin payload to a capture directory when
430
424
  * `AGENT_HOOK_CAPTURE_DIR` is set. Fail-silent: any IO / JSON error must
@@ -506,6 +500,7 @@ interface RunResult {
506
500
  rc: number;
507
501
  stderr: string;
508
502
  stdout: string;
503
+ /** Elapsed ms, NOT floored — floored where STORED. See `concern_timings.ts`. */
509
504
  duration_ms: number;
510
505
  }
511
506
 
@@ -680,8 +675,7 @@ function _run_concern_inproc(
680
675
  }
681
676
  clearHookStdinOverride();
682
677
  }
683
- const elapsed = Math.floor(performance.now() - started);
684
- return { rc, stderr: err, stdout: out, duration_ms: elapsed };
678
+ return { rc, stderr: err, stdout: out, duration_ms: performance.now() - started };
685
679
  }
686
680
 
687
681
  function _run_concern(concern: ConcernDef, envelope: JsonObject): RunResult {
@@ -736,7 +730,7 @@ function _run_concern(concern: ConcernDef, envelope: JsonObject): RunResult {
736
730
  env: concern_env,
737
731
  timeout: 30000,
738
732
  });
739
- const elapsed = Math.floor(performance.now() - started);
733
+ const elapsed = performance.now() - started;
740
734
  if (proc.error) {
741
735
  // OSError / timeout equivalent — log execution-failed so the
742
736
  // never-block contract keeps a trace.
@@ -796,6 +790,7 @@ interface FeedbackEntry extends JsonObject {
796
790
  concern: string;
797
791
  }
798
792
 
793
+
799
794
  /**
800
795
  * Write per-concern feedback files + summary rollup.
801
796
  *
@@ -1064,7 +1059,7 @@ function _parse_args(argv: string[]): Args {
1064
1059
  process.stderr.write(
1065
1060
  `dispatch_hook: the following arguments are required: ${missing.join(", ")}\n`,
1066
1061
  );
1067
- process.exit(2);
1062
+ process.exit(EXIT_USAGE);
1068
1063
  }
1069
1064
  return args;
1070
1065
  }
@@ -1251,6 +1246,15 @@ export function main(argv?: string[]): number {
1251
1246
  }
1252
1247
  }
1253
1248
 
1249
+ // Step 3.1 — the bundle we execute must be the bundle the build produced.
1250
+ // Beside `denyOnStdinFailure` for its reason: the verdict needs the resolved
1251
+ // concern list. Rules: `bundle_integrity.ts`.
1252
+ if (_IN_BUNDLE) {
1253
+ const stop = runBundleIntegrityGate(fileURLToPath(import.meta.url), args.platform,
1254
+ args.event, concerns.some((c) => !_is_advisory(c)));
1255
+ if (stop !== null) return stop;
1256
+ }
1257
+
1254
1258
  const envelope = _build_envelope(args, payload_text);
1255
1259
  const session_id = _resolve_session_id(envelope);
1256
1260
  const started_at = _now_iso();
@@ -1286,6 +1290,7 @@ export function main(argv?: string[]): number {
1286
1290
  // classes are present, which any concern loses, the single measurement pass,
1287
1291
  // and the at-most-four envelope shapes — so the loop below reads a shape
1288
1292
  // rather than re-deriving one. Rationale and cost model: payload_stub.ts.
1293
+ const timing_samples: ConcernTimingSample[] = [];
1289
1294
  const keep_by_concern = concerns.map(
1290
1295
  (c) => [c, _concern_body_classes(c)] as const,
1291
1296
  );
@@ -1308,6 +1313,7 @@ export function main(argv?: string[]): number {
1308
1313
  const concern_started = _now_iso();
1309
1314
  const { rc: rawRcResult, stderr: stderr_text, stdout: stdout_text, duration_ms } =
1310
1315
  _run_concern(concern, concern_envelope);
1316
+ timing_samples.push({ concern: String(concern["name"]), duration_us: duration_ms * 1000 });
1311
1317
  let rc = rawRcResult;
1312
1318
  const raw_rc = rc;
1313
1319
  if (rc >= 3) {
@@ -1376,7 +1382,7 @@ export function main(argv?: string[]): number {
1376
1382
  severity: _severity_for(rc),
1377
1383
  decision: (reply["decision"] || _severity_for(rc)) as JsonValue,
1378
1384
  reason: (reply["reason"] ?? null) as JsonValue,
1379
- duration_ms,
1385
+ duration_ms: Math.floor(duration_ms),
1380
1386
  started_at: concern_started,
1381
1387
  completed_at: _now_iso(),
1382
1388
  fail_closed: Boolean(concern["fail_closed"]),
@@ -1392,6 +1398,7 @@ export function main(argv?: string[]): number {
1392
1398
  });
1393
1399
  }
1394
1400
  const final_rc = _reduce(rcs);
1401
+ writeConcernTimings(String(envelope["event"] ?? ""), String(envelope["platform"] ?? ""), timing_samples);
1395
1402
  _write_feedback(envelope, session_id, feedback_entries, final_rc, started_at);
1396
1403
  _record_rule_trips(envelope, feedback_entries);
1397
1404
  if (context_blocks.length > 0 && final_rc === EXIT_ALLOW) {
@@ -1459,7 +1466,6 @@ function _sortedRepr(s: ReadonlySet<string>): string {
1459
1466
  */
1460
1467
  let _stdin_read_failed: string | null = null;
1461
1468
 
1462
-
1463
1469
  // Bundle-safety: never auto-run when inlined into an esbuild bundle, where
1464
1470
  // every module shares the bundle's `import.meta.url` (declare at top of file).
1465
1471
  function _isCliEntry(): boolean {
@@ -86,11 +86,10 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
86
86
  import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
87
87
  import { readHookStdin } from './hook_stdin.js';
88
88
  import { atomic_write_json } from './state_io.js';
89
+ import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
89
90
 
90
- const EXIT_ALLOW = 0;
91
91
  // Severity is taken from the EXIT CODE, not from the `decision` field in the
92
92
  // stdout payload — see the file header for why this never blocks on `claude`.
93
- const EXIT_WARN = 2;
94
93
 
95
94
  /** Committed threshold (roadmap 5.1): minimum current line count of the
96
95
  * replaced file before this concern has anything to say. */
@@ -253,6 +253,7 @@ import {
253
253
  import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
254
254
  import { readHookStdin } from './hook_stdin.js';
255
255
  import { atomic_write_json, is_replay_mode } from './state_io.js';
256
+ import { EXIT_WARN } from './exit_codes.js';
256
257
 
257
258
  /** Default fire threshold — the council's high-risk shape (roadmap 5.1). */
258
259
  export const MUTATION_LINE_THRESHOLD = 50;
@@ -319,7 +320,6 @@ export function isSafeTranscriptPath(
319
320
  // reported at exit 2 so `host_semantics.emitFor` reduces it to severity
320
321
  // `"warn"` and forwards `additional_context`; see the file header for why
321
322
  // this exit code never actually blocks the turn on `claude`.
322
- const EXIT_WARN = 2;
323
323
 
324
324
  /** One `git diff --numstat` row: added/deleted line counts + the path. */
325
325
  export interface NumstatRow {
@@ -52,8 +52,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
52
52
  import { atomic_write_json } from "./state_io.js";
53
53
  import { readHookStdin } from "./hook_stdin.js";
54
54
  import { isCodePath, isTestPath } from "../_lib/test_delta.js";
55
+ import { EXIT_ALLOW, EXIT_BLOCK } from './exit_codes.js';
55
56
 
56
- const EXIT_ALLOW = 0;
57
57
  // MUST equal dispatch_hook.EXIT_BLOCK. The dispatcher's internal ladder is
58
58
  // 0 allow / 1 block / 2 warn — NOT the 2-means-block shape a PreToolUse guard
59
59
  // reads naturally from Claude's own native contract. This constant was 2 when
@@ -61,7 +61,6 @@ const EXIT_ALLOW = 0;
61
61
  // and the gate emitted advisory context while the operation went through.
62
62
  // Pinned against the dispatcher's export by
63
63
  // tests/hooks/concern_block_exit_parity.test.ts.
64
- const EXIT_BLOCK = 1;
65
64
 
66
65
  export const STATE_FILE = path.join("agents", "state", "evidence-dispatch.json");
67
66
 
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The hook exit-code table — one frozen definition, imported everywhere.
3
+ *
4
+ * road-to-a-kernel-that-guards-its-plumbing 4.1.
5
+ *
6
+ * WHAT WAS WRONG. Thirty-three files under `src/scripts/hooks/` each declared
7
+ * their own `const EXIT_ALLOW = 0` / `EXIT_BLOCK = 1` / `EXIT_WARN = 2`, two of
8
+ * them under a third name (`EXIT_OK`). Thirty-three copies of a contract is
9
+ * thirty-three places it can be changed in one and not the others, and the
10
+ * change that matters here is not a typo: exit 1 and exit 2 mean the OPPOSITE
11
+ * things on Claude Code from what they mean in this tree's internal language
12
+ * (`host_semantics.ts` exists entirely to lower one onto the other). A number
13
+ * that means "advisory" here and "hard deny" there is not a constant anyone
14
+ * should be re-deriving per file.
15
+ *
16
+ * WHAT THE TABLE ADDS BEYOND THE NUMBERS. `owner` and `authorizedBy` — who
17
+ * decides that a concern emits this code, and what authorises the dispatcher to
18
+ * act on it. A bare `1` in a concern says nothing about whether that concern was
19
+ * ever allowed to refuse; the manifest's `severity` is what authorises it, and
20
+ * the dispatcher enforces that ceiling (`_is_advisory`). Recording it beside the
21
+ * number is what makes the table a contract rather than an enum.
22
+ *
23
+ * WHAT IS NOT IN SCOPE. The `*-dispatcher.sh` trampolines carry inline
24
+ * `process.exit(0)` in a `node -e` one-liner that reads a workspace root off
25
+ * stdin. They are shell, they cannot import, and their exit code is the
26
+ * one-liner's, never a verdict. `lint_exit_codes` scans `.ts` only.
27
+ */
28
+
29
+ /** Allow — the concern has no objection. The overwhelmingly common verdict. */
30
+ export const EXIT_ALLOW = 0;
31
+
32
+ /** Block — the concern refuses. Only a `severity: blocking` concern may. */
33
+ export const EXIT_BLOCK = 1;
34
+
35
+ /** Warn — advisory. The dispatcher exits 0 and surfaces the reason. */
36
+ export const EXIT_WARN = 2;
37
+
38
+ /**
39
+ * Error — the concern crashed or could not decide.
40
+ *
41
+ * `3` is the floor, not the only value: the dispatcher treats every rc ≥ 3 the
42
+ * same way, and what that way IS depends on the concern's declared severity.
43
+ */
44
+ export const EXIT_ERROR = 3;
45
+
46
+ /**
47
+ * Usage error on the process's OWN argv — not a verdict, and not a row.
48
+ *
49
+ * `dispatch_hook` and `replay_hook` are also CLIs: run with `--platform`
50
+ * missing they exit before any concern is consulted. The number is 2 by the
51
+ * POSIX convention for a usage error, which COLLIDES with `EXIT_WARN` by
52
+ * coincidence and not by meaning — nothing advisory has happened, and no
53
+ * concern has spoken.
54
+ *
55
+ * Named rather than left as a bare `2`, and kept OUT of `EXIT_CODES`, because
56
+ * both readings are wrong: importing `EXIT_WARN` here would assert a verdict
57
+ * that was never reached, and adding a fifth row would put a non-verdict in a
58
+ * table whose whole subject is what a concern may say. `lint_exit_codes`
59
+ * refuses the bare literal, so the next writer has to pick one of the two
60
+ * deliberately.
61
+ */
62
+ export const EXIT_USAGE = 2;
63
+
64
+ export interface ExitCodeRow {
65
+ /** The numeric code, or its floor for the open-ended error band. */
66
+ readonly code: number;
67
+ readonly name: 'allow' | 'block' | 'warn' | 'error';
68
+ readonly meaning: string;
69
+ /** Who decides a concern emits this code. */
70
+ readonly owner: string;
71
+ /** What authorises the dispatcher to act on it. */
72
+ readonly authorizedBy: string;
73
+ }
74
+
75
+ /**
76
+ * The frozen table. Ordered by code; the last row is a band, not a value.
77
+ *
78
+ * `as const` plus `Object.freeze` rather than a plain array: this is the kind of
79
+ * structure a later convenience ("just push a row for the new verdict") turns
80
+ * into a fourth dialect, and the whole point of the file is that there is one.
81
+ */
82
+ export const EXIT_CODES: readonly ExitCodeRow[] = Object.freeze([
83
+ Object.freeze({
84
+ code: EXIT_ALLOW,
85
+ name: 'allow',
86
+ meaning: 'no objection; the dispatcher passes the call through',
87
+ owner: 'every concern — the default verdict',
88
+ authorizedBy: 'nothing; allowing needs no authority',
89
+ }),
90
+ Object.freeze({
91
+ code: EXIT_BLOCK,
92
+ name: 'block',
93
+ meaning: 'refuse the call; the dispatcher lowers this onto the host deny channel',
94
+ owner: 'a concern declared `severity: blocking` in hook_manifest.yaml',
95
+ authorizedBy:
96
+ 'the manifest entry plus its row in the BLOCKING_ALLOWLIST of ' +
97
+ 'tests/hooks/concern_severity.test.ts — an advisory concern emitting this is ' +
98
+ 'downgraded to warn by dispatch_hook._is_advisory',
99
+ }),
100
+ Object.freeze({
101
+ code: EXIT_WARN,
102
+ name: 'warn',
103
+ meaning: 'advisory; the dispatcher exits 0 and surfaces the reason',
104
+ owner: 'any concern',
105
+ authorizedBy:
106
+ 'nothing; warning is the ceiling for an advisory concern and is always available ' +
107
+ 'to a blocking one',
108
+ }),
109
+ Object.freeze({
110
+ code: EXIT_ERROR,
111
+ name: 'error',
112
+ meaning:
113
+ 'the concern crashed, timed out, or could not decide — any rc >= 3. The dispatcher ' +
114
+ 'resolves it by the concern`s declared severity, never by the number itself',
115
+ owner: 'no concern emits this on purpose; the runtime does',
116
+ authorizedBy:
117
+ 'hook_manifest.yaml `fail_closed` and `severity` together — see ' +
118
+ 'docs/contracts/hook-architecture-v1.md § Exit-code semantics',
119
+ }),
120
+ ] as const);
121
+
122
+ /** The row a raw exit code resolves to, or `null` for a negative code. */
123
+ export function rowFor(code: number): ExitCodeRow | null {
124
+ if (code >= EXIT_ERROR) {
125
+ return EXIT_CODES[3] ?? null;
126
+ }
127
+ return EXIT_CODES.find((r) => r.code === code) ?? null;
128
+ }
@@ -0,0 +1 @@
1
+ {"fingerprint":"08575a71d81fd8a5ef9e4b400f4a38cb66c26e1dd701d98f3cdc0bdc9e4758b4:22235","table":{"schema_version":1,"hosts":{"claude":{"surfaces":{"any":{"entry_shape":"claude-settings-hooks","json_shape":"hook-specific-output","fail_policy":"propagate","timeout_unit":"seconds","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://code.claude.com/docs/en/hooks.md","docs_digest":"9dd68316c1111c118e3ddb82861ac6271a8cd164cee0d371b26165efff35fe88","probe_at":"2026-09-06","host_version":"2.1.263","expires":"2027-09-06"},"slots":{"session_start":{"native":"SessionStart","block_exit":null,"answered_at":"2026-09-29"},"session_end":{"native":"SessionEnd","block_exit":null,"answered_at":"2026-09-29"},"stop":{"native":"Stop","block_exit":2,"answered_at":"2026-09-29"},"user_prompt_submit":{"native":"UserPromptSubmit","block_exit":2,"answered_at":"2026-09-29"},"pre_tool_use":{"native":"PreToolUse","block_exit":2,"answered_at":"2026-09-29"},"post_tool_use":{"native":"PostToolUse","block_exit":null,"answered_at":"2026-09-29"},"pre_compact":{"native":"PreCompact","block_exit":null,"answered_at":"2026-09-29"},"subagent_start":{"native":"SubagentStart","block_exit":null,"answered_at":"2026-09-29"},"subagent_stop":{"native":"SubagentStop","block_exit":null,"answered_at":"2026-09-29"}}}}},"augment":{"surfaces":{"any":{"entry_shape":"augment-settings-hooks","json_shape":"none","fail_policy":"discard","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://docs.augmentcode.com/cli/hooks.md","docs_digest":"e500823be8303717020c1823f670e06d100108f6e3ac4b8d12a46d91fd1898fa","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{"session_start":{"native":"SessionStart","block_exit":null,"answered_at":"2026-09-29"},"session_end":{"native":"SessionEnd","block_exit":null,"answered_at":"2026-09-29"},"stop":{"native":"Stop","block_exit":null,"answered_at":"2026-09-29"},"pre_tool_use":{"native":"PreToolUse","block_exit":null,"answered_at":"2026-09-29"},"post_tool_use":{"native":"PostToolUse","block_exit":null,"answered_at":"2026-09-29"}}}}},"cursor":{"surfaces":{"any":{"entry_shape":"cursor-hooks-json","json_shape":"none","fail_policy":"propagate","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://cursor.com/docs/hooks.md","docs_digest":"7294a2d63b19d79a5283c948afe174cba8b06a7585563d34a2c41b90f5df0d39","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{"session_start":{"native":"sessionStart","block_exit":null,"answered_at":"2026-09-29"},"session_end":{"native":"sessionEnd","block_exit":null,"answered_at":"2026-09-29"},"stop":{"native":"stop","block_exit":null,"answered_at":"2026-09-29"},"user_prompt_submit":{"native":"beforeSubmitPrompt","block_exit":null,"answered_at":"2026-09-29"},"post_tool_use":{"native":"postToolUse","block_exit":null,"answered_at":"2026-09-29"}}}}},"cline":{"surfaces":{"any":{"entry_shape":"cline-per-event-script","json_shape":"none","fail_policy":"discard","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://docs.cline.bot/customization/hooks.md","docs_digest":"ef87561721f4cb80fe6d682b6866d638a4952dd416d36cd5ab398426ce64084b","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{"session_start":{"native":["TaskStart","TaskResume"],"block_exit":null,"answered_at":"2026-09-29"},"session_end":{"native":"TaskComplete","block_exit":null,"answered_at":"2026-09-29"},"stop":{"native":"TaskCancel","block_exit":null,"answered_at":"2026-09-29"},"user_prompt_submit":{"native":"UserPromptSubmit","block_exit":null,"answered_at":"2026-09-29"},"post_tool_use":{"native":"PostToolUse","block_exit":null,"answered_at":"2026-09-29"}}}}},"windsurf":{"surfaces":{"any":{"entry_shape":"windsurf-hooks-json","json_shape":"none","fail_policy":"propagate","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://docs.devin.ai/desktop/cascade/hooks.md","docs_digest":"460ffe7aa91998804920f6722c0153b9948eadd5edafa19582298475a19835a2","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{"session_start":{"native":"post_setup_worktree","block_exit":null,"answered_at":"2026-09-29"},"user_prompt_submit":{"native":"pre_user_prompt","block_exit":null,"answered_at":"2026-09-29"},"stop":{"native":"post_cascade_response","block_exit":null,"answered_at":"2026-09-29"}}}}},"gemini":{"surfaces":{"any":{"entry_shape":"gemini-settings-hooks","json_shape":"none","fail_policy":"propagate","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://geminicli.com/docs/hooks/reference.md","docs_digest":"06d37f2f66812ee78bb46c37a112203376f0bae4d3b8b44ac79d0bda4b86a118","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{"session_start":{"native":"SessionStart","block_exit":null,"answered_at":"2026-09-29","matcher":""},"session_end":{"native":"SessionEnd","block_exit":null,"answered_at":"2026-09-29","matcher":""},"stop":{"native":"AfterAgent","block_exit":null,"answered_at":"2026-09-29","matcher":""},"user_prompt_submit":{"native":"BeforeAgent","block_exit":null,"answered_at":"2026-09-29","matcher":""},"post_tool_use":{"native":"AfterTool","block_exit":null,"answered_at":"2026-09-29","matcher":".*"}}}}},"cowork":{"surfaces":{"any":{"entry_shape":"none","json_shape":"none","fail_policy":"discard","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":null,"docs_digest":null,"probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{}}}},"copilot":{"surfaces":{"any":{"entry_shape":"none","json_shape":"none","fail_policy":"discard","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://docs.github.com/en/copilot/concepts/agents/hooks.md","docs_digest":"bfd40aa93ad62133efc17a50f189034f01594dc89a5d31a13a0c541d691acede","probe_at":null,"host_version":null,"expires":"2027-09-29"},"slots":{}}}},"codex":{"surfaces":{"any":{"entry_shape":"none","json_shape":"none","fail_policy":"discard","timeout_unit":"unknown","timeout_default":null,"verified":{"docs_at":"2026-09-29","docs_url":"https://developers.openai.com/codex/hooks.md","docs_digest":"724881d81f1a9fdb35ea42d4851a11b1f6606bbc6e9d2d05c1e8c466fc25c630","probe_at":"2026-10-01","host_version":"0.148.0","expires":"2027-09-29"},"slots":{}}}}}}}