@bookedsolid/rea 0.28.1 → 0.29.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.
- package/agents/rea-orchestrator.md +18 -0
- package/dist/audit/append.d.ts +1 -0
- package/dist/audit/append.js +1 -0
- package/dist/audit/delegation-event.d.ts +215 -0
- package/dist/audit/delegation-event.js +113 -0
- package/dist/cli/audit-specialists.d.ts +113 -0
- package/dist/cli/audit-specialists.js +220 -0
- package/dist/cli/doctor.d.ts +65 -0
- package/dist/cli/doctor.js +258 -0
- package/dist/cli/hook.d.ts +40 -8
- package/dist/cli/hook.js +305 -8
- package/dist/cli/index.js +7 -0
- package/dist/cli/install/manifest-schema.d.ts +6 -6
- package/dist/cli/install/settings-merge.js +20 -0
- package/dist/config/tier-map.js +22 -1
- package/dist/registry/loader.d.ts +6 -6
- package/hooks/blocked-paths-enforcer.sh +39 -0
- package/hooks/delegation-capture.sh +158 -0
- package/hooks/settings-protection.sh +39 -0
- package/package.json +1 -1
- package/scripts/dist-regression-gate.sh +46 -2
|
@@ -114,6 +114,24 @@ Consumer projects may extend the roster via `.rea/agents/` and profile YAMLs, bu
|
|
|
114
114
|
4. Delegate with full context — include file paths, constraints from policy.yaml, acceptance criteria, and the commit-discipline note above
|
|
115
115
|
5. Verify outputs before reporting completion — do not trust agent summaries at face value. Read the files, check git status, confirm the build.
|
|
116
116
|
|
|
117
|
+
## Self-review when the orchestrator implements directly (0.29.0+)
|
|
118
|
+
|
|
119
|
+
There are sessions where the orchestrator must implement work itself instead of dispatching:
|
|
120
|
+
|
|
121
|
+
- Subagent dispatch is unavailable (no Task tool in the current harness, exempt-subagent scenario).
|
|
122
|
+
- The task is narrowly scoped to a single small surface where the dispatch overhead exceeds the implementation cost.
|
|
123
|
+
- A codex round between specialist hand-offs is being used as the de facto specialist tier (the "Option C" iteration pattern from the 0.29.0 marathon).
|
|
124
|
+
|
|
125
|
+
In every such case, you MUST still apply the specialist discipline that delegation would have enforced. This is not optional — the structural risk of "one Opus turn implements five surfaces" is exactly the failure mode that principal-engineer review caught in the 0.28.0 cycle (manifest glob-injection P1 + cache-staleness P2, both pre-commit). Reach the same closure shape by:
|
|
126
|
+
|
|
127
|
+
1. **Name the specialists you are channeling.** Before each surface, state which specialist's discipline applies (e.g. "shell-scripting-specialist + adversarial-test-specialist for the bash gate corpus; typescript-specialist for the CLI; platform-architect for the workflow"). State it out loud so the user can spot a mis-cast role.
|
|
128
|
+
2. **Codex round between surfaces, not just at the end.** A single end-of-build codex round across 5 surfaces buries P1s in noise. One round per surface keeps the signal sharp. The 0.27.0 direct-Bash codex CLI is cheap enough at one Opus turn per round to make this routine.
|
|
129
|
+
3. **Explicit threat-model framing for security-tier changes.** When patching a hook, name the bypass class, the conservative-vs-narrow reading, and the sibling shapes the class implies. Refuse to commit until the corpus enumerates every shape the class includes.
|
|
130
|
+
4. **Single-commit-per-PR discipline still applies.** Squash local work before push. The pre-push gate's stateless codex review runs once against the squashed diff; granular commits multiply the review burden without surfacing new findings.
|
|
131
|
+
5. **Defer ruthlessly.** Trimmed-scope greenlights from the user are a maximum, not a minimum. The marathon's 0.28.0 lesson was "principal-engineer trimmed the 11-item plate to 6 with crisp deferral reasons." Apply the same lens during direct-implementation: if surface 6 needs structural rework, defer it to the next minor with the reason in the changeset rather than ship a half-baked closure.
|
|
132
|
+
|
|
133
|
+
A self-review checkpoint after each surface (read the diff back, run the targeted tests, fire codex against the working tree) IS the specialist tier when no subagent is in the path. Skip the checkpoint and the structural lesson resets.
|
|
134
|
+
|
|
117
135
|
## The Plan / Build / Review Loop (default workflow)
|
|
118
136
|
|
|
119
137
|
REA's default engineering workflow is three-legged, with Review performed by a different model than Build:
|
package/dist/audit/append.d.ts
CHANGED
|
@@ -83,3 +83,4 @@ export type { AuditRecord, EmissionSource } from '../gateway/middleware/audit-ty
|
|
|
83
83
|
export { Tier, InvocationStatus } from '../policy/types.js';
|
|
84
84
|
export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, type CodexVerdict, type CodexReviewMetadata, } from './codex-event.js';
|
|
85
85
|
export { LOCAL_REVIEW_TOOL_NAME, LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME, LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME, LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME, LOCAL_REVIEW_SERVER_NAME, type LocalReviewVerdict, type LocalReviewMetadata, type LocalReviewSkippedOverrideMetadata, type LocalReviewSkippedUnavailableMetadata, } from './local-review-event.js';
|
|
86
|
+
export { DELEGATION_SIGNAL_TOOL_NAME, DELEGATION_SIGNAL_SERVER_NAME, DELEGATION_SIGNAL_SCHEMA_VERSION, DelegationSignalMetadataSchema, type DelegationTool, type DelegationSignalMetadata, type DelegationSignalMetadataParsed, } from './delegation-event.js';
|
package/dist/audit/append.js
CHANGED
|
@@ -204,3 +204,4 @@ export async function appendAuditRecord(baseDir, input) {
|
|
|
204
204
|
export { Tier, InvocationStatus } from '../policy/types.js';
|
|
205
205
|
export { CODEX_REVIEW_TOOL_NAME, CODEX_REVIEW_SERVER_NAME, } from './codex-event.js';
|
|
206
206
|
export { LOCAL_REVIEW_TOOL_NAME, LOCAL_REVIEW_SKIPPED_OVERRIDE_TOOL_NAME, LOCAL_REVIEW_SKIPPED_UNAVAILABLE_TOOL_NAME, LOCAL_REVIEW_PREFLIGHT_SKIPPED_TOOL_NAME, LOCAL_REVIEW_SERVER_NAME, } from './local-review-event.js';
|
|
207
|
+
export { DELEGATION_SIGNAL_TOOL_NAME, DELEGATION_SIGNAL_SERVER_NAME, DELEGATION_SIGNAL_SCHEMA_VERSION, DelegationSignalMetadataSchema, } from './delegation-event.js';
|
|
@@ -0,0 +1,215 @@
|
|
|
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 declare 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 declare 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 declare const DELEGATION_SIGNAL_SCHEMA_VERSION: 1;
|
|
90
|
+
/**
|
|
91
|
+
* The two valid delegation-tool values. `Agent` and `Skill` are the
|
|
92
|
+
* exact tool names emitted by Claude Code's PreToolUse hook payload —
|
|
93
|
+
* anything else is a misclassification at the hook layer.
|
|
94
|
+
*/
|
|
95
|
+
export type DelegationTool = 'Agent' | 'Skill';
|
|
96
|
+
/**
|
|
97
|
+
* Canonical metadata payload for `rea.delegation_signal`. Embedded
|
|
98
|
+
* under `metadata` on the audit record. The audit-record envelope
|
|
99
|
+
* itself supplies `tool_name`, `server_name`, `session_id`, `timestamp`,
|
|
100
|
+
* `prev_hash`, `hash`, `redacted_fields`, etc. — keep those out of
|
|
101
|
+
* metadata.
|
|
102
|
+
*/
|
|
103
|
+
export interface DelegationSignalMetadata {
|
|
104
|
+
/**
|
|
105
|
+
* Always `1` for the 0.29.0 shape. Carried as a literal so future
|
|
106
|
+
* v2-aware readers can distinguish records they understand from
|
|
107
|
+
* records they don't.
|
|
108
|
+
*/
|
|
109
|
+
schema_version: typeof DELEGATION_SIGNAL_SCHEMA_VERSION;
|
|
110
|
+
/**
|
|
111
|
+
* Which Claude Code surface fired the hook — `'Agent'` for the
|
|
112
|
+
* subagent dispatch tool, `'Skill'` for the skill invocation tool.
|
|
113
|
+
* The reader CLI groups records on `subagent_type` regardless of
|
|
114
|
+
* `delegation_tool` (a `deep-dive` skill and a `deep-dive` agent
|
|
115
|
+
* roll into the same bucket), but the field is retained for forensic
|
|
116
|
+
* queries that want to distinguish the two.
|
|
117
|
+
*/
|
|
118
|
+
delegation_tool: DelegationTool;
|
|
119
|
+
/**
|
|
120
|
+
* For `Agent`: the value of `tool_input.subagent_type` at the hook
|
|
121
|
+
* (e.g. `'rea-orchestrator'`).
|
|
122
|
+
*
|
|
123
|
+
* For `Skill`: the value of `tool_input.skill` (e.g. `'deep-dive'`).
|
|
124
|
+
*
|
|
125
|
+
* Always passed through `redactSecrets` before landing here. If a
|
|
126
|
+
* planted secret pattern fires, this field is `'[REDACTED]'` and
|
|
127
|
+
* the matching pattern name appears in the record's
|
|
128
|
+
* `redacted_fields` envelope.
|
|
129
|
+
*
|
|
130
|
+
* Reader CLI groups records on this field.
|
|
131
|
+
*/
|
|
132
|
+
subagent_type: string;
|
|
133
|
+
/**
|
|
134
|
+
* The session id Claude Code attached to the hook payload — the same
|
|
135
|
+
* value the harness uses for its own correlation. Captured verbatim
|
|
136
|
+
* so a future per-session breakdown (deferred to 0.29.1) can group
|
|
137
|
+
* records without scanning the entire chain.
|
|
138
|
+
*
|
|
139
|
+
* Distinct from the audit envelope's `session_id`, which uses the
|
|
140
|
+
* caller's session ("external" for the CLI subcommand). The
|
|
141
|
+
* envelope's `session_id` says WHO wrote the record; this field says
|
|
142
|
+
* WHO Claude Code thinks is delegating.
|
|
143
|
+
*/
|
|
144
|
+
session_id_observed: string;
|
|
145
|
+
/**
|
|
146
|
+
* When the dispatching agent is itself a subagent, this is the
|
|
147
|
+
* parent's subagent_type at hook-fire time. Drawn from
|
|
148
|
+
* `CLAUDE_PARENT_SUBAGENT` / `tool_input.parent_subagent_type` when
|
|
149
|
+
* present; `null` for top-level dispatches.
|
|
150
|
+
*
|
|
151
|
+
* Like `subagent_type`, redacted before landing here.
|
|
152
|
+
*/
|
|
153
|
+
parent_subagent_type: string | null;
|
|
154
|
+
/**
|
|
155
|
+
* SHA-256 hex digest of `tool_input.description` (Agent) or
|
|
156
|
+
* `tool_input.prompt` (Skill). When neither is present an empty
|
|
157
|
+
* string is hashed — the resulting digest is the well-known
|
|
158
|
+
* `e3b0c4...` constant, which readers can recognize as "no prompt".
|
|
159
|
+
*
|
|
160
|
+
* # Why hash, not redact
|
|
161
|
+
*
|
|
162
|
+
* The prompt is the actionable content of the delegation — it
|
|
163
|
+
* routinely names files, customers, internal URLs, half-finished
|
|
164
|
+
* thoughts. Redacting it via pattern-matching is best-effort; hashing
|
|
165
|
+
* it is total. The collision-resistance of SHA-256 still lets two
|
|
166
|
+
* identical prompts produce identical hashes, which is enough for
|
|
167
|
+
* the delegation-pattern queries this telemetry exists to support.
|
|
168
|
+
*/
|
|
169
|
+
invocation_description_sha256: string;
|
|
170
|
+
/**
|
|
171
|
+
* ISO-8601 timestamp Claude Code attached to the hook event, when
|
|
172
|
+
* present. Distinct from the audit-record envelope's `timestamp`
|
|
173
|
+
* (which is the moment the CLI subcommand wrote the line). Both
|
|
174
|
+
* fields are useful: the envelope timestamp orders the chain,
|
|
175
|
+
* `hook_event_timestamp` orders the underlying events.
|
|
176
|
+
*/
|
|
177
|
+
hook_event_timestamp?: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Strict-mode zod schema for the metadata payload. Unknown fields are
|
|
181
|
+
* rejected — a future v2 producer must bump `DELEGATION_SIGNAL_SCHEMA_VERSION`
|
|
182
|
+
* AND update this schema in the same commit, otherwise v1 readers fail
|
|
183
|
+
* loud rather than silently dropping new fields.
|
|
184
|
+
*
|
|
185
|
+
* The schema is exported so the CLI subcommand validates its OWN
|
|
186
|
+
* emitted metadata before passing it to `appendAuditRecord` — defense
|
|
187
|
+
* in depth against a future refactor that wires the field set
|
|
188
|
+
* incorrectly. (Same posture as `loadPolicy` self-validation.)
|
|
189
|
+
*/
|
|
190
|
+
export declare const DelegationSignalMetadataSchema: z.ZodObject<{
|
|
191
|
+
schema_version: z.ZodLiteral<1>;
|
|
192
|
+
delegation_tool: z.ZodUnion<[z.ZodLiteral<"Agent">, z.ZodLiteral<"Skill">]>;
|
|
193
|
+
subagent_type: z.ZodString;
|
|
194
|
+
session_id_observed: z.ZodString;
|
|
195
|
+
parent_subagent_type: z.ZodUnion<[z.ZodString, z.ZodNull]>;
|
|
196
|
+
invocation_description_sha256: z.ZodString;
|
|
197
|
+
hook_event_timestamp: z.ZodOptional<z.ZodString>;
|
|
198
|
+
}, "strict", z.ZodTypeAny, {
|
|
199
|
+
schema_version: 1;
|
|
200
|
+
delegation_tool: "Agent" | "Skill";
|
|
201
|
+
subagent_type: string;
|
|
202
|
+
session_id_observed: string;
|
|
203
|
+
parent_subagent_type: string | null;
|
|
204
|
+
invocation_description_sha256: string;
|
|
205
|
+
hook_event_timestamp?: string | undefined;
|
|
206
|
+
}, {
|
|
207
|
+
schema_version: 1;
|
|
208
|
+
delegation_tool: "Agent" | "Skill";
|
|
209
|
+
subagent_type: string;
|
|
210
|
+
session_id_observed: string;
|
|
211
|
+
parent_subagent_type: string | null;
|
|
212
|
+
invocation_description_sha256: string;
|
|
213
|
+
hook_event_timestamp?: string | undefined;
|
|
214
|
+
}>;
|
|
215
|
+
export type DelegationSignalMetadataParsed = z.infer<typeof DelegationSignalMetadataSchema>;
|
|
@@ -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 {};
|