@quantiya/codevibe-claude-plugin 1.0.35 → 1.0.37

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 (61) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/server.js +13 -13
  3. package/hooks/permission-request.sh +32 -2
  4. package/hooks/stop.sh +22 -2
  5. package/node_modules/@quantiya/codevibe-core/dist/appsync/__tests__/appsync-client.test.d.ts +1 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +139 -1
  7. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +5 -0
  8. package/node_modules/@quantiya/codevibe-core/dist/audit-keys/__tests__/audit-keys-parity.test.d.ts +1 -0
  9. package/node_modules/@quantiya/codevibe-core/dist/audit-keys/index.d.ts +41 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/auth/__tests__/auth-telemetry.test.d.ts +1 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/auth/auth-telemetry.d.ts +29 -8
  12. package/node_modules/@quantiya/codevibe-core/dist/index.d.ts +4 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/index.js +194 -33
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-bootstrap.test.d.ts +1 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-failure-recourse.test.d.ts +1 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-save.test.d.ts +1 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-seat-picker.test.d.ts +1 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-telemetry.test.d.ts +1 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-test-agents.test.d.ts +1 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-types.test.d.ts +1 -0
  21. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/setup-wizard.test.d.ts +1 -0
  22. package/node_modules/@quantiya/codevibe-core/dist/orchestration/__tests__/v1-options.test.d.ts +1 -0
  23. package/node_modules/@quantiya/codevibe-core/dist/orchestration/detect-agents.d.ts +56 -0
  24. package/node_modules/@quantiya/codevibe-core/dist/orchestration/index.d.ts +3 -0
  25. package/node_modules/@quantiya/codevibe-core/dist/orchestration/orchestration-cli.d.ts +12 -0
  26. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-bootstrap.d.ts +146 -0
  27. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-failure-recourse.d.ts +23 -0
  28. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-save.d.ts +47 -0
  29. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-seat-picker.d.ts +72 -0
  30. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-telemetry.d.ts +54 -0
  31. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-test-agents.d.ts +108 -0
  32. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-types.d.ts +140 -0
  33. package/node_modules/@quantiya/codevibe-core/dist/orchestration/setup-wizard.d.ts +57 -0
  34. package/node_modules/@quantiya/codevibe-core/dist/orchestration/v1-options.d.ts +108 -0
  35. package/node_modules/@quantiya/codevibe-core/dist/reviewer/__tests__/integration.test.d.ts +1 -0
  36. package/node_modules/@quantiya/codevibe-core/dist/reviewer/__tests__/mocks.test.d.ts +1 -0
  37. package/node_modules/@quantiya/codevibe-core/dist/reviewer/__tests__/output-parser.test.d.ts +1 -0
  38. package/node_modules/@quantiya/codevibe-core/dist/reviewer/__tests__/registry.test.d.ts +1 -0
  39. package/node_modules/@quantiya/codevibe-core/dist/reviewer/__tests__/subprocess.test.d.ts +1 -0
  40. package/node_modules/@quantiya/codevibe-core/dist/reviewer/index.d.ts +15 -0
  41. package/node_modules/@quantiya/codevibe-core/dist/reviewer/mocks.d.ts +80 -0
  42. package/node_modules/@quantiya/codevibe-core/dist/reviewer/output-parser.d.ts +95 -0
  43. package/node_modules/@quantiya/codevibe-core/dist/reviewer/provider.d.ts +153 -0
  44. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/claude-live-smoke.test.d.ts +1 -0
  45. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/claude.test.d.ts +1 -0
  46. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/codex-live-smoke.test.d.ts +1 -0
  47. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/codex.test.d.ts +1 -0
  48. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/gemini-live-smoke.test.d.ts +1 -0
  49. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/__tests__/gemini.test.d.ts +1 -0
  50. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/claude.d.ts +59 -0
  51. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/codex.d.ts +67 -0
  52. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/common.d.ts +25 -0
  53. package/node_modules/@quantiya/codevibe-core/dist/reviewer/providers/gemini.d.ts +108 -0
  54. package/node_modules/@quantiya/codevibe-core/dist/reviewer/registry.d.ts +87 -0
  55. package/node_modules/@quantiya/codevibe-core/dist/reviewer/subprocess.d.ts +117 -0
  56. package/node_modules/@quantiya/codevibe-core/dist/reviewer/types.d.ts +101 -0
  57. package/node_modules/@quantiya/codevibe-core/dist/types/index.d.ts +2 -0
  58. package/node_modules/@quantiya/codevibe-core/dist/types/orchestration.d.ts +57 -0
  59. package/node_modules/@quantiya/codevibe-core/dist/types/reviewer.d.ts +67 -0
  60. package/node_modules/@quantiya/codevibe-core/dist/types/session.d.ts +16 -0
  61. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ import { type ReviewerProvider, type ReviewerSpec } from '../provider.js';
