@twistezo/ai-review-council 2.0.0 → 2.0.2

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/README.md CHANGED
@@ -8,8 +8,12 @@ Similar tools usually bill per token through provider APIs. This one runs locall
8
8
 
9
9
  Docs:
10
10
 
11
- - [CHANGELOG.md](CHANGELOG.md).
12
- - [ARCHITECTURE.md](ARCHITECTURE.md).
11
+ - [CHANGELOG.md](CHANGELOG.md)
12
+ - [ARCHITECTURE.md](ARCHITECTURE.md)
13
+
14
+ ## Demo
15
+
16
+ <img src="demo/wizard.gif" alt="AI Review Council demo" width="600">
13
17
 
14
18
  ## Requirements
15
19
 
package/dist/ui/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useApp, useWindowSize } from 'ink';
2
+ import { Box, Text, useApp } from 'ink';
3
3
  import { useState } from 'react';
4
4
  import { reviewSessionDraftSchema } from '../domain/review-session.js';
5
5
  import { AnswersPanel } from './components/answers-panel.js';
@@ -34,10 +34,6 @@ const buildSessionDraft = (state, timeoutMinutes) => {
34
34
  });
35
35
  return result.success ? result.data : null;
36
36
  };
37
- // paddingX on both sides of the screen
38
- const screenPadding = 2;
39
- // a short rule under the header, narrower on small windows
40
- const separatorWidth = 40;
41
37
  // The answer each wizard step sets, shown with `← selecting` while that step is open.
42
38
  const stepFields = {
43
39
  language: 'language',
@@ -121,18 +117,14 @@ export const App = ({ title, services, presets, timeoutMinutes, onFinish }) => {
121
117
  if (action === 'back') {
122
118
  back?.();
123
119
  }
124
- else if (action === 'start') {
125
- setState((current) => ({ ...current, running: session }));
126
- }
127
120
  else {
128
- finish({ kind: 'cancelled' });
121
+ setState((current) => ({ ...current, running: session }));
129
122
  }
130
123
  } }));
131
124
  }
132
125
  }
133
126
  };
134
- const { columns } = useWindowSize();
135
127
  const steps = numberedSteps(state.skipped);
136
128
  const stepNumber = state.running === null ? steps.indexOf(state.step) + 1 : 0;
137
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: theme.title, children: title }), state.checks.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(RequirementsLine, { checks: state.checks }) })), _jsx(Text, { dimColor: true, children: '─'.repeat(Math.max(1, Math.min(separatorWidth, columns - screenPadding))) }), _jsx(Box, { marginTop: 1, children: state.running === null ? (_jsx(StepCounterContext.Provider, { value: stepNumber > 0 ? `step ${stepNumber} of ${steps.length}` : null, children: _jsx(StepAnswersContext.Provider, { value: stepFields[state.step] === undefined ? null : (_jsx(AnswersPanel, { answers: answersBefore(state), selecting: stepFields[state.step] ?? null })), children: renderStep() }) })) : (_jsx(RunScreen, { session: state.running, dependencies: services.review, writeClipboard: services.writeClipboard, openReport: services.openReport, onFinished: finish, onReported: onFinish, onQuit: exit })) })] }));
129
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: theme.title, children: title }), state.checks.length > 0 && _jsx(RequirementsLine, { checks: state.checks }), _jsx(Box, { marginTop: 1, children: state.running === null ? (_jsx(StepCounterContext.Provider, { value: stepNumber > 0 ? `step ${stepNumber} of ${steps.length}` : null, children: _jsx(StepAnswersContext.Provider, { value: stepFields[state.step] === undefined ? null : (_jsx(AnswersPanel, { answers: answersBefore(state), selecting: stepFields[state.step] ?? null })), children: renderStep() }) })) : (_jsx(RunScreen, { session: state.running, dependencies: services.review, writeClipboard: services.writeClipboard, openReport: services.openReport, onFinished: finish, onReported: onFinish, onQuit: exit })) })] }));
138
130
  };
