@bookedsolid/rea 0.28.2 → 0.30.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 (38) hide show
  1. package/.husky/prepare-commit-msg +295 -0
  2. package/MIGRATING.md +75 -0
  3. package/dist/audit/append.d.ts +1 -0
  4. package/dist/audit/append.js +1 -0
  5. package/dist/audit/delegation-event.d.ts +215 -0
  6. package/dist/audit/delegation-event.js +113 -0
  7. package/dist/cli/audit-specialists.d.ts +113 -0
  8. package/dist/cli/audit-specialists.js +220 -0
  9. package/dist/cli/doctor.d.ts +114 -1
  10. package/dist/cli/doctor.js +523 -5
  11. package/dist/cli/hook.d.ts +40 -8
  12. package/dist/cli/hook.js +305 -8
  13. package/dist/cli/index.js +9 -0
  14. package/dist/cli/init.js +120 -0
  15. package/dist/cli/install/manifest-schema.d.ts +6 -6
  16. package/dist/cli/install/prepare-commit-msg.d.ts +83 -0
  17. package/dist/cli/install/prepare-commit-msg.js +208 -0
  18. package/dist/cli/install/settings-merge.js +20 -0
  19. package/dist/cli/upgrade.js +34 -0
  20. package/dist/config/settings-schema.d.ts +2087 -0
  21. package/dist/config/settings-schema.js +294 -0
  22. package/dist/config/tier-map.js +22 -1
  23. package/dist/policy/loader.d.ts +58 -0
  24. package/dist/policy/loader.js +68 -0
  25. package/dist/policy/profiles.d.ts +48 -0
  26. package/dist/policy/profiles.js +25 -0
  27. package/dist/policy/types.d.ts +51 -0
  28. package/dist/registry/loader.d.ts +12 -12
  29. package/hooks/delegation-capture.sh +158 -0
  30. package/package.json +1 -1
  31. package/profiles/bst-internal-no-codex.yaml +15 -0
  32. package/profiles/bst-internal.yaml +16 -0
  33. package/profiles/client-engagement.yaml +14 -0
  34. package/profiles/lit-wc.yaml +14 -0
  35. package/profiles/minimal.yaml +16 -0
  36. package/profiles/open-source-no-codex.yaml +13 -0
  37. package/profiles/open-source.yaml +13 -0
  38. package/templates/prepare-commit-msg.husky.sh +295 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Single source of truth for the `rea.delegation_signal` audit event shape
