@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 +6 -2
- package/dist/ui/app.js +3 -11
- package/dist/ui/components/column-picker.js +1 -1
- package/dist/ui/components/run-progress.js +1 -1
- package/dist/ui/steps/prompt-step.js +1 -1
- package/dist/ui/steps/run-screen.js +2 -2
- package/dist/ui/steps/summary-step.js +1 -5
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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 &&
|
|
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 ? '
|
|
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
|
|
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)
|
|
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: "
|
|
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
|
};
|