2
+ import { type SubprocessOutcome } from '../subprocess.js';
3
+ import type { ReviewerVerdict } from '../types.js';
4
+ /**
5
+ * Built command shape — split out so tests can inspect args / env without
6
+ * spawning a process. `runReviewer` consumes this shape directly.
7
+ */
8
+ export interface BuiltCommand {
9
+ command: string;
10
+ args: string[];
11
+ env: NodeJS.ProcessEnv;
12
+ }
13
+ /** Construction options. */
14
+ export interface ClaudeReviewerProviderOptions {
15
+ /** Override the `claude` executable path. Production callers pass nothing
16
+ * (PATH lookup); integration tests can stub Claude with a shell-script
17
+ * fixture. */
18
+ executable?: string;
19
+ }
20
+ /**
21
+ * Real Claude Code reviewer provider. Wraps the `claude` CLI.
22
+ *
23
+ * Construct with `new ClaudeReviewerProvider()` for the standard `claude`
24
+ * binary, or `new ClaudeReviewerProvider({ executable: '/opt/bin/mock-claude' })`
25
+ * for tests that point at a fixture CLI on a controlled PATH.
26
+ */
27
+ export declare class ClaudeReviewerProvider implements ReviewerProvider {
28
+ private readonly executable;
29
+ constructor(opts?: ClaudeReviewerProviderOptions);
30
+ evaluate(spec: ReviewerSpec, gateId: string): Promise<ReviewerVerdict>;
31
+ }
32
+ /**
33
+ * Construct the Claude CLI invocation. Split out from `evaluate` so unit
34
+ * tests can assert on the built arg list + env without actually spawning a
35
+ * process.
36
+ *
37
+ * Locked flag set:
38
+ * - `--print` — non-interactive mode; emit the response and exit, rather
39
+ * than waiting for further turns. Required so the subprocess actually
40
+ * terminates on its own.
41
+ * - `--allowed-tools <CSV>` — design-locked sandbox. `ReviewerSpec.tool_allowlist`
42
+ * is populated from the engine's policy; the provider trusts the spec
43
+ * rather than hard-coding the list.
44
+ * - `--model <hint>` — optional; omitted when `spec.model_hint` is `null`,
45
+ * deferring to Claude's default.
46
+ *
47
+ * # QUORUM_REVIEWER_SUBPROCESS env var
48
+ *
49
+ * Set unconditionally to `'1'` in the child's environment. See module docs
50
+ * for the rationale and why `--bare` was rejected as the primary defense.
51
+ */
52
+ export declare function buildCommand(executable: string, spec: ReviewerSpec): BuiltCommand;
53
+ /**
54
+ * Map the raw subprocess outcome into a `ReviewerVerdict` or throw a
55
+ * structured `ReviewerErrorClass`. Exit code is checked first — a crashed
56
+ * CLI that happened to print something valid on stdout should still be
57
+ * treated as a spawn failure, not a silently-accepted verdict.
58
+ */
59
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome): ReviewerVerdict;
@@ -0,0 +1,67 @@
1
+ import { type ReviewerProvider, type ReviewerSpec } from '../provider.js';
2
+ import { type SubprocessOutcome } from '../subprocess.js';
3
+ import type { ReviewerVerdict } from '../types.js';
4
+ import type { BuiltCommand } from './claude.js';
5
+ /** Construction options. */
6
+ export interface CodexReviewerProviderOptions {
7
+ /** Override the `codex` executable path. Production callers pass nothing
8
+ * (PATH lookup); integration tests can stub Codex with a shell-script
9
+ * fixture. */
10
+ executable?: string;
11
+ }
12
+ /**
13
+ * Real Codex CLI reviewer provider. Wraps the `codex exec` subcommand.
14
+ */
15
+ export declare class CodexReviewerProvider implements ReviewerProvider {
16
+ private readonly executable;
17
+ constructor(opts?: CodexReviewerProviderOptions);
18
+ evaluate(spec: ReviewerSpec, gateId: string): Promise<ReviewerVerdict>;
19
+ }
20
+ /**
21
+ * Construct the Codex CLI invocation. Split out from `evaluate` so unit
22
+ * tests can assert on the built arg list without actually spawning a
23
+ * process.
24
+ *
25
+ * Locked flag set:
26
+ * - `exec` — the non-interactive subcommand (the interactive default
27
+ * would block forever waiting for user input).
28
+ * - `--sandbox read-only` — design-locked read-only sandbox.
29
+ * - `--skip-git-repo-check` — reviewer subprocesses must run in any cwd,
30
+ * not just git working trees.
31
+ * - `--color never` — strip ANSI escapes from any incidental stdout
32
+ * output (the JSONL stream is not affected, but logs and error
33
+ * messages are).
34
+ * - `--json` — emit JSONL events to stdout for token-count parsing.
35
+ * - `--ephemeral` — do NOT write a session JSONL under
36
+ * `~/.codex/sessions/`.
37
+ * - `--output-last-message <path>` — write the model's final agent
38
+ * message verbatim to `path`.
39
+ * - `--model <hint>` — optional; omitted when `spec.model_hint` is `null`.
40
+ * - `-` (final arg) — read prompt from stdin.
41
+ */
42
+ export declare function buildCommand(executable: string, spec: ReviewerSpec, lastMessagePath: string): BuiltCommand;
43
+ /**
44
+ * Map the raw subprocess outcome + the file Codex wrote into a
45
+ * `ReviewerVerdict` or throw a structured `ReviewerErrorClass`. See
46
+ * `claude.ts::buildVerdict` for the shared safety rule (non-zero exit
47
+ * overrides any parseable stdout / file).
48
+ */
49
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome, lastMessage: string): ReviewerVerdict;
50
+ /**
51
+ * Sum `usage.input_tokens + usage.output_tokens` across every
52
+ * `turn.completed` JSONL event in `stdout`. Returns `null` when no
53
+ * `turn.completed` event reported any token count, so dashboards can
54
+ * distinguish "no data" from a real zero.
55
+ *
56
+ * Lenient: malformed JSONL lines are silently skipped (Codex's stream is
57
+ * designed to be append-only, so partial flushes during timeout could
58
+ * leave a final truncated line).
59
+ */
60
+ export declare function sumCodexTokens(stdout: string): number | null;
61
+ /**
62
+ * Generate a unique-per-spawn path for `--output-last-message`. Lives in
63
+ * the OS temp dir; we own its lifecycle (create on codex's side, read +
64
+ * delete on ours). Per-process pid + UUID is enough — no race risk across
65
+ * concurrent reviewers in the same engine run.
66
+ */
67
+ export declare function makeLastMessagePath(): string;
@@ -0,0 +1,25 @@
1
+ import type { AgentKind } from '../types.js';
2
+ import type { ReviewerError } from '../provider.js';
3
+ import type { SubprocessError } from '../subprocess.js';
4
+ /**
5
+ * Map a subprocess-layer error into a `ReviewerError` for the given agent.
6
+ * Mirrors Rust's `providers::claude::map_subprocess_error` byte-for-byte:
7
+ *
8
+ * - `spawn_failed` → `ReviewerError::SpawnFailed { agent, reason }`
9
+ * - `timeout` → `ReviewerError::Timeout { agent, elapsed_ms }`
10
+ * - `io` → `ReviewerError::SpawnFailed { agent, reason: "io error: <msg>" }`
11
+ * (deliberate; an IO failure during spawn is a spawn failure from the
12
+ * audit log's perspective — the reviewer never produced a verdict)
13
+ * - `cancelled` → `ReviewerError` cancelled (no agent attribution; the
14
+ * Rust enum omits the agent field on Cancelled because cancellation
15
+ * can fire on any reviewer in flight)
16
+ *
17
+ * The Rust source's `map_subprocess_error` handles only three variants
18
+ * (`SpawnFailed`, `Timeout`, `Io`) because the Rust subprocess layer
19
+ * doesn't have a Cancelled variant — engine-driven cancellation in Rust
20
+ * is handled at a higher layer via `tokio::select!` against the abort
21
+ * future, never surfacing as a `SubprocessError`. The TS port carries
22
+ * cancellation in the subprocess layer (via `AbortSignal`), so we
23
+ * include it here.
24
+ */
25
+ export declare function mapSubprocessError(agent: AgentKind, err: SubprocessError): ReviewerError;
@@ -0,0 +1,108 @@
1
+ import { type ReviewerProvider, type ReviewerSpec } from '../provider.js';
2
+ import { type SubprocessOutcome } from '../subprocess.js';
3
+ import type { ReviewerVerdict } from '../types.js';
4
+ import type { BuiltCommand } from './claude.js';
5
+ /** Construction options. */
6
+ export interface GeminiReviewerProviderOptions {
7
+ /** Override the `gemini` executable path. Production callers pass nothing
8
+ * (PATH lookup); integration tests can stub Gemini with a shell-script
9
+ * fixture. */
10
+ executable?: string;
11
+ }
12
+ /**
13
+ * JSON envelope shape emitted by `gemini --output-format json`. We only care
14
+ * about `response`; the other fields (`session_id`, `stats`) are consumed by
15
+ * the JSON deserializer but not surfaced on `ReviewerVerdict` directly.
16
+ *
17
+ * Tolerates missing or extra top-level keys for cross-version compatibility.
18
+ */
19
+ export interface GeminiEnvelope {
20
+ response: string;
21
+ /** Gemini 0.38.2 emits this; older versions may not. */
22
+ session_id?: string;
23
+ stats?: GeminiStats;
24
+ }
25
+ export interface GeminiStats {
26
+ /** Map of model name → per-model stats. Token usage MUST be summed across
27
+ * every entry (R2 "telemetry timebomb" lesson — picking an arbitrary one
28
+ * via `Object.values()[0]` would under-report when Gemini reports
29
+ * auxiliary models). */
30
+ models?: Record<string, GeminiModelStats>;
31
+ }
32
+ export interface GeminiModelStats {
33
+ tokens?: {
34
+ /** Prompt + response tokens combined. Populated by Gemini's
35
+ * `stats.models.<name>.tokens.total`; used for cost telemetry. */
36
+ total?: number;
37
+ };
38
+ }
39
+ /**
40
+ * Real Gemini CLI reviewer provider. Wraps the `gemini` binary.
41
+ */
42
+ export declare class GeminiReviewerProvider implements ReviewerProvider {
43
+ private readonly executable;
44
+ constructor(opts?: GeminiReviewerProviderOptions);
45
+ evaluate(spec: ReviewerSpec, gateId: string): Promise<ReviewerVerdict>;
46
+ }
47
+ /**
48
+ * Construct the Gemini CLI invocation. Split out from `evaluate` so unit
49
+ * tests can assert on the built arg list + env without actually spawning a
50
+ * process.
51
+ *
52
+ * Locked flag set:
53
+ * - `-p ''` — non-interactive mode, empty inline prompt so the full prompt
54
+ * is read from stdin (consistent with Claude path).
55
+ * - `--approval-mode plan` — read-only sandbox.
56
+ * - `--output-format json` — structured reply envelope that survives
57
+ * user-hook stdout pollution.
58
+ * - `--model <hint>` — optional; omitted when `spec.model_hint` is `null`.
59
+ *
60
+ * `spec.tool_allowlist` is **intentionally unused** for Gemini. Plan mode
61
+ * is the design-locked sandbox; mapping the Claude-style allowlist to
62
+ * Gemini's (deprecated) `--allowed-tools` flag would hit the known
63
+ * non-interactive bugs flagged in the module docs.
64
+ *
65
+ * # QUORUM_REVIEWER_SUBPROCESS env var
66
+ *
67
+ * Set unconditionally to `'1'` in the child's environment. The user's
68
+ * Gemini plugin checks this at the top of `hooks/common.sh` and
69
+ * short-circuits every hook — without this gate, reviewer spawns would
70
+ * each create a new backend session and mark the user's primary Gemini
71
+ * session INACTIVE (empirically observed 2026-04-21 during Phase 2c.2
72
+ * local testing).
73
+ */
74
+ export declare function buildCommand(executable: string, spec: ReviewerSpec): BuiltCommand;
75
+ /**
76
+ * Map the raw subprocess outcome into a `ReviewerVerdict` or throw a
77
+ * structured `ReviewerErrorClass`. See `claude.ts::buildVerdict` for the
78
+ * shared safety rule (non-zero exit overrides any parseable stdout).
79
+ */
80
+ export declare function buildVerdict(spec: ReviewerSpec, gateId: string, outcome: SubprocessOutcome): ReviewerVerdict;
81
+ /**
82
+ * Parse the FIRST complete JSON object in `stdout`, discarding any trailing
83
+ * bytes. User-level hooks (e.g., CodeVibe's own Gemini plugin hooks
84
+ * installed in `~/.gemini/settings.json`) append log lines to stdout AFTER
85
+ * the model's reply envelope. This walks the text byte-by-byte tracking
86
+ * brace depth + string escapes to find where the first JSON object ends,
87
+ * then `JSON.parse` that slice — the equivalent of Rust's
88
+ * `serde_json::Deserializer::into_iter().next()` for our purposes.
89
+ *
90
+ * Returns `null` if stdout contains no valid JSON value at all, the
91
+ * extracted slice fails strict JSON parsing, OR the parsed value isn't a
92
+ * shape-conformant `GeminiEnvelope` (i.e. missing or non-string
93
+ * `response`). The caller routes any of these to ParseFailure with raw
94
+ * stdout — matching Rust's serde-deserialization-failure path.
95
+ *
96
+ * # Why runtime shape validation
97
+ *
98
+ * Rust's `serde_json::Deserializer` rejects `{}` or `{"response":123}`
99
+ * during deserialization because `GeminiEnvelope { response: String, ... }`
100
+ * makes the field required at the type-system layer. TypeScript's
101
+ * `JSON.parse(slice) as GeminiEnvelope` is a compile-time-only assertion
102
+ * with no runtime check, so we reproduce serde's behavior with an
103
+ * explicit `validateGeminiEnvelope` step. Without this, a syntactically-
104
+ * valid-but-shape-bad envelope would land an `undefined.replace()`
105
+ * TypeError up the call stack instead of a structured ParseFailure
106
+ * (R1 finding on Phase 2f.1.b round 1).
107
+ */
108
+ export declare function parseFirstJsonEnvelope(stdout: string): GeminiEnvelope | null;
@@ -0,0 +1,87 @@
1
+ import { type ReviewerProvider, type ReviewerSpec } from './provider.js';
2
+ import type { AgentKind, ReviewerVerdict } from './types.js';
3
+ /**
4
+ * Per-agent reviewer dispatch.
5
+ *
6
+ * Build with the fluent `with()` builder for the common production
7
+ * case (register all known agents up front), or the mutating
8
+ * `register()` helper when the set is determined at runtime.
9
+ * `providerFor` exposes the underlying provider for callers (e.g.,
10
+ * test harnesses) that need direct access without going through the
11
+ * `evaluate` method.
12
+ *
13
+ * `ReviewerRegistry` itself implements `ReviewerProvider` so it
14
+ * slots into anything that takes a single provider — including
15
+ * another `ReviewerRegistry`. (Compose-of-compose works for free.)
16
+ */
17
+ export declare class ReviewerRegistry implements ReviewerProvider {
18
+ private readonly providers;
19
+ /**
20
+ * Builder-style register: returns `this` with `agent` mapped to
21
+ * `provider`. Intended for the production "register all three at
22
+ * startup" pattern:
23
+ *
24
+ * ```ts
25
+ * const registry = new ReviewerRegistry()
26
+ * .with('claude', new ClaudeReviewerProvider())
27
+ * .with('gemini', new GeminiReviewerProvider())
28
+ * .with('codex', new CodexReviewerProvider());
29
+ * ```
30
+ *
31
+ * Re-registering the same agent overwrites the previous provider —
32
+ * useful for tests that swap in a fixture provider after construction.
33
+ */
34
+ with(agent: AgentKind, provider: ReviewerProvider): this;
35
+ /**
36
+ * Mutating register. Equivalent to `with()` but returns void; use
37
+ * when chaining isn't readable (e.g., conditional registration in
38
+ * a loop).
39
+ */
40
+ register(agent: AgentKind, provider: ReviewerProvider): void;
41
+ /**
42
+ * Lookup the provider for `agent`. Returns `undefined` if no
43
+ * provider is registered. Callers should prefer using the
44
+ * `evaluate` method on the registry itself, which handles the
45
+ * missing-provider case as a typed `ReviewerError`. This method
46
+ * is exposed for test harnesses that need direct provider access
47
+ * (e.g., to call provider methods that aren't part of the
48
+ * interface).
49
+ */
50
+ providerFor(agent: AgentKind): ReviewerProvider | undefined;
51
+ /**
52
+ * Returns the set of agents that have registered providers, in
53
+ * insertion order. Useful for callers that want to log the
54
+ * configured registry shape or for a future startup-time validator
55
+ * that checks the policy snapshot's reviewer_agents are all covered.
56
+ */
57
+ registeredAgents(): AgentKind[];
58
+ /**
59
+ * Dispatch by `spec.agent` to the registered provider. If no
60
+ * provider is registered for that agent, throw
61
+ * `ReviewerErrorClass` with `kind: 'spawn_failed'` so the engine's
62
+ * existing escalation path handles it. The `reason` field includes
63
+ * the agent name so audit can identify the misconfiguration.
64
+ */
65
+ evaluate(spec: ReviewerSpec, gateId: string): Promise<ReviewerVerdict>;
66
+ }
67
+ /**
68
+ * Production factory: build a registry pre-populated with all three
69
+ * subprocess providers (Claude, Gemini, Codex) using the standard
70
+ * binaries from PATH. Equivalent of Rust's
71
+ * `withSubprocessProviders()` napi factory.
72
+ *
73
+ * Plugins call this once at startup and hand the registry to the
74
+ * engine fan-out path. Inert registrations cost nothing — no
75
+ * subprocess fires until the policy actually references that agent.
76
+ *
77
+ * For tests / dev environments that need to override binary paths,
78
+ * construct the providers manually:
79
+ *
80
+ * ```ts
81
+ * const registry = new ReviewerRegistry()
82
+ * .with('claude', new ClaudeReviewerProvider({ executable: '/opt/bin/claude' }))
83
+ * .with('gemini', new GeminiReviewerProvider({ executable: '/opt/bin/gemini' }))
84
+ * .with('codex', new CodexReviewerProvider({ executable: '/opt/bin/codex' }));
85
+ * ```
86
+ */
87
+ export declare function createSubprocessReviewerRegistry(): ReviewerRegistry;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Successful subprocess run. Exit status may still be failure (non-zero
3
+ * code); the caller decides whether to treat that as a reviewer spawn error
4
+ * or as a parse failure with whatever stdout it produced.
5
+ */
6
+ export interface SubprocessOutcome {
7
+ /** Captured stdout decoded as UTF-8. Reviewers that emit non-UTF-8 are
8
+ * misbehaving by contract; lossy decode keeps the parser running rather
9
+ * than returning an IO error for what is really a "reviewer is broken"
10
+ * scenario. */
11
+ stdout: string;
12
+ /** Captured stderr (same lossy-decode policy). Surfaced to callers so
13
+ * audit records can include it. */
14
+ stderr: string;
15
+ /** Wall-clock milliseconds from `spawn()` to exit. Populated even on
16
+ * non-zero exit because per-reviewer latency is a cost / reliability
17
+ * telemetry input. */
18
+ elapsed_ms: number;
19
+ /** Whether the process exited with status 0. Separate from the stdout
20
+ * contents — a reviewer that printed its verdict and then crashed on
21
+ * shutdown is still "failed" from an audit perspective even if the
22
+ * verdict parses cleanly. */
23
+ exit_success: boolean;
24
+ }
25
+ /**
26
+ * Subprocess-layer errors. Discriminated union; callers (per-agent
27
+ * providers) map these into `ReviewerError` with their own `AgentKind`.
28
+ */
29
+ export type SubprocessError = {
30
+ /** The child process could not be spawned at all (binary not on
31
+ * PATH, permission denied, fork/exec failed). */
32
+ kind: 'spawn_failed';
33
+ reason: string;
34
+ } | {
35
+ /** The child exceeded its `timeout_ms` budget. The child has been
36
+ * (or will be) killed via SIGKILL by the time this error is
37
+ * returned. */
38
+ kind: 'timeout';
39
+ /** The timeout budget in ms, for telemetry. */
40
+ elapsed_ms: number;
41
+ } | {
42
+ /** A non-timeout IO failure occurred while running the subprocess
43
+ * (stdin write failed, stdout read failed, etc.). */
44
+ kind: 'io';
45
+ reason: string;
46
+ } | {
47
+ /** Caller signalled abort via the `signal` option before the child
48
+ * exited. The child has been killed via SIGKILL. Distinct from
49
+ * `timeout` so the audit log can attribute "reviewer cancelled by
50
+ * engine" separately from "reviewer ran past budget." */
51
+ kind: 'cancelled';
52
+ };
53
+ /** Class wrapper so callers can `throw`/`catch` typed errors. */
54
+ export declare class SubprocessErrorClass extends Error {
55
+ readonly detail: SubprocessError;
56
+ constructor(detail: SubprocessError);
57
+ }
58
+ /**
59
+ * Inputs to `runReviewer`. Callers (per-agent providers) build this struct
60
+ * with their agent-specific binary + args + env, then hand off lifecycle
61
+ * management to this module.
62
+ */
63
+ export interface RunReviewerOptions {
64
+ /** Path or command name to spawn. */
65
+ command: string;
66
+ /** Arguments to pass to the command. */
67
+ args: readonly string[];
68
+ /**
69
+ * Environment variables to set. **MUST include
70
+ * `QUORUM_REVIEWER_SUBPROCESS: '1'`** — this is the plugin-isolation env
71
+ * guard that prevents reviewer hook fires from evicting the user's
72
+ * primary plugin session. Each provider is responsible for setting it;
73
+ * this module does NOT inject it automatically because providers may
74
+ * also need to forward `process.env` selectively.
75
+ */
76
+ env: NodeJS.ProcessEnv;
77
+ /** Working directory; falls back to `process.cwd()`. */
78
+ cwd?: string;
79
+ /**
80
+ * Prompt to write to the child's stdin. Always followed by stdin EOF.
81
+ * Most reviewer CLIs finish reading the prompt then begin emitting
82
+ * output — a reviewer that blocks on a stuck stdin pipe is the §3.1
83
+ * test scenario.
84
+ */
85
+ prompt: string;
86
+ /**
87
+ * Wall-clock timeout (ms) bounding the whole spawn-to-exit window,
88
+ * INCLUDING the stdin write. This is the load-bearing detail per §3.1:
89
+ * an earlier (Rust v1) implementation wrapped only the wait-for-exit,
90
+ * which let a child that refused to drain stdin block the parent in
91
+ * pipe-full back-pressure indefinitely. Wrapping write+wait under one
92
+ * timeout is the fix.
93
+ */
94
+ timeout_ms: number;
95
+ /**
96
+ * Optional `AbortSignal` for engine-driven cancellation (e.g., user
97
+ * abort, sibling reviewer hard-rejecting). Aborting after a clean exit
98
+ * is a no-op; aborting mid-flight teardown is a `cancelled` error.
99
+ */
100
+ signal?: AbortSignal;
101
+ }
102
+ /**
103
+ * Run a preconfigured command as a reviewer subprocess.
104
+ *
105
+ * Contract:
106
+ * - Stdin, stdout, stderr are piped (caller cannot override; output capture
107
+ * is the whole purpose of the call).
108
+ * - `prompt` is written to the child's stdin then stdin is closed (EOF).
109
+ * - The whole spawn → write-stdin → wait-for-exit lifecycle races against
110
+ * `timeout_ms` AND any `signal` abort. On timeout / abort the streams
111
+ * are explicitly destroyed and the child is SIGKILL'd before throwing.
112
+ *
113
+ * Throws `SubprocessErrorClass` on failure; the `.detail.kind` discriminator
114
+ * tells the caller which path tripped (`spawn_failed` / `timeout` / `io` /
115
+ * `cancelled`).
116
+ */
117
+ export declare function runReviewer(opts: RunReviewerOptions): Promise<SubprocessOutcome>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Three agent kinds the reviewer subprocess layer can spawn.
3
+ *
4
+ * Lowercase wire format matches `codevibe_policy::AgentKind`'s
5
+ * `#[serde(rename_all = "lowercase")]` attribute. **Append-only after launch**
6
+ * — adding a fourth agent (e.g. a hypothetical fourth CLI) is fine; renaming
7
+ * `claude` → `Claude` would break every audit chain entry that has serialised
8
+ * it.
9
+ */
10
+ export type AgentKind = 'claude' | 'gemini' | 'codex';
11
+ /**
12
+ * Review lens a reviewer seat evaluates through.
13
+ *
14
+ * snake_case wire format matches `codevibe_policy::ReviewerRole`'s
15
+ * `#[serde(rename_all = "snake_case")]` attribute, locked in Phase 2f.0.a.1.
16
+ * **Append-only post-launch** — these strings are cryptographically bound
17
+ * into the audit hash chain once used. Renaming or removing a value would
18
+ * break chain verification on every prior entry. Adding new values is fine
19
+ * (Custom roles slipped to 2.0.1 per the locked design).
20
+ *
21
+ * Code-artifact lenses: architecture, correctness, security.
22
+ * Docs-artifact lenses: accuracy, clarity, completeness.
23
+ * Mixed-artifact composite lenses: architecture_and_accuracy,
24
+ * correctness_and_clarity, security_and_completeness.
25
+ *
26
+ * The AppSync-facing `ReviewerRole` enum in `src/types/reviewer.ts` uses
27
+ * UPPERCASE GraphQL convention; this snake_case version is for the Rust
28
+ * engine wire. Translation between the two layers is the AppSync resolver's
29
+ * job, not the reviewer module's.
30
+ */
31
+ export type ReviewerRole = 'architecture' | 'correctness' | 'security' | 'accuracy' | 'clarity' | 'completeness' | 'architecture_and_accuracy' | 'correctness_and_clarity' | 'security_and_completeness';
32
+ /**
33
+ * Reviewer verdict kinds. Four values, each maps to a different path through
34
+ * the consensus engine.
35
+ *
36
+ * UPPERCASE wire format matches `codevibe_reviewer::VerdictKind`'s
37
+ * `#[serde(rename_all = "UPPERCASE")]` attribute. Append-only post-launch.
38
+ *
39
+ * - `APPROVE` — plan/artifact is sound, proceed.
40
+ * - `REJECT` — plan/artifact has fundamental problems; do not proceed.
41
+ * - `REVISE` — plan/artifact is close but needs specific changes. Requires
42
+ * populated `suggested_changes` per the parser's locked invariant.
43
+ * - `ESCALATE` — reviewer cannot decide confidently; surface to user.
44
+ */
45
+ export type VerdictKind = 'APPROVE' | 'REJECT' | 'REVISE' | 'ESCALATE';
46
+ /**
47
+ * Newtype-equivalent for `ReviewerVerdict.verdict_id`. Mirrors Rust's
48
+ * `VerdictId(pub Uuid)` with `#[serde(transparent)]` — wire form is just the
49
+ * UUID string, no envelope.
50
+ */
51
+ export type VerdictId = string;
52
+ /**
53
+ * One reviewer's verdict at one gate. Records reasoning plus perf/cost
54
+ * metadata for telemetry (review latency, token cost per task, etc.).
55
+ *
56
+ * `gate_id` is a plain UUID string (matches Rust's `Uuid` field — the engine
57
+ * wraps this in its own `GateId` newtype, but the reviewer crate stays
58
+ * agnostic to avoid an engine→reviewer→engine cycle).
59
+ *
60
+ * # Identity
61
+ *
62
+ * Per Phase 2f.0.a's seat/role pivot, verdicts are keyed on `seat_id` +
63
+ * `role`, NOT on `reviewer_agent`. Consensus dedup + audit attribution
64
+ * happen via `seat_id`; `reviewer_agent` stays for cost-attribution metadata
65
+ * only and is NOT a uniqueness key (two seats may share an agent in the
66
+ * single-vendor case).
67
+ *
68
+ * Field ordering + names match the Rust struct verbatim — the audit chain's
69
+ * SHA-256 hashes serialised JSON, so any change to field order or naming
70
+ * would invalidate every prior chain entry. Append-only after launch.
71
+ */
72
+ export interface ReviewerVerdict {
73
+ /** Primary key. UUID v4 string. */
74
+ verdict_id: VerdictId;
75
+ /** Foreign key to `ReviewGate`. Plain UUID string per the reviewer crate's
76
+ * agnostic-to-engine design. */
77
+ gate_id: string;
78
+ /** Seat that produced the verdict. Primary identity key for dedup +
79
+ * audit attribution. Providers MUST copy this from `ReviewerSpec.seat_id`. */
80
+ seat_id: number;
81
+ /** Lens the seat reviewed through. Copied from `ReviewerSpec.role` verbatim. */
82
+ role: ReviewerRole;
83
+ /** Agent that produced the verdict. Descriptive metadata (cost attribution,
84
+ * per-agent reliability); NOT an identity key. */
85
+ reviewer_agent: AgentKind;
86
+ /** The verdict itself. */
87
+ verdict: VerdictKind;
88
+ /** Reviewer's free-form reasoning. */
89
+ reasoning: string;
90
+ /** Ordered list of specific changes. **Required** when `verdict === 'REVISE'`;
91
+ * empty for other verdicts (enforced by the output parser). */
92
+ suggested_changes: string[];
93
+ /** Model identifier used (for cost attribution). */
94
+ model_used: string | null;
95
+ /** Tokens consumed by this reviewer. */
96
+ tokens_used: number | null;
97
+ /** End-to-end latency for this reviewer's verdict (ms). */
98
+ latency_ms: number | null;
99
+ /** ISO 8601 UTC timestamp string when the reviewer returned the verdict. */
100
+ submitted_at: string;
101
+ }
@@ -2,3 +2,5 @@ export * from './events';
2
2
  export * from './session';
