@kal-elsam/kairo-runtime 0.6.0 → 0.7.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 (31) hide show
  1. package/README.md +24 -0
  2. package/package.json +1 -1
  3. package/scripts/cockpit-smoke.mjs +1 -1
  4. package/src/cli.js +59 -0
  5. package/src/global/ink/cockpit-controller.js +10 -0
  6. package/src/global/ink/cockpit-focus.js +18 -3
  7. package/src/global/ink/cockpit-models.js +8 -1
  8. package/src/global/ink/cockpit-reviews.js +62 -0
  9. package/src/global/ink/cockpit-runs.js +11 -2
  10. package/src/global/ink/cockpit-views.js +19 -2
  11. package/src/global/ink/orchestrator-app.js +23 -3
  12. package/src/global/ink/orchestrator-state.js +2 -0
  13. package/src/global/ink/use-orchestrator-data.js +30 -0
  14. package/src/global/paths.js +1 -0
  15. package/src/global/runtime/execution-adapters/codex.js +2 -1
  16. package/src/global/runtime/execution-adapters/create-execution-adapter.js +1 -0
  17. package/src/global/runtime/execution-adapters/index.js +1 -0
  18. package/src/global/runtime/execution-adapters/pi.js +2 -1
  19. package/src/global/runtime/review/index.js +37 -0
  20. package/src/global/runtime/review/review-cli.js +150 -0
  21. package/src/global/runtime/review/review-codex.js +132 -0
  22. package/src/global/runtime/review/review-exec.js +136 -0
  23. package/src/global/runtime/review/review-fs.js +52 -0
  24. package/src/global/runtime/review/review-git.js +212 -0
  25. package/src/global/runtime/review/review-patch.js +122 -0
  26. package/src/global/runtime/review/review-pi.js +168 -0
  27. package/src/global/runtime/review/review-receipts.js +128 -0
  28. package/src/global/runtime/review/review-runner.js +119 -0
  29. package/src/global/runtime/review/review-types.js +108 -0
  30. package/src/global/runtime/review/review-validate.js +280 -0
  31. package/src/global/runtime/write-atomic-json.js +24 -20
package/README.md CHANGED
@@ -301,6 +301,30 @@ Launches `pi --mode json --no-session` (optional `--model`). `read-only` maps to
301
301
  to `--approve`). Custom `PI_CODING_AGENT_DIR` blocks config writes in 0.6.0 but does
302
302
  not block runtime. Kairo does not install Pi or assert subscription/entitlement.
303
303
 
304
+ ### Bounded review (Codex / Pi)
305
+
306
+ Read-only native review against a Git snapshot. Never mutates the repo, never
307
+ auto-fixes, and never persists prompts, diffs, or transcripts. Receipts land under
308
+ `~/.harness/reviews/<reviewId>/receipt.json` (secret-free). Only Codex and Pi are
309
+ `reviewCompatible` in 0.7.0; Cursor / Claude / OpenCode report `false` until audited.
310
+
311
+ ```bash
312
+ kairo review --agent codex
313
+ kairo review --agent pi --base main
314
+ kairo review --agent codex --commit <sha>
315
+ kairo review --agent codex --fail-on high --json
316
+ kairo reviews list [--limit N] [--json]
317
+ kairo reviews show <reviewId> [--json]
318
+ ```
319
+
320
+ `--agent` is always required. Scope is working-tree by default; use `--base` or
321
+ `--commit` (mutually exclusive). Private paths need `--include-private` plus TTY
322
+ confirm or `--yes`/`--confirm`. Exit codes: `0` ok, `1` severity threshold,
323
+ `2` operational/stale/invalid/cancel.
324
+
325
+ Cockpit: **Runs → Reviews** lists receipts and opens a read-only detail view
326
+ (no launch from Cockpit in v1).
327
+
304
328
  Primary governance flow:
305
329
 