@@ -24,7 +24,7 @@ export const ColumnPicker = ({ columns, onChange, onSubmit, height = 10 }) => {
24
24
  return (_jsx(Box, { flexDirection: "row", gap: 3, children: columns.map((column, columnIndex) => {
25
25
  const isActiveColumn = columnIndex === activeColumn;
26
26
  const { start, end } = visibleWindow(column.options.length, column.selected, height);
27
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, underline: isActiveColumn, color: isActiveColumn ? theme.focus : undefined, children: [isActiveColumn ? '▸ ' : ' ', column.title] }), _jsx(Text, { dimColor: true, children: start > 0 ? ' ↑' : ' ' }), column.options.slice(start, end).map((option, offset) => {
27
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, underline: isActiveColumn, color: isActiveColumn ? theme.focus : undefined, children: [isActiveColumn ? '› ' : ' ', column.title] }), _jsx(Text, { dimColor: true, children: start > 0 ? ' ↑' : ' ' }), column.options.slice(start, end).map((option, offset) => {
28
28
  const isSelected = start + offset === column.selected;
29
29
  return (_jsxs(Text, { color: isSelected ? (isActiveColumn ? theme.selection : theme.chosen) : undefined, dimColor: !isSelected, children: [isSelected ? '› ' : ' ', option] }, option));
30
30
  }), _jsx(Text, { dimColor: true, children: end < column.options.length ? ' ↓' : ' ' })] }, column.title));
@@ -43,7 +43,7 @@ export const RunProgressView = ({ rows: unsortedRows, now, frame, windowColumns
43
43
  const rows = sortRunRows(unsortedRows);
44
44
  const widths = columnWidths(rows, windowColumns);
45
45
  const spinner = spinnerFrames[frame % spinnerFrames.length] ?? '•';
46
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Cells, { widths: widths, provider: "Provider", model: "Model", effort: "Effort", review: "Review", status: _jsx(Text, { dimColor: true, children: "Status" }), time: "Time", isDimmed: true }), rows.map((row, index) => {
46
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Cells, { widths: widths, provider: "Provider", model: "Model", effort: "Effort", review: "Review", status: _jsx(Text, { dimColor: true, children: "Status" }), time: "Time", isDimmed: true }), _jsx(Box, { height: 1 }), rows.map((row, index) => {
47
47
  const tone = phaseTones[row.progress.phase];
48
48
  const mark = tone === 'active' ? spinner : toneMarks[tone];
49
49
  const startsCouncil = row.isCouncil && index > 0 && rows[index - 1]?.isCouncil === false;
@@ -5,5 +5,5 @@ import { MultilineInput } from '../components/multiline-input.js';
5
5
  import { StepFrame } from '../components/step-frame.js';
6
6
  export const PromptStep = ({ value, readClipboard, onSubmit, onBack }) => {
7
7
  const [prompt, setPrompt] = useState(value);
8
- return (_jsxs(StepFrame, { title: "Review prompt", hints: "Enter new line \u00B7 Ctrl+D confirm \u00B7 Ctrl+V or Cmd+V paste", onBack: onBack, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Task, acceptance criteria, links. Shared by all agents." }) }), _jsx(MultilineInput, { value: prompt, onChange: setPrompt, onSubmit: () => onSubmit(prompt.trim()), readClipboard: readClipboard, placeholder: "Type or paste (Ctrl+V) the review prompt" })] }));
8
+ return (_jsxs(StepFrame, { title: "Review prompt", hints: "Enter new line \u00B7 Ctrl+D confirm \u00B7 Cmd+V paste", onBack: onBack, children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Review context, goals and skills. Shared by all agents." }), _jsx(Text, { dimColor: true, children: "Read-only local review. No live web access." })] }), _jsx(MultilineInput, { value: prompt, onChange: setPrompt, onSubmit: () => onSubmit(prompt.trim()), readClipboard: readClipboard, placeholder: "Type or paste (Ctrl+V) the review prompt" })] }));
9
9
  };
@@ -53,6 +53,6 @@ export const RunScreen = ({ session, dependencies, writeClipboard, openReport, o
53
53
  return () => controller.abort();
54
54
  }, []);
55
55
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: outcome === null
56
- ? `Reviewing ${session.scope.changedFiles.length} file(s) · Ctrl+C cancels all agents`
57
- : `Reviewed ${session.scope.changedFiles.length} file(s)` }), _jsx(Box, { marginTop: 1, children: _jsx(RunProgress, { rows: rows }) }), outcome !== null && report !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ReportResult, { report: report, writeClipboard: writeClipboard, openReport: openReport, onQuit: onQuit }) }))] }));
56
+ ? `Reviewing ${session.scope.changedFiles.length} file(s)`
57
+ : `Reviewed ${session.scope.changedFiles.length} file(s)` }), _jsx(Box, { marginTop: 1, children: _jsx(RunProgress, { rows: rows }) }), outcome === null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Ctrl+C cancels all agents" }) })), outcome !== null && report !== null && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ReportResult, { report: report, writeClipboard: writeClipboard, openReport: openReport, onQuit: onQuit }) }))] }));
58
58
  };