3
3
  export * from './encryption';
4
4
  export * from './auth';
5
+ export * from './reviewer';
6
+ export * from './orchestration';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Wire-shape `UserDecisionKind` enum — matches AppSync schema directly
3
+ * (`codevibe-backend/graphql/schema.graphql:716-722`). Append-only post-launch.
4
+ *
5
+ * V1 ships only the 3 no-notes kinds (`ACCEPT`, `REJECT_RESTART`,
6
+ * `ABORT_TASK`); the two `_WITH_NOTES` variants are defined here for
7
+ * completeness against the schema but are NOT in V1's hardcoded option
8
+ * table — they require a `notes: EncryptedPayloadInput` value alongside
9
+ * the kind, which the V1 "type a number" UX has no surface for.
10
+ */
11
+ export declare enum UserDecisionKind {
12
+ ACCEPT = "ACCEPT",
13
+ ACCEPT_WITH_NOTES = "ACCEPT_WITH_NOTES",
14
+ REJECT_WITH_NOTES = "REJECT_WITH_NOTES",
15
+ REJECT_RESTART = "REJECT_RESTART",
16
+ ABORT_TASK = "ABORT_TASK"
17
+ }
18
+ /**
19
+ * Input shape for `applyUserDecision` mutation. Matches AppSync schema
20
+ * (`codevibe-backend/graphql/schema.graphql:775-790`).
21
+ *
22
+ * REQUIRED fields: `gateId`, `taskId`, `sessionId`, `currentRound`, `decision`.
23
+ * OPTIONAL field: `notes` (only used for `_WITH_NOTES` decision kinds, which
24
+ * are V2-only in V1).
25
+ */
26
+ export interface ApplyUserDecisionInput {
27
+ gateId: string;
28
+ taskId: string;
29
+ sessionId: string;
30
+ currentRound: number;
31
+ decision: UserDecisionKind;
32
+ notes?: never;
33
+ }
34
+ /**
35
+ * `UserDecisionAppliedEvent` envelope — returned by `applyUserDecision`
36
+ * mutation AND streamed via `onApplyUserDecision` subscription per
37
+ * Phase 3.b mobile V1 bridge §1.1. Top-level `sessionId` is the
38
+ * subscription filter key (mirrors `GateResolvedEvent` precedent at
39
+ * `schema.graphql:815-821`).
40
+ *
41
+ * `payload` is `AWSJSON` on the wire — AppSync delivers it as a
42
+ * JSON-encoded string that callers must `JSON.parse` before treating
43
+ * as the typed `PostDecisionAction` shape from
44
+ * `codevibe-revision/src/user_decision.rs:109`. V1 desktop-plugin
45
+ * callers do NOT consume `payload` (the engine-determined next-gate
46
+ * action is handled server-side by the orchestration loop, NOT
47
+ * client-side in V1); the field is preserved on the envelope for
48
+ * forward-compat.
49
+ */
50
+ export interface UserDecisionAppliedEvent {
51
+ sessionId: string;
52
+ taskId: string;
53
+ gateId: string;
54
+ decision: UserDecisionKind;
55
+ /** AWSJSON-encoded `PostDecisionAction` shape; opaque to V1 plugin callers. */
56
+ payload: string;
57
+ }