306
330
  ```txt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kal-elsam/kairo-runtime",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Pi, Engram, and Graphify.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kal-elSam/harness#readme",
@@ -26,7 +26,7 @@ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
26
26
  const require = createRequire(import.meta.url);
27
27
  const pkg = require(join(root, "package.json"));
28
28
 
29
- assert.equal(pkg.version, "0.6.0");
29
+ assert.equal(pkg.version, "0.7.0");
30
30
  assert.ok(pkg.dependencies["ansi-escapes"]);
31
31
 
32
32
  assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
package/src/cli.js CHANGED
@@ -38,6 +38,7 @@ import { runWorkspaceDetect, runWorkspaceDoctor, runWorkspaceInit, runWorkspaceU
38
38
  import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orchestrator.js";
39
39
  import { runIntelligenceCli } from "./global/intelligence-cli.js";
40
40
  import { runGlobalRun, runGlobalRuns } from "./global/runtime/run-cli.js";
41
+ import { runGlobalReview, runGlobalReviews } from "./global/runtime/review/review-cli.js";
41
42
  import {
42
43
  LEGACY_PACKAGE_NAME,
43
44
  PACKAGE_NAME,
@@ -111,6 +112,12 @@ export async function runCli(argv) {
111
112
  case "runs":
112
113
  await runGlobalRuns(optionsWithPolicy, packageManifest);
113
114
  return;
115
+ case "review":
116
+ await runGlobalReview(optionsWithPolicy, packageManifest);
117
+ return;
118
+ case "reviews":
119
+ await runGlobalReviews(optionsWithPolicy, packageManifest);
120
+ return;
114
121
  case "intelligence":
115
122
  await runIntelligenceCli(optionsWithPolicy, packageManifest);
116
123
  return;
@@ -371,6 +378,11 @@ export function parseArgs(argv) {
371
378
  intelligencePaths: [],
372
379
  runsAction: null,
373
380
  runId: null,
381
+ reviewId: null,
382
+ reviewsAction: null,
383
+ base: null,
384
+ commit: null,
385
+ failOn: null,
374
386
  agent: null,
375
387
  task: null,
376
388
  model: null,
@@ -401,6 +413,10 @@ export function parseArgs(argv) {
401
413
  parseRunsAction(args, options);
402
414
  }
403
415
 
416
+ if (command === "reviews") {
417
+ parseReviewsAction(args, options);
418
+ }
419
+
404
420
  if (command === "intelligence") {
405
421
  parseIntelligenceAction(args, options);
406
422
  }
@@ -495,6 +511,15 @@ export function parseArgs(argv) {
495
511
  else if (arg.startsWith("--timeout=")) options.timeoutMs = parsePositiveInt(arg.slice("--timeout=".length), "timeout") * 1000;
496
512
  else if (arg === "--include-private") options.includePrivate = true;
497
513
  else if (arg === "--cloud-consent") options.cloudConsent = true;
514
+ else if (arg === "--base") options.base = requireFlagValue("--base", args[++index]);
515
+ else if (arg.startsWith("--base=")) options.base = requireFlagValue("--base", arg.slice("--base=".length));
516
+ else if (arg === "--commit") options.commit = requireFlagValue("--commit", args[++index]);
517
+ else if (arg.startsWith("--commit=")) {
518
+ options.commit = requireFlagValue("--commit", arg.slice("--commit=".length));
519
+ } else if (arg === "--fail-on") options.failOn = requireFlagValue("--fail-on", args[++index]);
520
+ else if (arg.startsWith("--fail-on=")) {
521
+ options.failOn = requireFlagValue("--fail-on", arg.slice("--fail-on=".length));
522
+ }
498
523
  else if (arg === "--help" || arg === "-h") options.help = true;
499
524
  else if (arg === "--version" || arg === "-v") options.version = true;
500
525
  else throw new Error(`Unknown option "${arg}".`);
@@ -624,6 +649,26 @@ function parseRunsAction(args, options) {
624
649
  }
625
650
  }
626
651
 
652
+ function parseReviewsAction(args, options) {
653
+ const action = args[0];
654
+ if (!action || action.startsWith("-")) {
655
+ options.reviewsAction = "list";
656
+ return;
657
+ }
658
+ if (!new Set(["list", "show"]).has(action)) {
659
+ throw new Error(`Unknown reviews action "${action}". Use list or show.`);
660
+ }
661
+ args.shift();
662
+ options.reviewsAction = action;
663
+ if (action === "show") {
664
+ const reviewId = args[0];
665
+ if (!reviewId || reviewId.startsWith("-")) {
666
+ throw new Error(`Missing review id. Use: ${formatCliCommand("reviews show <reviewId>")}`);
667
+ }
668
+ options.reviewId = args.shift();
669
+ }
670
+ }
671
+
627
672
  function parseIntelligenceAction(args, options) {
628
673
  const action = args[0];
629
674
  if (!action || action.startsWith("-")) {
@@ -657,6 +702,13 @@ function parsePositiveInt(value, label) {
657
702
  return parsed;
658
703
  }
659
704
 
705
+ function requireFlagValue(flag, value) {
706
+ if (value == null || value === "" || String(value).startsWith("-")) {
707
+ throw new Error(`Missing value for ${flag}.`);
708
+ }
709
+ return value;
710
+ }
711
+
660
712
  function parseScope(value) {
661
713
  if (!SCOPES.has(value)) {
662
714
  throw new Error(`Invalid scope "${value}". Use agent-global or workspace.`);
@@ -673,6 +725,8 @@ function normalizeCommand(command) {
673
725
  if (command === "orchestrator") return "orchestrator";
674
726
  if (command === "run") return "run";
675
727
  if (command === "runs") return "runs";
728
+ if (command === "review") return "review";
729
+ if (command === "reviews") return "reviews";
676
730
  if (command === "intelligence" || command === "intel") return "intelligence";
677
731
  if (command === "setup") return "setup";
678
732
  if (command === "status") return "status";
@@ -747,6 +801,9 @@ Usage:
747
801
  ${cli} runs list [--json] [--limit <n>] [--active-only]
748
802
  ${cli} runs show <runId> [--json] [--limit <n>] [--follow]
749
803
  ${cli} runs stop <runId> [--json]
804
+ ${cli} review --agent codex|pi [--base <ref>|--commit <sha>] [--model <name>] [--include-private] [--yes|--confirm] [--fail-on high|medium|low] [--json]
805
+ ${cli} reviews list [--limit <n>] [--json]
806
+ ${cli} reviews show <reviewId> [--json]
750
807
  ${cli} orchestrator [--json] Read-only agent capability diagnostics
751
808
  ${cli} intelligence [status|models|context|route|ask] [--json]
752
809
  ${cli} intelligence models --backend opencode-go|opencode-zen|opencode
@@ -795,6 +852,8 @@ Commands:
795
852
  Tab switches region only when content is interactive (runs/launch).
796
853
  run Launch a managed agent run with local audit trail.
797
854
  runs List, inspect, or cancel agent runs under ~/.harness/runs/.
855
+ review Bounded read-only review via Codex or Pi; receipts under ~/.harness/reviews/.
856
+ reviews List or show prior review receipts (secret-free).
798
857
  orchestrator Read-only capability registry diagnostics (--json supported).
799
858
  intelligence Harness Engineering layer: backends, context packs, routing, budgets.
800
859
  Local-first (Ollama). Cloud only with --cloud-consent.
@@ -144,6 +144,16 @@ export function reduceCockpitUi(state, action) {
144
144
  returnView: null
145
145
  };
146
146
  }
147
+ if (state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
148
+ const back = ORCHESTRATOR_VIEWS.REVIEWS;
149
+ return {
150
+ ...state,
151
+ view: back,
152
+ listIndex: 0,
153
+ region: defaultRegionForView(back, state.layoutMode),
154
+ returnView: null
155
+ };
156
+ }
147
157
  if (isRunsChildView(state.view)) {
148
158
  return goRunsHub(state);
149
159
  }
@@ -18,6 +18,8 @@ const CONTENT_INTERACTIVE_VIEWS = new Set([
18
18
  ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
19
19
  ORCHESTRATOR_VIEWS.RECENT_RUNS,
20
20
  ORCHESTRATOR_VIEWS.RUN_DETAIL,
21
+ ORCHESTRATOR_VIEWS.REVIEWS,
22
+ ORCHESTRATOR_VIEWS.REVIEW_DETAIL,
21
23
  ORCHESTRATOR_VIEWS.LAUNCH,
22
24
  ORCHESTRATOR_VIEWS.ACTIVITY
23
25
  ]);
@@ -41,7 +43,11 @@ export function defaultRegionForView(view, layoutMode = LAYOUT_MODES.COMPACT) {
41
43
 
42
44
  export function interactiveRegionsFor(state) {
43
45
  const regions = regionsForLayout(state.layoutMode);
44
- if (!isContentInteractiveView(state.view) || state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
46
+ if (
47
+ !isContentInteractiveView(state.view)
48
+ || state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
49
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
50
+ ) {
45
51
  return regions.filter((region) => region === COCKPIT_REGIONS.NAV);
46
52
  }
47
53
  return regions.filter(
@@ -51,7 +57,12 @@ export function interactiveRegionsFor(state) {
51
57
 
52
58
  export function canTabBetweenRegions(state) {
53
59
  if (!isContentInteractiveView(state.view)) return false;
54
- if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) return false;
60
+ if (
61
+ state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
62
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
63
+ ) {
64
+ return false;
65
+ }
55
66
  return interactiveRegionsFor(state).length >= 2;
56
67
  }
57
68
 
@@ -70,7 +81,11 @@ export function routeCockpitKey(state, keyAction) {
70
81
  return { type: "arrow", direction: keyAction.direction };
71
82
  }
72
83
  if (state.region === COCKPIT_REGIONS.CONTENT && isContentInteractiveView(state.view)) {
73
- if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL || state.view === ORCHESTRATOR_VIEWS.LAUNCH) {
84
+ if (
85
+ state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
86
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
87
+ || state.view === ORCHESTRATOR_VIEWS.LAUNCH
88
+ ) {
74
89
  return null;
75
90
  }
76
91
  return {
@@ -250,6 +250,11 @@ export function buildFooterModel({
250
250
  return { text: parts.join(` ${glyphs.bullet} `) };
251
251
  }
252
252
 
253
+ if (view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
254
+ parts.push("Esc Back");
255
+ return { text: parts.join(` ${glyphs.bullet} `) };
256
+ }
257
+
253
258
  if (view === ORCHESTRATOR_VIEWS.CHANGES) {
254
259
  return { text: buildChangesFooterParts(changesPhase).join(` ${glyphs.bullet} `) };
255
260
  }
@@ -263,6 +268,7 @@ export function buildFooterModel({
263
268
  const showTab = view === ORCHESTRATOR_VIEWS.RUNS
264
269
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
265
270
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
271
+ || view === ORCHESTRATOR_VIEWS.REVIEWS
266
272
  || view === ORCHESTRATOR_VIEWS.LAUNCH
267
273
  || view === ORCHESTRATOR_VIEWS.ACTIVITY;
268
274
  if (showTab) {
@@ -280,7 +286,8 @@ export function buildFooterModel({
280
286
  || region === COCKPIT_REGIONS.NAV
281
287
  || view === ORCHESTRATOR_VIEWS.RUNS
282
288
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
283
- || view === ORCHESTRATOR_VIEWS.RECENT_RUNS) {
289
+ || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
290
+ || view === ORCHESTRATOR_VIEWS.REVIEWS) {
284
291
  parts.push("Enter Open");
285
292
  }
286
293
  if (view !== ORCHESTRATOR_VIEWS.LAUNCH) {
@@ -0,0 +1,62 @@
1
+ /** Read-only Cockpit helpers for review receipts under ~/.harness/reviews/. */
2
+
3
+ export function countFindingsBySeverity(findings = []) {
4
+ const counts = { high: 0, medium: 0, low: 0 };
5
+ for (const finding of findings) {
6
+ const severity = String(finding?.severity ?? "").toLowerCase();
7
+ if (counts[severity] != null) counts[severity] += 1;
8
+ }
9
+ return counts;
10
+ }
11
+
12
+ export function selectReviewFromList(receipts = [], listIndex = 0) {
13
+ if (!Array.isArray(receipts) || receipts.length === 0) return null;
14
+ const index = Math.min(Math.max(0, listIndex), receipts.length - 1);
15
+ return receipts[index] ?? null;
16
+ }
17
+
18
+ export function formatReviewListLines(receipts = []) {
19
+ if (!Array.isArray(receipts) || receipts.length === 0) {
20
+ return ["No review receipts yet. Run kairo review --agent codex|pi."];
21
+ }
22
+ return receipts.map((receipt) => {
23
+ const counts = countFindingsBySeverity(receipt.findings);
24
+ const findingTotal = (receipt.findings ?? []).length;
25
+ const created = String(receipt.createdAt ?? "").slice(0, 19).replace("T", " ");
26
+ return `${receipt.reviewId} ${String(receipt.state).padEnd(10)} ${String(receipt.agentId).padEnd(6)} ${findingTotal}f (h${counts.high}/m${counts.medium}/l${counts.low}) ${created}`;
27
+ });
28
+ }
29
+
30
+ export function formatReviewDetailLines(receipt) {
31
+ if (!receipt) return ["Review receipt not found."];
32
+ const counts = countFindingsBySeverity(receipt.findings);
33
+ const snapshot = receipt.snapshot ?? {};
34
+ const totals = snapshot.totals ?? {};
35
+ const lines = [
36
+ `Id: ${receipt.reviewId}`,
37
+ `Agent: ${receipt.agentId}${receipt.model ? ` · model ${receipt.model}` : ""}`,
38
+ `State: ${receipt.state}`,
39
+ `Created: ${receipt.createdAt ?? "n/a"}`,
40
+ `Findings: ${(receipt.findings ?? []).length} (high ${counts.high}, medium ${counts.medium}, low ${counts.low})`,
41
+ `Snapshot: ${snapshot.mode ?? "n/a"} · files ${totals.fileCount ?? 0} · fingerprint ${String(snapshot.fingerprint ?? "").slice(0, 12) || "n/a"}`
42
+ ];
43
+ if ((receipt.warnings ?? []).length > 0) {
44
+ lines.push(`Warnings: ${receipt.warnings.length}`);
45
+ }
46
+ const findings = receipt.findings ?? [];
47
+ if (findings.length === 0) {
48
+ lines.push("", "No findings recorded.");
49
+ return lines;
50
+ }
51
+ lines.push("", "Findings (read-only):");
52
+ for (const finding of findings.slice(0, 20)) {
53
+ const loc = finding.line != null ? `:${finding.line}` : "";
54
+ lines.push(
55
+ ` [${String(finding.severity).toUpperCase()}] ${finding.path ?? "?"}${loc} — ${finding.title ?? "untitled"}`
56
+ );
57
+ }
58
+ if (findings.length > 20) {
59
+ lines.push(` … ${findings.length - 20} more (use kairo reviews show ${receipt.reviewId})`);
60
+ }
61
+ return lines;
62
+ }
@@ -13,6 +13,12 @@ export const RUNS_HUB_ITEMS = [
13
13
  view: ORCHESTRATOR_VIEWS.RECENT_RUNS,
14
14
  description: "Review completed and failed run outcomes."
15
15
  },
16
+ {
17
+ id: "reviews",
18
+ label: "Reviews",
19
+ view: ORCHESTRATOR_VIEWS.REVIEWS,
20
+ description: "Browse secret-free bounded-review receipts (read-only)."
21
+ },
16
22
  {
17
23
  id: "launch",
18
24
  label: "New run",
@@ -27,13 +33,16 @@ export function isRunsBranchView(view) {
27
33
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
28
34
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
29
35
  || view === ORCHESTRATOR_VIEWS.LAUNCH
30
- || view === ORCHESTRATOR_VIEWS.RUN_DETAIL;
36
+ || view === ORCHESTRATOR_VIEWS.RUN_DETAIL
37
+ || view === ORCHESTRATOR_VIEWS.REVIEWS
38
+ || view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL;
31
39
  }
32
40
 
33
41
  export function isRunsChildView(view) {
34
42
  return view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
35
43
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
36
- || view === ORCHESTRATOR_VIEWS.LAUNCH;
44
+ || view === ORCHESTRATOR_VIEWS.LAUNCH
45
+ || view === ORCHESTRATOR_VIEWS.REVIEWS;
37
46
  }
38
47
 
39
48
  export function resolveRunsHubItem(listIndex = 0, items = RUNS_HUB_ITEMS) {
@@ -14,6 +14,7 @@ import {
14
14
  import { windowLinesForLayout } from "./cockpit-models.js";
15
15
  import { LAYOUT_MODES } from "./layout.js";
16
16
  import { formatRunsHubLines, RUNS_HUB_ITEMS } from "./cockpit-runs.js";
17
+ import { formatReviewDetailLines, formatReviewListLines } from "./cockpit-reviews.js";
17
18
  import { formatChangesActionLines } from "./cockpit-changes.js";
18
19
  import { formatRecoveryLines } from "./cockpit-recovery.js";
19
20
 
@@ -74,6 +75,8 @@ export function renderCockpitView({
74
75
  layoutMode = LAYOUT_MODES.COMPACT,
75
76
  selectedRun,
76
77
  selectedEvents,
78
+ reviews = [],
79
+ selectedReview = null,
77
80
  changesAction = null,
78
81
  recoveryAction = null,
79
82
  colorEnabled = true
@@ -112,7 +115,7 @@ export function renderCockpitView({
112
115
  formatRunsHubLines(RUNS_HUB_ITEMS),
113
116
  listIndex,
114
117
  colorEnabled,
115
- "Choose Active runs, History, or New run."
118
+ "Choose Active runs, History, Reviews, or New run."
116
119
  );
117
120
  case ORCHESTRATOR_VIEWS.ACTIVE_RUNS:
118
121
  return listBlock(
@@ -136,6 +139,14 @@ export function renderCockpitView({
136
139
  colorEnabled,
137
140
  "Open Runs after governance is healthy."
138
141
  );
142
+ case ORCHESTRATOR_VIEWS.REVIEWS:
143
+ return listBlock(
144
+ "Reviews",
145
+ formatReviewListLines(reviews),
146
+ listIndex,
147
+ colorEnabled,
148
+ "Receipts are read-only. Launch reviews via kairo review --agent codex|pi."
149
+ );
139
150
  case ORCHESTRATOR_VIEWS.LAUNCH:
140
151
  if (launchableAgents.length === 0) {
141
152
  return React.createElement(CockpitEmptyState, {
@@ -165,6 +176,12 @@ export function renderCockpitView({
165
176
  formatRunDetailLines(selectedRun, selectedEvents)
166
177
  .map((line) => React.createElement(Text, { key: line }, line))
167
178
  );
179
+ case ORCHESTRATOR_VIEWS.REVIEW_DETAIL:
180
+ return React.createElement(Box, { flexDirection: "column" },
181
+ React.createElement(Text, { bold: true }, "Review detail"),
182
+ formatReviewDetailLines(selectedReview)
183
+ .map((line) => React.createElement(Text, { key: line }, line))
184
+ );
168
185
  case ORCHESTRATOR_VIEWS.DIAGNOSTICS:
169
186
  return governanceList(
170
187
  "System health",
@@ -178,7 +195,7 @@ export function renderCockpitView({
178
195
  React.createElement(Text, null, "Kairo keeps IDEs and agents aligned with project architecture and workflows."),
179
196
  React.createElement(Text, null, "Primary flow: scan → findings → preview → confirm → apply → re-scan."),
180
197
  React.createElement(Text, null, "↑↓ navigate · Enter open · Esc back/exit · R refresh/retry · ? help"),
181
- React.createElement(Text, null, "Runs are secondary after setup and repairs.")
198
+ React.createElement(Text, null, "Runs are secondary after setup and repairs. Reviews are read-only receipts.")
182
199
  );
183
200
  default: {
184
201
  const _exhaustive = view;
@@ -25,6 +25,7 @@ import {
25
25
  import { buildControlCenterModel } from "./cockpit-control-center.js";
26
26
  import { resolveEnterNavIntent } from "./cockpit-enter.js";
27
27
  import { resolveRunsHubItem, RUNS_HUB_ITEMS } from "./cockpit-runs.js";
28
+ import { selectReviewFromList } from "./cockpit-reviews.js";
28
29
  import { resolveProjectReadiness } from "../dashboard-guidance.js";
29
30
  import { CONTROL_PLANE_HEALTH } from "../control-plane-snapshot.js";
30
31
  import { CockpitShell } from "./cockpit/primitives.js";
@@ -142,6 +143,7 @@ export function OrchestratorApp({
142
143
  return;
143
144
  }
144
145
  data.setSelectedRun(null);
146
+ data.setSelectedReview(null);
145
147
  data.resetLaunchWizard();
146
148
  dispatch({ type: "escape" });
147
149
  return;
@@ -158,9 +160,11 @@ export function OrchestratorApp({
158
160
  ? (data.dashboard?.activeRuns ?? []).length
159
161
  : ui.view === ORCHESTRATOR_VIEWS.RECENT_RUNS
160
162
  ? (data.dashboard?.recentRuns ?? []).length
161
- : ui.view === ORCHESTRATOR_VIEWS.ACTIVITY
162
- ? listRecoverySnapshots(data.snapshot).length
163
- : 0;
163
+ : ui.view === ORCHESTRATOR_VIEWS.REVIEWS
164
+ ? (data.reviews ?? []).length
165
+ : ui.view === ORCHESTRATOR_VIEWS.ACTIVITY
166
+ ? listRecoverySnapshots(data.snapshot).length
167
+ : 0;
164
168
 
165
169
  let routed = null;
166
170
  if (key.tab) {
@@ -229,6 +233,9 @@ export function OrchestratorApp({
229
233
  if (hubItem.action === "launch") {
230
234
  data.resetLaunchWizard();
231
235
  }
236
+ if (hubItem.view === ORCHESTRATOR_VIEWS.REVIEWS) {
237
+ data.loadReviews().catch(() => {});
238
+ }
232
239
  dispatch({
233
240
  type: "set-view",
234
241
  view: hubItem.view,
@@ -247,6 +254,13 @@ export function OrchestratorApp({
247
254
  return;
248
255
  }
249
256
 
257
+ if (ui.region === COCKPIT_REGIONS.CONTENT
258
+ && ui.view === ORCHESTRATOR_VIEWS.REVIEWS
259
+ && key.return) {
260
+ data.openReviewDetail(selectReviewFromList(data.reviews ?? [], ui.listIndex), dispatch);
261
+ return;
262
+ }
263
+
250
264
  if (ui.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
251
265
  if (inputKey.toLowerCase() === "c" && isRunCancellable(data.selectedRun)) {
252
266
  data.handleCancelRun();
@@ -304,6 +318,10 @@ export function OrchestratorApp({
304
318
  }
305
319
 
306
320
  if (inputKey.toLowerCase() === "r" && ui.view !== ORCHESTRATOR_VIEWS.LAUNCH) {
321
+ if (ui.view === ORCHESTRATOR_VIEWS.REVIEWS || ui.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
322
+ data.loadReviews().catch(() => {});
323
+ return;
324
+ }
307
325
  data.reload().catch(() => {});
308
326
  }
309
327
  });
@@ -406,6 +424,8 @@ export function OrchestratorApp({
406
424
  launchableAgents: data.launchableAgents,
407
425
  selectedRun: data.selectedRun,
408
426
  selectedEvents: data.selectedEvents,
427
+ reviews: data.reviews,
428
+ selectedReview: data.selectedReview,
409
429
  changesAction: data.changesAction,
410
430
  recoveryAction: data.recoveryAction,
411
431
  controlCenter,
@@ -22,6 +22,8 @@ export const ORCHESTRATOR_VIEWS = {
22
22
  ACTIVE_RUNS: "active-runs",
23
23
  RECENT_RUNS: "recent-runs",
24
24
  RUN_DETAIL: "run-detail",
25
+ REVIEWS: "reviews",
26
+ REVIEW_DETAIL: "review-detail",
25
27
  PROVIDERS: "providers",
26
28
  LAUNCH: "launch",
27
29
  DIAGNOSTICS: "diagnostics",
@@ -33,6 +33,8 @@ import {
33
33
  listRecoverySnapshots,
34
34
  reduceRecoveryAction
35
35
  } from "./cockpit-recovery.js";
36
+ import { listReviewReceipts } from "../runtime/review/review-receipts.js";
37
+ import { assertReceiptSecretFree } from "../runtime/review/review-validate.js";
36
38
 
37
39
  export function useOrchestratorData({
38
40
  homeDir,
@@ -50,6 +52,8 @@ export function useOrchestratorData({
50
52
  const [snapshot, setSnapshot] = useState(null);
51
53
  const [selectedRun, setSelectedRun] = useState(null);
52
54
  const [selectedEvents, setSelectedEvents] = useState([]);
55
+ const [reviews, setReviews] = useState([]);
56
+ const [selectedReview, setSelectedReview] = useState(null);
53
57
  const [statusMessage, setStatusMessage] = useState(null);
54
58
  const [launchAgentIndex, setLaunchAgentIndex] = useState(0);
55
59
  const [launchStep, setLaunchStep] = useState(LAUNCH_WIZARD_STEPS.AGENT);
@@ -124,6 +128,27 @@ export function useOrchestratorData({
124
128
  });
125
129
  };
126
130
 
131
+ const loadReviews = async () => {
132
+ const receipts = (await listReviewReceipts({ homeDir, limit: 50 }))
133
+ .map((receipt) => assertReceiptSecretFree(receipt));
134
+ setReviews(receipts);
135
+ if (selectedReview) {
136
+ const refreshed = receipts.find((entry) => entry.reviewId === selectedReview.reviewId) ?? null;
137
+ setSelectedReview(refreshed);
138
+ }
139
+ return receipts;
140
+ };
141
+
142
+ const openReviewDetail = (receipt, dispatch) => {
143
+ if (!receipt) return;
144
+ setSelectedReview(assertReceiptSecretFree(receipt));
145
+ dispatch({
146
+ type: "set-view",
147
+ view: ORCHESTRATOR_VIEWS.REVIEW_DETAIL,
148
+ returnView: ORCHESTRATOR_VIEWS.REVIEWS
149
+ });
150
+ };
151
+
127
152
  const handleLaunch = async (draft, profile, dispatch) => {
128
153
  if (!draft.agentId || !draft.task.trim()) {
129
154
  setError("Agent and task are required.");
@@ -353,6 +378,9 @@ export function useOrchestratorData({
353
378
  selectedRun,
354
379
  setSelectedRun,
355
380
  selectedEvents,
381
+ reviews,
382
+ selectedReview,
383
+ setSelectedReview,
356
384
  statusMessage,
357
385
  launchAgentIndex,
358
386
  setLaunchAgentIndex,
@@ -369,6 +397,8 @@ export function useOrchestratorData({
369
397
  reload,
370
398
  resetLaunchWizard,
371
399
  openRunDetail,
400
+ loadReviews,
401
+ openReviewDetail,
372
402
  handleLaunch,
373
403
  handleCancelRun,
374
404
  previewChanges,
@@ -18,6 +18,7 @@ export function harnessHomePaths(homeDir) {
18
18
  profilePath: join(root, "profile.json"),
19
19
  historyPath: join(root, "history.jsonl"),
20
20
  runsDir: join(root, "runs"),
21
+ reviewsDir: join(root, "reviews"),
21
22
  coreDir: join(root, "core"),
22
23
  backupsDir: join(root, "backups")
23
24
  };
@@ -71,7 +71,8 @@ export default createExecutionAdapter({
71
71
  tokens: true,
72
72
  diff: false,
73
73
  cancel: true,
74
- transcript: true
74
+ transcript: true,
75
+ reviewCompatible: true
75
76
  },
76
77
  buildLaunch: buildCodexLaunch,
77
78
  parseEventLine: parseCodexEventLine
@@ -20,6 +20,7 @@ export function createExecutionAdapter({
20
20
  diff: false,
21
21
  cancel: true,
22
22
  transcript: false,
23
+ reviewCompatible: false,
23
24
  ...capabilities
24
25
  },
25
26
 
@@ -26,6 +26,7 @@ export function inspectExecutionAdapters(context = {}) {
26
26
  label: adapter.label,
27
27
  executable: adapter.executable,
28
28
  capabilities: adapter.capabilities,
29
+ reviewCompatible: Boolean(adapter.capabilities?.reviewCompatible),
29
30
  ...adapter.availability(context)
30
31
  }));
31
32
  }
@@ -175,7 +175,8 @@ export default createExecutionAdapter({
175
175
  tokens: true,
176
176
  diff: false,
177
177
  cancel: true,
178
- transcript: true
178
+ transcript: true,
179
+ reviewCompatible: true
179
180
  },
180
181
  checkAvailability: checkPiAvailability,
181
182
  buildLaunch: buildPiLaunch,
@@ -0,0 +1,37 @@
1
+ export {
2
+ REVIEW_SCOPE_MODES, REVIEW_SEVERITIES, REVIEW_STATES, REVIEW_EXIT_CODES, REVIEW_AGENTS,
3
+ REVIEW_LIMITS, REVIEW_SNAPSHOT_ERROR_CODES, ReviewSnapshotError, resolveReviewScopeMode,
4
+ createFindingId, canonicalFingerprint, isReviewPrivatePath, isBinaryContent,
5
+ assertReviewPathSafe, assertWithinReviewLimits, requirePrivateConsent
6
+ } from "./review-types.js";
7
+ export {
8
+ resolveReviewSnapshot, fingerprintReviewSnapshot, detectReviewSnapshotDrift,
9
+ readReviewRegularFile
10
+ } from "./review-git.js";
11
+ export {
12
+ REVIEW_PATCH_ERROR_CODES, filterDiffToAdmittedPaths, buildScopedReviewPatch
13
+ } from "./review-patch.js";
14
+ export {
15
+ REVIEW_VALIDATION_ERROR_CODES, ReviewValidationError,
16
+ validateReviewOutput, assertReceiptSecretFree
17
+ } from "./review-validate.js";
18
+ export {
19
+ assertSafeReviewId, createReviewId, reviewPaths,
20
+ buildReviewReceipt, saveReviewReceipt, loadReviewReceipt, listReviewReceipts
21
+ } from "./review-receipts.js";
22
+ export {
23
+ REVIEW_EXEC_ERROR_CODES, REVIEW_EXEC_LIMITS, ReviewExecError,
24
+ runBoundedProcess, assertBoundedProcessOk
25
+ } from "./review-exec.js";
26
+ export {
27
+ REVIEW_CODEX_ERROR_CODES, buildCodexReviewArgs, buildCodexCliEnv,
28
+ buildCodexReviewPrompt, parseCodexReviewJsonl, runCodexReview
29
+ } from "./review-codex.js";
30
+ export {
31
+ REVIEW_PI_ERROR_CODES, buildPiReviewArgs, buildPiCliEnv,
32
+ buildPiReviewPrompt, buildPiReviewStdin, parsePiReviewJsonl, runPiReview
33
+ } from "./review-pi.js";
34
+ export {
35
+ REVIEW_RUNNER_ERROR_CODES, ReviewRunnerError,
36
+ resolveReviewAgent, resolveReviewExitCode, runReview
37
+ } from "./review-runner.js";