3
+ * (0.29.0+).
4
+ *
5
+ * 0.29.0 — delegation-telemetry MVP. Claude Code's PreToolUse hook tree
6
+ * gains a new matcher (`Agent|Skill`) that pipes a redacted, hashed
7
+ * record of every subagent dispatch and skill invocation into
8
+ * `.rea/audit.jsonl`. The signal is observational, not gating — it
9
+ * answers "which specialists is this session actually delegating to,
10
+ * and how often" without altering the autonomy tree.
11
+ *
12
+ * # The two delegation tools
13
+ *
14
+ * Current Claude Code exposes exactly two delegation surfaces:
15
+ *
16
+ * - `Agent` — dispatches a curated subagent (rea-orchestrator,
17
+ * code-reviewer, …). The agent name is at
18
+ * `tool_input.subagent_type`.
19
+ * - `Skill` — invokes a named skill (deep-dive, /loop, …). The skill
20
+ * name is at `tool_input.skill`.
21
+ *
22
+ * mcp-protocol-specialist verified BOTH payload paths against current
23
+ * Claude Code. A Skill that internally forks an Agent fires PreToolUse
24
+ * TWICE (Skill then Agent) for the same logical action; v1 records
25
+ * both — deduplication lives in the reader, not the writer.
26
+ *
27
+ * # Not `Task`
28
+ *
29
+ * In current Claude Code the tools are `Agent` and `Skill`. The names
30
+ * `TaskCreate`/`TaskList`/`TaskUpdate` belong to the unrelated todo-list
31
+ * tool surface and MUST NOT match. The settings.json matcher is
32
+ * `Agent|Skill` everywhere — anchored on a `^…$` boundary by the hook
33
+ * runtime, so `Agent` doesn't accidentally collide with hypothetical
34
+ * future tools named `Agentic…`.
35
+ *
36
+ * # Privacy invariant
37
+ *
38
+ * The raw `description` / `prompt` payload NEVER touches `.rea/audit.jsonl`
39
+ * — only its SHA-256 hash. The hash is collision-resistant identification
40
+ * (two identical prompts produce identical hashes, enabling
41
+ * delegation-pattern discovery) without persisting prompt content.
42
+ *
43
+ * The agent / skill name field DOES land in the audit log, but is run
44
+ * through `redactSecrets` first. A subagent_type that contains a
45
+ * planted credential string (synthetic AWS key, GitHub token, …) is
46
+ * replaced with `[REDACTED]` and the matching pattern names are
47
+ * appended to the record's `redacted_fields` envelope.
48
+ *
49
+ * # Provider seam (kept tiny)
50
+ *
51
+ * Unlike `rea.local_review`, this event does NOT have a `provider`
52
+ * field. The producer is always Claude Code's hook runtime and the
53
+ * `emission_source: 'rea-cli'` envelope is sufficient. If a future
54
+ * runtime (e.g. another agent host) wants to emit signals through the
55
+ * same channel, it writes the same shape with the same tool_name and
56
+ * relies on `session_id_observed` / `delegation_tool` for
57
+ * disambiguation.
58
+ *
59
+ * # Schema version
60
+ *
61
+ * The literal `schema_version: 1` is part of the metadata payload. Zod
62
+ * strict-mode rejects unknown fields, so a future v2 producer writing
63
+ * v2-only fields against a v1 consumer fails-loud rather than silently
64
+ * dropping data. Readers filter by `tool_name === 'rea.delegation_signal'`
65
+ * AND `metadata.schema_version === 1`.
66
+ */
67
+ import { z } from 'zod';
68
+ /**
69
+ * Canonical `tool_name` on the audit record envelope. Readers filter on
70
+ * this exact literal — anything else is a different event class.
71
+ */
72
+ export const DELEGATION_SIGNAL_TOOL_NAME = 'rea.delegation_signal';
73
+ /**
74
+ * `server_name` envelope value. The signal originates from Claude Code's
75
+ * hook runtime, captured by `rea hook delegation-signal` and appended
76
+ * via the public audit-record API. Naming it `claude-code-hooks` makes
77
+ * the producer surface unambiguous in forensic queries (vs.
78
+ * `'rea'` which is used for first-party rea CLI events like
79
+ * `rea.local_review`).
80
+ */
81
+ export const DELEGATION_SIGNAL_SERVER_NAME = 'claude-code-hooks';
82
+ /**
83
+ * Schema version literal. Bumped only when the metadata shape gains a
84
+ * non-backwards-compatible change. Adding optional fields does NOT bump
85
+ * the version — zod's strict mode rejects them, so any new field MUST
86
+ * either ship with a major-version bump OR have its zod parser updated
87
+ * in lockstep.
88
+ */
89
+ export const DELEGATION_SIGNAL_SCHEMA_VERSION = 1;
90
+ /**
91
+ * Strict-mode zod schema for the metadata payload. Unknown fields are
92
+ * rejected — a future v2 producer must bump `DELEGATION_SIGNAL_SCHEMA_VERSION`
93
+ * AND update this schema in the same commit, otherwise v1 readers fail
94
+ * loud rather than silently dropping new fields.
95
+ *
96
+ * The schema is exported so the CLI subcommand validates its OWN
97
+ * emitted metadata before passing it to `appendAuditRecord` — defense
98
+ * in depth against a future refactor that wires the field set
99
+ * incorrectly. (Same posture as `loadPolicy` self-validation.)
100
+ */
101
+ export const DelegationSignalMetadataSchema = z
102
+ .object({
103
+ schema_version: z.literal(DELEGATION_SIGNAL_SCHEMA_VERSION),
104
+ delegation_tool: z.union([z.literal('Agent'), z.literal('Skill')]),
105
+ subagent_type: z.string(),
106
+ session_id_observed: z.string(),
107
+ parent_subagent_type: z.union([z.string(), z.null()]),
108
+ invocation_description_sha256: z
109
+ .string()
110
+ .regex(/^[0-9a-f]{64}$/, 'invocation_description_sha256 must be a lowercase 64-char hex SHA-256 digest'),
111
+ hook_event_timestamp: z.string().optional(),
112
+ })
113
+ .strict();
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `rea audit specialists` — 0.29.0 reader CLI for delegation-telemetry.
3
+ *
4
+ * Walks `.rea/audit.jsonl`, filters records by
5
+ * `tool_name === 'rea.delegation_signal'`, groups by
6
+ * `metadata.subagent_type`, and prints a table (default) or JSON
7
+ * document (`--json`).
8
+ *
9
+ * # Current-session-only in v1
10
+ *
11
+ * v1 has NO `--since` flag and NO `--session=ID` flag. The principal-
12
+ * engineer scope-cut deferred both to 0.29.1. The filter is:
13
+ *
14
+ * - If `CLAUDE_SESSION_ID` is set, include only records whose
15
+ * `metadata.session_id_observed` matches.
16
+ * - Otherwise, include all records in the chain and print a note so
17
+ * the operator knows what they're seeing.
18
+ *
19
+ * # Output shape
20
+ *
21
+ * subagent_type count last_seen (UTC)
22
+ * rea-orchestrator 12 2026-05-12T21:30:00Z
23
+ * code-reviewer 5 2026-05-12T21:28:00Z
24
+ * deep-dive 2 2026-05-12T21:14:00Z
25
+ *
26
+ * JSON mode prints `{ session_filter, records, groups }` where
27
+ * `records` is the raw filtered subset (for piping into jq) and
28
+ * `groups` is the per-subagent rollup.
29
+ */
30
+ import type { Command } from 'commander';
31
+ import { type DelegationTool } from '../audit/delegation-event.js';
32
+ export interface AuditSpecialistsOptions {
33
+ /** Emit a single JSON document on stdout instead of the table. */
34
+ json?: boolean;
35
+ /**
36
+ * Override session filtering. Production callers omit (the CLI reads
37
+ * `CLAUDE_SESSION_ID` from the env). Tests set this so they don't
38
+ * mutate `process.env`.
39
+ *
40
+ * - `string` → filter to records whose `session_id_observed` matches.
41
+ * - `null` → no filter (show all records).
42
+ * - `undefined` → derive from env.
43
+ */
44
+ sessionFilter?: string | null;
45
+ /** Override CWD. Tests set this; production uses `process.cwd()`. */
46
+ baseDir?: string;
47
+ }
48
+ interface DelegationGroup {
49
+ subagent_type: string;
50
+ count: number;
51
+ /** Latest envelope timestamp seen in the group. */
52
+ last_seen: string;
53
+ /** Breakdown of which delegation_tool fired (Agent vs. Skill). */
54
+ by_tool: Record<DelegationTool, number>;
55
+ }
56
+ interface DelegationRecord {
57
+ timestamp: string;
58
+ session_id_observed: string;
59
+ delegation_tool: DelegationTool;
60
+ subagent_type: string;
61
+ parent_subagent_type: string | null;
62
+ invocation_description_sha256: string;
63
+ hook_event_timestamp?: string;
64
+ }
65
+ interface AuditSpecialistsResult {
66
+ /** Which session filter was applied. `null` means "no filter". */
67
+ session_filter: string | null;
68
+ /** Was the filter derived from `CLAUDE_SESSION_ID` env? Informational. */
69
+ session_filter_source: 'env' | 'option' | 'none';
70
+ /** Raw matched records, in chain order. */
71
+ records: DelegationRecord[];
72
+ /** Per-subagent rollups, sorted by descending count then by name. */
73
+ groups: DelegationGroup[];
74
+ /**
75
+ * Files actually walked. v1 only walks `.rea/audit.jsonl`; future
76
+ * `--since` rotated-file support extends this.
77
+ */
78
+ files_scanned: string[];
79
+ }
80
+ /**
81
+ * Read the audit file and return delegation records (filtered + parsed
82
+ * into a reader-friendly shape). Malformed lines are skipped silently
83
+ * — `rea audit verify` is the right tool for chain integrity.
84
+ */
85
+ export declare function loadDelegationRecords(baseDir: string, sessionFilter: string | null): Promise<{
86
+ records: DelegationRecord[];
87
+ filesScanned: string[];
88
+ }>;
89
+ /**
90
+ * Group records by `subagent_type`. Sorts by descending count, then
91
+ * alphabetical on tie. `last_seen` is the latest envelope timestamp in
92
+ * the group.
93
+ */
94
+ export declare function groupBySubagent(records: DelegationRecord[]): DelegationGroup[];
95
+ /**
96
+ * Computation-only entrypoint. Returns the full result so callers
97
+ * (CLI, tests) can render or assert. `runAuditSpecialists` is the thin
98
+ * commander wrapper that prints + exits.
99
+ */
100
+ export declare function computeAuditSpecialists(options?: AuditSpecialistsOptions): Promise<AuditSpecialistsResult>;
101
+ /**
102
+ * Commander entrypoint. Reads, renders, exits 0. The CLI is read-only
103
+ * — no audit-chain writes, no exit-code-as-verdict semantics.
104
+ */
105
+ export declare function runAuditSpecialists(options: AuditSpecialistsOptions): Promise<void>;
106
+ /**
107
+ * Attach the `specialists` subcommand to the `rea audit` command group.
108
+ * Exported as a registrar so `src/cli/index.ts` can wire it next to the
109
+ * existing `rotate` and `verify` subcommands without leaking commander
110
+ * knowledge into this module.
111
+ */
112
+ export declare function registerAuditSpecialistsSubcommand(auditCommand: Command): void;
113
+ export {};
@@ -0,0 +1,220 @@
1
+ /**
2
+ * `rea audit specialists` — 0.29.0 reader CLI for delegation-telemetry.
3
+ *
4
+ * Walks `.rea/audit.jsonl`, filters records by
5
+ * `tool_name === 'rea.delegation_signal'`, groups by
6
+ * `metadata.subagent_type`, and prints a table (default) or JSON
7
+ * document (`--json`).
8
+ *
9
+ * # Current-session-only in v1
10
+ *
11
+ * v1 has NO `--since` flag and NO `--session=ID` flag. The principal-
12
+ * engineer scope-cut deferred both to 0.29.1. The filter is:
13
+ *
14
+ * - If `CLAUDE_SESSION_ID` is set, include only records whose
15
+ * `metadata.session_id_observed` matches.
16
+ * - Otherwise, include all records in the chain and print a note so
17
+ * the operator knows what they're seeing.
18
+ *
19
+ * # Output shape
20
+ *
21
+ * subagent_type count last_seen (UTC)
22
+ * rea-orchestrator 12 2026-05-12T21:30:00Z
23
+ * code-reviewer 5 2026-05-12T21:28:00Z
24
+ * deep-dive 2 2026-05-12T21:14:00Z
25
+ *
26
+ * JSON mode prints `{ session_filter, records, groups }` where
27
+ * `records` is the raw filtered subset (for piping into jq) and
28
+ * `groups` is the per-subagent rollup.
29
+ */
30
+ import fs from 'node:fs/promises';
31
+ import path from 'node:path';
32
+ import { DELEGATION_SIGNAL_TOOL_NAME, DELEGATION_SIGNAL_SCHEMA_VERSION, } from '../audit/delegation-event.js';
33
+ import { AUDIT_FILE, REA_DIR, log } from './utils.js';
34
+ /**
35
+ * Type guard for a record that has the delegation-signal shape. Skips
36
+ * envelope shape validation (zod runs at write time); checks only the
37
+ * fields the reader actually uses.
38
+ */
39
+ function isDelegationRecord(r) {
40
+ if (r.tool_name !== DELEGATION_SIGNAL_TOOL_NAME)
41
+ return false;
42
+ const m = r.metadata;
43
+ if (m === undefined)
44
+ return false;
45
+ if (m.schema_version !== DELEGATION_SIGNAL_SCHEMA_VERSION)
46
+ return false;
47
+ if (m.delegation_tool !== 'Agent' && m.delegation_tool !== 'Skill')
48
+ return false;
49
+ if (typeof m.subagent_type !== 'string')
50
+ return false;
51
+ return true;
52
+ }
53
+ /**
54
+ * Read the audit file and return delegation records (filtered + parsed
55
+ * into a reader-friendly shape). Malformed lines are skipped silently
56
+ * — `rea audit verify` is the right tool for chain integrity.
57
+ */
58
+ export async function loadDelegationRecords(baseDir, sessionFilter) {
59
+ const auditFile = path.join(baseDir, REA_DIR, AUDIT_FILE);
60
+ let raw;
61
+ try {
62
+ raw = await fs.readFile(auditFile, 'utf8');
63
+ }
64
+ catch (e) {
65
+ if (e.code === 'ENOENT') {
66
+ return { records: [], filesScanned: [] };
67
+ }
68
+ throw e;
69
+ }
70
+ const records = [];
71
+ for (const line of raw.split('\n')) {
72
+ if (line.length === 0)
73
+ continue;
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(line);
77
+ }
78
+ catch {
79
+ continue;
80
+ }
81
+ if (!isDelegationRecord(parsed))
82
+ continue;
83
+ const m = parsed.metadata;
84
+ if (sessionFilter !== null && m.session_id_observed !== sessionFilter)
85
+ continue;
86
+ const rec = {
87
+ timestamp: parsed.timestamp,
88
+ session_id_observed: m.session_id_observed,
89
+ delegation_tool: m.delegation_tool,
90
+ subagent_type: m.subagent_type,
91
+ parent_subagent_type: m.parent_subagent_type,
92
+ invocation_description_sha256: m.invocation_description_sha256,
93
+ ...(m.hook_event_timestamp !== undefined
94
+ ? { hook_event_timestamp: m.hook_event_timestamp }
95
+ : {}),
96
+ };
97
+ records.push(rec);
98
+ }
99
+ return { records, filesScanned: [auditFile] };
100
+ }
101
+ /**
102
+ * Group records by `subagent_type`. Sorts by descending count, then
103
+ * alphabetical on tie. `last_seen` is the latest envelope timestamp in
104
+ * the group.
105
+ */
106
+ export function groupBySubagent(records) {
107
+ const byName = new Map();
108
+ for (const r of records) {
109
+ let g = byName.get(r.subagent_type);
110
+ if (g === undefined) {
111
+ g = {
112
+ subagent_type: r.subagent_type,
113
+ count: 0,
114
+ last_seen: r.timestamp,
115
+ by_tool: { Agent: 0, Skill: 0 },
116
+ };
117
+ byName.set(r.subagent_type, g);
118
+ }
119
+ g.count += 1;
120
+ g.by_tool[r.delegation_tool] += 1;
121
+ if (r.timestamp > g.last_seen)
122
+ g.last_seen = r.timestamp;
123
+ }
124
+ return Array.from(byName.values()).sort((a, b) => {
125
+ if (b.count !== a.count)
126
+ return b.count - a.count;
127
+ return a.subagent_type.localeCompare(b.subagent_type);
128
+ });
129
+ }
130
+ /**
131
+ * Computation-only entrypoint. Returns the full result so callers
132
+ * (CLI, tests) can render or assert. `runAuditSpecialists` is the thin
133
+ * commander wrapper that prints + exits.
134
+ */
135
+ export async function computeAuditSpecialists(options = {}) {
136
+ const baseDir = options.baseDir ?? process.cwd();
137
+ let sessionFilter;
138
+ let source;
139
+ if (options.sessionFilter === undefined) {
140
+ const envId = process.env['CLAUDE_SESSION_ID'];
141
+ if (typeof envId === 'string' && envId.length > 0) {
142
+ sessionFilter = envId;
143
+ source = 'env';
144
+ }
145
+ else {
146
+ sessionFilter = null;
147
+ source = 'none';
148
+ }
149
+ }
150
+ else {
151
+ sessionFilter = options.sessionFilter;
152
+ source = options.sessionFilter === null ? 'none' : 'option';
153
+ }
154
+ const { records, filesScanned } = await loadDelegationRecords(baseDir, sessionFilter);
155
+ const groups = groupBySubagent(records);
156
+ return {
157
+ session_filter: sessionFilter,
158
+ session_filter_source: source,
159
+ records,
160
+ groups,
161
+ files_scanned: filesScanned,
162
+ };
163
+ }
164
+ function renderTable(result) {
165
+ if (result.groups.length === 0) {
166
+ const note = result.session_filter !== null
167
+ ? `No delegation signals recorded for session ${result.session_filter}.`
168
+ : 'No delegation signals recorded.';
169
+ return `${note}\n (Records are written by the .claude/hooks/delegation-capture.sh PreToolUse hook on every Agent/Skill dispatch.)\n`;
170
+ }
171
+ const headers = ['subagent_type', 'count', 'agent', 'skill', 'last_seen (UTC)'];
172
+ const rows = result.groups.map((g) => [
173
+ g.subagent_type,
174
+ String(g.count),
175
+ String(g.by_tool.Agent),
176
+ String(g.by_tool.Skill),
177
+ g.last_seen,
178
+ ]);
179
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)));
180
+ const lines = [];
181
+ lines.push(headers.map((h, i) => h.padEnd(widths[i])).join(' '));
182
+ lines.push(widths.map((w) => '-'.repeat(w)).join(' '));
183
+ for (const row of rows) {
184
+ lines.push(row.map((c, i) => c.padEnd(widths[i])).join(' '));
185
+ }
186
+ return lines.join('\n') + '\n';
187
+ }
188
+ /**
189
+ * Commander entrypoint. Reads, renders, exits 0. The CLI is read-only
190
+ * — no audit-chain writes, no exit-code-as-verdict semantics.
191
+ */
192
+ export async function runAuditSpecialists(options) {
193
+ const result = await computeAuditSpecialists(options);
194
+ if (options.json === true) {
195
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
196
+ return;
197
+ }
198
+ if (result.session_filter !== null) {
199
+ log(`Delegation signals (session=${result.session_filter}, source=${result.session_filter_source}):`);
200
+ }
201
+ else {
202
+ log('Delegation signals (no session filter — set $CLAUDE_SESSION_ID to scope; v1 omits --since / --session by design):');
203
+ }
204
+ process.stdout.write(renderTable(result));
205
+ }
206
+ /**
207
+ * Attach the `specialists` subcommand to the `rea audit` command group.
208
+ * Exported as a registrar so `src/cli/index.ts` can wire it next to the
209
+ * existing `rotate` and `verify` subcommands without leaking commander
210
+ * knowledge into this module.
211
+ */
212
+ export function registerAuditSpecialistsSubcommand(auditCommand) {
213
+ auditCommand
214
+ .command('specialists')
215
+ .description('Summarize `rea.delegation_signal` audit records — counts per subagent / skill, last-seen timestamps, agent-vs-skill breakdown. Reads only the current `.rea/audit.jsonl`. Honors $CLAUDE_SESSION_ID for current-session filtering. v1 omits --since / --session by design (deferred to 0.29.1).')
216
+ .option('--json', 'emit JSON (records + groups) instead of the human-readable table. Composes with jq.')
217
+ .action(async (opts) => {
218
+ await runAuditSpecialists({ ...(opts.json === true ? { json: true } : {}) });
219
+ });
220
+ }
@@ -19,6 +19,44 @@ export interface CheckResult {
19
19
  * Exported so tests can drive this without spinning up the full `runDoctor`.
20
20
  */
21
21
  export declare function checkFingerprintStore(baseDir: string): Promise<CheckResult>;
22
+ /**
23
+ * 0.30.0 (Class M settings.json schema) — `EXPECTED_HOOKS` is exported
24
+ * so the schema validator at `src/config/settings-schema.ts` can
25
+ * cross-check rea-shipped hook filenames against entries it sees in
26
+ * a consumer's `.claude/settings.json`. The validator's `--strict`
27
+ * mode FAILS when a known rea-managed hook is missing from the
28
+ * consumer's registration; default mode logs a warn.
29
+ */
30
+ export declare const EXPECTED_AGENTS: string[];
31
+ export declare const EXPECTED_HOOKS: string[];
32
+ /**
33
+ * 0.30.0 Class M — validate `.claude/settings.json` against the zod
34
+ * schema in `src/config/settings-schema.ts`.
35
+ *
36
+ * Status posture:
37
+ *
38
+ * - `strict: false` (default `rea doctor`) — emit a warn when:
39
+ * - zod parse fails (unknown top-level key, missing matcher,
40
+ * malformed hook entry, etc.),
41
+ * - any `command` contains a `..` traversal after stripping
42
+ * `$CLAUDE_PROJECT_DIR`,
43
+ * - any rea-shipped hook from `EXPECTED_HOOKS` is missing from
44
+ * the consumer's registrations.
45
+ * The harness keeps working — the schema only refuses to call
46
+ * malformed hook entries; we surface the issue without breaking
47
+ * the install.
48
+ *
49
+ * - `strict: true` (`rea doctor --strict`) — fail (hard) on the
50
+ * same conditions. Used by CI gates that want a hard floor on
51
+ * consumer settings.
52
+ *
53
+ * Returns `pass` when everything cleared. Returns one `CheckResult`
54
+ * per concern; called once and emits one result. Combined with the
55
+ * existing `checkSettingsJson` (which checks for the historical Bash
56
+ * + Write|Edit|MultiEdit|NotebookEdit matchers), gives consumers a
57
+ * complete picture.
58
+ */
59
+ export declare function checkSettingsSchema(baseDir: string, strict: boolean): CheckResult;
22
60
  /**
23
61
  * Detect whether `baseDir` is a git repository. Returns true for the three
24
62
  * shapes git itself accepts:
@@ -46,6 +84,7 @@ export declare function checkFingerprintStore(baseDir: string): Promise<CheckRes
46
84
  * NOT a trust boundary. Do not key security decisions on the return value.
47
85
  */
48
86
  export declare function isGitRepo(baseDir: string): boolean;
87
+ export declare function checkPrepareCommitMsgHook(baseDir: string): CheckResult;
49
88
  /**
50
89
  * Hard-fail when `policy.review.codex_required: true` but the `codex`
51
90
  * binary is not on PATH. Pre-0.12.0 this prereq surfaced only at first
@@ -65,6 +104,63 @@ export declare function checkCodexBinaryOnPath(): CheckResult;
65
104
  * the real probe.
66
105
  */
67
106
  export declare function checksFromProbeState(state: CodexProbeState): CheckResult[];
107
+ /**
108
+ * 0.29.0 — verify the delegation-capture hook is registered in
109
+ * `.claude/settings.json` under PreToolUse with matcher `Agent|Skill`
110
+ * AND that the hook file exists at the expected dogfood path.
111
+ *
112
+ * Status posture for 0.29.0:
113
+ *
114
+ * The 0.29.0 release introduces a new desired-hook entry in
115
+ * `defaultDesiredHooks()` that `rea init` and `rea upgrade` will merge
116
+ * into consumer `.claude/settings.json` files. Existing consumer
117
+ * installs (and this repo's own dogfood, which is locked from
118
+ * agent-driven edits by `settings-protection.sh`) won't have the
119
+ * matcher registered until the operator runs `rea upgrade`.
120
+ *
121
+ * To keep the upgrade-lag period from breaking `rea doctor`, the
122
+ * check is `warn` (not `fail`) for 0.29.0. The detail message names
123
+ * the exact command to fix and points at the canonical
124
+ * `delegation-capture.sh` install. After 0.29.0+1 consumer-install
125
+ * cycles have propagated, this should be promoted to `fail` so a
126
+ * skipped upgrade is loud rather than silent. Codex round 2 P2
127
+ * (2026-05-12).
128
+ *
129
+ * Hook-file presence is verified separately by `checkHooksInstalled`
130
+ * via `EXPECTED_HOOKS` — that path stays at the hard-`fail` posture
131
+ * because file presence is part of the install manifest and doesn't
132
+ * suffer the same template-propagation lag.
133
+ */
134
+ export declare function checkDelegationHookRegistered(baseDir: string): CheckResult;
135
+ /**
136
+ * 0.29.0 — synthetic round-trip of the delegation-signal audit path.
137
+ * Drives a synthetic Claude Code PreToolUse hook payload through the
138
+ * REAL `rea hook delegation-signal` CLI by spawning a child process
139
+ * (same path the shell hook hits) and asserts:
140
+ *
141
+ * - The CLI exited 0.
142
+ * - A new `rea.delegation_signal` record landed on disk.
143
+ * - The record's metadata contains the probe tag (so we don't
144
+ * mistakenly attribute an existing record to our run).
145
+ * - Chain integrity holds (recomputed hash == stored hash).
146
+ *
147
+ * Codex round 1 P2 (2026-05-12): the previous implementation called
148
+ * `appendAuditRecord()` directly — short-circuiting stdin parsing,
149
+ * SHA-256 hashing, redact-secrets timing, and the `process.exit`
150
+ * ordering that round 1's P1 exposed. That made the smoke check
151
+ * report success even when the real production path was broken.
152
+ *
153
+ * This rewrite exercises the same surface the `Agent|Skill`
154
+ * PreToolUse hook does in production, so future regressions in
155
+ * stdin parsing, hashing, redaction, or process-lifecycle behavior
156
+ * fail the smoke check loudly.
157
+ *
158
+ * Gated behind `--smoke` so a casual `rea doctor` doesn't write
159
+ * probe records on every invocation. Operators run
160
+ * `rea doctor --smoke` after install / upgrade to confirm the
161
+ * pipeline is wired end-to-end.
162
+ */
163
+ export declare function checkDelegationRoundTrip(baseDir: string): Promise<CheckResult>;
68
164
  /**
69
165
  * Assemble the full checklist for a given baseDir. Exported so tests can
70
166
  * exercise the conditional branching without capturing stdout from
@@ -80,7 +176,9 @@ export declare function checksFromProbeState(state: CodexProbeState): CheckResul
80
176
  *
81
177
  * `activeForeign` always yields `fail` — a foreign hook bypassing the gate is a hard governance gap.
82
178
  */
83
- export declare function collectChecks(baseDir: string, codexProbeState?: CodexProbeState, prePushState?: PrePushDoctorState): CheckResult[];
179
+ export declare function collectChecks(baseDir: string, codexProbeState?: CodexProbeState, prePushState?: PrePushDoctorState, options?: {
180
+ strict?: boolean;
181
+ }): CheckResult[];
84
182
  export interface RunDoctorOptions {
85
183
  /** When true, print a 7-day telemetry summary after the checks (G11.5). */
86
184
  metrics?: boolean;
@@ -91,6 +189,21 @@ export interface RunDoctorOptions {
91
189
  * `rea upgrade` to reconcile).
92
190
  */
93
191
  drift?: boolean;
192
+ /**
193
+ * 0.29.0 — when true, run the synthetic delegation-signal round-trip
194
+ * check. Writes a probe `rea.delegation_signal` audit record (with
195
+ * the doctor-smoke session id) and verifies chain integrity. Gated
196
+ * behind a flag so casual `rea doctor` invocations don't pollute the
197
+ * audit log with probe records.
198
+ */
199
+ smoke?: boolean;
200
+ /**
201
+ * 0.30.0 — when true, every advisory check (settings.json schema
202
+ * cross-check, prepare-commit-msg foreign-hook warn, etc.) is
203
+ * promoted to hard fail. Used by CI gates that want a strict floor
204
+ * on consumer installs. Default `false`.
205
+ */
206
+ strict?: boolean;
94
207
  }
95
208
  export interface DriftRow {
96
209
  path: string;