@@ -22,9 +22,5 @@ export const summarizePrompt = (prompt) => {
22
22
  export const SummaryStep = ({ session, onAction }) => {
23
23
  const { repository, reviewers } = session;
24
24
  const [firstPromptLine = '', ...otherPromptLines] = summarizePrompt(session.prompt);
25
- return (_jsxs(StepFrame, { title: "Summary", hints: "\u2191\u2193 row \u00B7 Enter confirm", children: [_jsx(Field, { label: "Language", children: reportLanguageNames[session.language] }), _jsx(Field, { label: "Project", children: repository.root }), _jsx(Field, { label: "Branch", children: repository.branch ?? 'detached HEAD' }), _jsx(Field, { label: "Working tree", children: repository.hasChanges ? (_jsx(Text, { color: theme.warning, children: "currently has uncommitted changes" })) : (_jsx(Text, { color: theme.success, children: "currently clean" })) }), _jsx(Field, { label: "Scope", children: describeScope(session.scope) }), _jsx(Field, { label: "Council", children: formatConfiguration(session.council) }), _jsx(Field, { label: "Reviewers", children: String(reviewers.length) }), _jsx(Box, { paddingLeft: labelWidth, children: _jsx(ReviewerList, { reviewers: reviewers }) }), _jsx(Field, { label: "Prompt", children: session.prompt === '' ? _jsx(Text, { dimColor: true, children: "none, review of the code alone" }) : firstPromptLine }), otherPromptLines.map((line, index) => (_jsx(Box, { paddingLeft: labelWidth, children: _jsx(Text, { children: line }) }, index))), _jsxs(Field, { label: "Timeout", children: [session.timeoutMinutes, " min per agent"] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectList, { items: [
26
- { key: 'start', label: 'Start' },
27
- { key: 'back', label: 'Back' },
28
- { key: 'cancel', label: 'Cancel' },
29
- ], onSelect: (key) => onAction(key === 'start' || key === 'back' ? key : 'cancel') }) })] }));
25
+ return (_jsxs(StepFrame, { title: "Summary", hints: "Enter confirm", onBack: () => onAction('back'), children: [_jsx(Field, { label: "Language", children: reportLanguageNames[session.language] }), _jsx(Field, { label: "Project", children: repository.root }), _jsx(Field, { label: "Branch", children: repository.branch ?? 'detached HEAD' }), _jsx(Field, { label: "Working tree", children: repository.hasChanges ? (_jsx(Text, { color: theme.warning, children: "currently has uncommitted changes" })) : (_jsx(Text, { color: theme.success, children: "currently clean" })) }), _jsx(Field, { label: "Scope", children: describeScope(session.scope) }), _jsx(Field, { label: "Council", children: formatConfiguration(session.council) }), _jsx(Field, { label: "Reviewers", children: null }), _jsx(Box, { paddingLeft: labelWidth, children: _jsx(ReviewerList, { reviewers: reviewers }) }), _jsx(Field, { label: "Prompt", children: session.prompt === '' ? _jsx(Text, { dimColor: true, children: "none, review of the code alone" }) : firstPromptLine }), otherPromptLines.map((line, index) => (_jsx(Box, { paddingLeft: labelWidth, children: _jsx(Text, { children: line }) }, index))), _jsxs(Field, { label: "Timeout", children: [session.timeoutMinutes, " min per agent"] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectList, { items: [{ key: 'start', label: 'Start' }], onSelect: () => onAction('start') }) })] }));
30
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twistezo/ai-review-council",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Terminal app that runs independent Codex and Claude Code reviewers and merges their results",
5
5
  "license": "MIT",
6
6
  "keywords": [