@ariso-ai/ivan 1.0.35 → 1.0.38
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 +8 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -18
- package/dist/config.js.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/services/address-task-executor.d.ts.map +1 -1
- package/dist/services/address-task-executor.js +4 -3
- package/dist/services/address-task-executor.js.map +1 -1
- package/dist/services/claude-cli-executor.d.ts +2 -0
- package/dist/services/claude-cli-executor.d.ts.map +1 -1
- package/dist/services/claude-cli-executor.js +113 -85
- package/dist/services/claude-cli-executor.js.map +1 -1
- package/dist/services/claude-executor.d.ts.map +1 -1
- package/dist/services/claude-executor.js +102 -16
- package/dist/services/claude-executor.js.map +1 -1
- package/dist/services/interjection-manager.d.ts +102 -0
- package/dist/services/interjection-manager.d.ts.map +1 -0
- package/dist/services/interjection-manager.js +332 -0
- package/dist/services/interjection-manager.js.map +1 -0
- package/dist/services/risk-analysis-executor.d.ts +8 -0
- package/dist/services/risk-analysis-executor.d.ts.map +1 -0
- package/dist/services/risk-analysis-executor.js +178 -0
- package/dist/services/risk-analysis-executor.js.map +1 -0
- package/dist/services/task-executor.d.ts.map +1 -1
- package/dist/services/task-executor.js +5 -4
- package/dist/services/task-executor.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
/**
|
|
5
|
+
* Captures additional context the user types while an Ivan command is running,
|
|
6
|
+
* similar to interjecting in an interactive Claude Code session.
|
|
7
|
+
*
|
|
8
|
+
* Input is read in raw mode with the echo rendered by this class on its own
|
|
9
|
+
* input line — a spinner redrawing its line would otherwise wipe the
|
|
10
|
+
* terminal's native echo, making typing invisible. While the user is
|
|
11
|
+
* composing, the active spinner (registered via claudeSpinner) is paused, and
|
|
12
|
+
* resumed when the input is submitted or cancelled.
|
|
13
|
+
*
|
|
14
|
+
* Each submitted line is either delivered immediately to a live listener (the
|
|
15
|
+
* SDK executor streams it into the running session mid-turn) or buffered until
|
|
16
|
+
* the next turn starts (the CLI executor applies it in an automatic follow-up
|
|
17
|
+
* turn).
|
|
18
|
+
*/
|
|
19
|
+
export class InterjectionManager {
|
|
20
|
+
static instance = null;
|
|
21
|
+
static getInstance() {
|
|
22
|
+
if (!InterjectionManager.instance) {
|
|
23
|
+
InterjectionManager.instance = new InterjectionManager();
|
|
24
|
+
}
|
|
25
|
+
return InterjectionManager.instance;
|
|
26
|
+
}
|
|
27
|
+
refCount = 0;
|
|
28
|
+
listening = false;
|
|
29
|
+
keypressAttached = false;
|
|
30
|
+
rawModeSet = false;
|
|
31
|
+
buffer = '';
|
|
32
|
+
pending = [];
|
|
33
|
+
liveListener = null;
|
|
34
|
+
hintShown = false;
|
|
35
|
+
activeSpinner = null;
|
|
36
|
+
pausedSpinner = null;
|
|
37
|
+
keypressHandler = (str, key) => this.handleKeypress(str, key);
|
|
38
|
+
selfWrite = false;
|
|
39
|
+
inputLineVisible = false;
|
|
40
|
+
externalMidLine = false;
|
|
41
|
+
unhookStreams = null;
|
|
42
|
+
/** Interjections require an interactive terminal. */
|
|
43
|
+
isAvailable() {
|
|
44
|
+
return Boolean(process.stdin.isTTY);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Begin capturing keystrokes. Ref-counted so overlapping turns can
|
|
48
|
+
* start/stop freely; the listener is only torn down when the outermost
|
|
49
|
+
* stop() is reached.
|
|
50
|
+
*/
|
|
51
|
+
start(quiet = false) {
|
|
52
|
+
if (!this.isAvailable())
|
|
53
|
+
return;
|
|
54
|
+
this.refCount++;
|
|
55
|
+
if (this.listening)
|
|
56
|
+
return;
|
|
57
|
+
this.listening = true;
|
|
58
|
+
if (!this.keypressAttached) {
|
|
59
|
+
readline.emitKeypressEvents(process.stdin);
|
|
60
|
+
this.keypressAttached = true;
|
|
61
|
+
}
|
|
62
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
63
|
+
process.stdin.setRawMode(true);
|
|
64
|
+
this.rawModeSet = true;
|
|
65
|
+
}
|
|
66
|
+
process.stdin.on('keypress', this.keypressHandler);
|
|
67
|
+
process.stdin.resume();
|
|
68
|
+
this.hookOutputStreams();
|
|
69
|
+
if (!this.hintShown && !quiet) {
|
|
70
|
+
console.log(chalk.cyan('💬 Type a message and press Enter at any time to give Ivan additional context'));
|
|
71
|
+
this.hintShown = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Stop capturing keystrokes once all active turns have finished. */
|
|
75
|
+
stop() {
|
|
76
|
+
if (!this.isAvailable())
|
|
77
|
+
return;
|
|
78
|
+
if (this.refCount > 0)
|
|
79
|
+
this.refCount--;
|
|
80
|
+
if (this.refCount === 0 && this.listening) {
|
|
81
|
+
this.listening = false;
|
|
82
|
+
process.stdin.removeListener('keypress', this.keypressHandler);
|
|
83
|
+
if (this.rawModeSet) {
|
|
84
|
+
process.stdin.setRawMode(false);
|
|
85
|
+
this.rawModeSet = false;
|
|
86
|
+
}
|
|
87
|
+
// Release stdin so the process can exit and later prompts (inquirer)
|
|
88
|
+
// get a clean stream.
|
|
89
|
+
process.stdin.pause();
|
|
90
|
+
// Abandon any partially typed input and restore the spinner.
|
|
91
|
+
if (this.buffer) {
|
|
92
|
+
this.buffer = '';
|
|
93
|
+
this.clearInputLine();
|
|
94
|
+
}
|
|
95
|
+
this.unhookStreams?.();
|
|
96
|
+
this.unhookStreams = null;
|
|
97
|
+
this.resumeSpinner();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* While the user is composing, the `💬 > ` echo owns the cursor row with no
|
|
102
|
+
* trailing newline — anything the executors print (tool-usage lines,
|
|
103
|
+
* streamed CLI output) would land right after the typed text and shove the
|
|
104
|
+
* echo onto a new line. Intercept stdout/stderr so external output clears
|
|
105
|
+
* the echo, prints on its own line, and the echo is redrawn beneath it.
|
|
106
|
+
*/
|
|
107
|
+
hookOutputStreams() {
|
|
108
|
+
if (this.unhookStreams)
|
|
109
|
+
return;
|
|
110
|
+
const hook = (stream) => {
|
|
111
|
+
const original = stream.write.bind(stream);
|
|
112
|
+
const manager = this;
|
|
113
|
+
stream.write = function (chunk, encoding, callback) {
|
|
114
|
+
if (manager.selfWrite || manager.buffer.length === 0) {
|
|
115
|
+
return original(chunk, encoding, callback);
|
|
116
|
+
}
|
|
117
|
+
if (manager.inputLineVisible) {
|
|
118
|
+
manager.selfWrite = true;
|
|
119
|
+
original('\r\x1b[2K');
|
|
120
|
+
manager.selfWrite = false;
|
|
121
|
+
manager.inputLineVisible = false;
|
|
122
|
+
}
|
|
123
|
+
const result = original(chunk, encoding, callback);
|
|
124
|
+
const text = typeof chunk === 'string'
|
|
125
|
+
? chunk
|
|
126
|
+
: globalThis.Buffer.from(chunk).toString('utf8');
|
|
127
|
+
if (text.length > 0) {
|
|
128
|
+
manager.externalMidLine = !text.endsWith('\n');
|
|
129
|
+
if (!manager.externalMidLine)
|
|
130
|
+
manager.render();
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
return () => {
|
|
135
|
+
stream.write = original;
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
const unhookStdout = hook(process.stdout);
|
|
139
|
+
const unhookStderr = hook(process.stderr);
|
|
140
|
+
this.unhookStreams = () => {
|
|
141
|
+
unhookStdout();
|
|
142
|
+
unhookStderr();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Registers the spinner currently rendering so it can be paused while the
|
|
147
|
+
* user types (its line redraws would erase the input echo otherwise).
|
|
148
|
+
*/
|
|
149
|
+
attachSpinner(spinner) {
|
|
150
|
+
this.activeSpinner = spinner;
|
|
151
|
+
}
|
|
152
|
+
detachSpinner(spinner) {
|
|
153
|
+
if (this.activeSpinner === spinner)
|
|
154
|
+
this.activeSpinner = null;
|
|
155
|
+
if (this.pausedSpinner === spinner)
|
|
156
|
+
this.pausedSpinner = null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Register a handler that receives interjections the moment they are typed
|
|
160
|
+
* (used by the SDK executor to stream them into the running session).
|
|
161
|
+
* Returns a release function; after release, new input buffers again.
|
|
162
|
+
*/
|
|
163
|
+
setLiveListener(listener) {
|
|
164
|
+
this.liveListener = listener;
|
|
165
|
+
return () => {
|
|
166
|
+
if (this.liveListener === listener)
|
|
167
|
+
this.liveListener = null;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Returns and clears any buffered interjections. */
|
|
171
|
+
drainPending() {
|
|
172
|
+
return this.pending.splice(0);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Puts interjections back at the front of the buffer — used when a turn
|
|
176
|
+
* ends before delivering input it had already accepted.
|
|
177
|
+
*/
|
|
178
|
+
requeue(texts) {
|
|
179
|
+
if (texts.length > 0)
|
|
180
|
+
this.pending.unshift(...texts);
|
|
181
|
+
}
|
|
182
|
+
hasPending() {
|
|
183
|
+
return this.pending.length > 0;
|
|
184
|
+
}
|
|
185
|
+
handleKeypress(str, key = {}) {
|
|
186
|
+
if (key.ctrl && key.name === 'c') {
|
|
187
|
+
// Raw mode suppresses the terminal's own SIGINT; re-emit it so the
|
|
188
|
+
// executors' Ctrl+C handlers still fire.
|
|
189
|
+
if (this.rawModeSet)
|
|
190
|
+
process.kill(process.pid, 'SIGINT');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
194
|
+
this.submit();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (key.name === 'backspace') {
|
|
198
|
+
if (this.buffer) {
|
|
199
|
+
this.buffer = this.buffer.slice(0, -1);
|
|
200
|
+
this.render();
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (key.ctrl && key.name === 'u') {
|
|
205
|
+
this.buffer = '';
|
|
206
|
+
this.render();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (key.name === 'escape') {
|
|
210
|
+
this.buffer = '';
|
|
211
|
+
this.clearInputLine();
|
|
212
|
+
this.resumeSpinner();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (key.ctrl || key.meta)
|
|
216
|
+
return;
|
|
217
|
+
// Printable input only (pasted text arrives as a multi-char str).
|
|
218
|
+
if (!str)
|
|
219
|
+
return;
|
|
220
|
+
const printable = Array.from(str)
|
|
221
|
+
.filter((ch) => {
|
|
222
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
223
|
+
return code >= 0x20 && code !== 0x7f;
|
|
224
|
+
})
|
|
225
|
+
.join('');
|
|
226
|
+
if (!printable)
|
|
227
|
+
return;
|
|
228
|
+
if (this.buffer.length === 0)
|
|
229
|
+
this.pauseSpinner();
|
|
230
|
+
this.buffer += printable;
|
|
231
|
+
this.render();
|
|
232
|
+
}
|
|
233
|
+
submit() {
|
|
234
|
+
const text = this.buffer.trim();
|
|
235
|
+
this.buffer = '';
|
|
236
|
+
this.clearInputLine();
|
|
237
|
+
if (text) {
|
|
238
|
+
if (this.liveListener) {
|
|
239
|
+
this.liveListener(text);
|
|
240
|
+
console.log(chalk.cyan('💬 Context sent to Claude — it will be incorporated into the current task'));
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
this.pending.push(text);
|
|
244
|
+
console.log(chalk.cyan('💬 Context queued — Ivan will include it in the next turn'));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.resumeSpinner();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Redraws the input line. The spinner is paused while composing, so this
|
|
251
|
+
* line owns the cursor row; hooked stdout/stderr writes clear and redraw it
|
|
252
|
+
* around any external output so the echo always sits on the bottom line.
|
|
253
|
+
*/
|
|
254
|
+
render() {
|
|
255
|
+
if (!this.listening)
|
|
256
|
+
return;
|
|
257
|
+
this.selfWrite = true;
|
|
258
|
+
// If external output left the cursor mid-line, move past it instead of
|
|
259
|
+
// overwriting the partial line.
|
|
260
|
+
if (this.externalMidLine) {
|
|
261
|
+
process.stderr.write('\n');
|
|
262
|
+
this.externalMidLine = false;
|
|
263
|
+
}
|
|
264
|
+
process.stderr.write('\r\x1b[2K' + chalk.cyan('💬 > ') + this.buffer);
|
|
265
|
+
this.selfWrite = false;
|
|
266
|
+
this.inputLineVisible = true;
|
|
267
|
+
}
|
|
268
|
+
clearInputLine() {
|
|
269
|
+
this.selfWrite = true;
|
|
270
|
+
process.stderr.write('\r\x1b[2K');
|
|
271
|
+
this.selfWrite = false;
|
|
272
|
+
this.inputLineVisible = false;
|
|
273
|
+
}
|
|
274
|
+
pauseSpinner() {
|
|
275
|
+
const spinner = this.activeSpinner;
|
|
276
|
+
if (spinner && spinner.isSpinning) {
|
|
277
|
+
this.pausedSpinner = spinner;
|
|
278
|
+
spinner.stop();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
resumeSpinner() {
|
|
282
|
+
// Only restart a spinner we paused ourselves that is still the active
|
|
283
|
+
// one — if the executor finished it (succeed/fail) while the user was
|
|
284
|
+
// typing, detachSpinner has already cleared it.
|
|
285
|
+
if (this.pausedSpinner && this.pausedSpinner === this.activeSpinner) {
|
|
286
|
+
this.pausedSpinner.start();
|
|
287
|
+
}
|
|
288
|
+
this.pausedSpinner = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Creates a spinner for phases where a Claude turn runs and interjections are
|
|
293
|
+
* being captured: stdin stays readable (no discardStdin) and the spinner is
|
|
294
|
+
* registered with the InterjectionManager so it pauses while the user types.
|
|
295
|
+
* Finishing the spinner (succeed/fail/warn/info/stopAndPersist) detaches it;
|
|
296
|
+
* plain stop() is left unwrapped because the manager itself uses it to pause.
|
|
297
|
+
*/
|
|
298
|
+
export function claudeSpinner(text) {
|
|
299
|
+
const spinner = ora({ text, discardStdin: false });
|
|
300
|
+
const manager = InterjectionManager.getInstance();
|
|
301
|
+
manager.attachSpinner(spinner);
|
|
302
|
+
const finish = (fn) => (...args) => {
|
|
303
|
+
manager.detachSpinner(spinner);
|
|
304
|
+
return fn(...args);
|
|
305
|
+
};
|
|
306
|
+
spinner.succeed = finish(spinner.succeed.bind(spinner));
|
|
307
|
+
spinner.fail = finish(spinner.fail.bind(spinner));
|
|
308
|
+
spinner.warn = finish(spinner.warn.bind(spinner));
|
|
309
|
+
spinner.info = finish(spinner.info.bind(spinner));
|
|
310
|
+
spinner.stopAndPersist = finish(spinner.stopAndPersist.bind(spinner));
|
|
311
|
+
return spinner;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Appends buffered interjections to the initial prompt of the next turn.
|
|
315
|
+
*/
|
|
316
|
+
export function appendInterjections(prompt, texts) {
|
|
317
|
+
if (texts.length === 0)
|
|
318
|
+
return prompt;
|
|
319
|
+
return `${prompt}\n\nAdditional context from the user (provided while Ivan was running — treat it as part of the task):\n${texts
|
|
320
|
+
.map((t) => `- ${t}`)
|
|
321
|
+
.join('\n')}`;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Formats interjections delivered as their own message — either streamed into
|
|
325
|
+
* a running SDK session or sent as a CLI follow-up turn.
|
|
326
|
+
*/
|
|
327
|
+
export function interjectionMessage(texts) {
|
|
328
|
+
return `The user interjected with additional context while you were working:\n${texts
|
|
329
|
+
.map((t) => `- ${t}`)
|
|
330
|
+
.join('\n')}\nIncorporate this guidance into the task you are working on, adjusting anything you have already done if needed.`;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=interjection-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interjection-manager.js","sourceRoot":"","sources":["../../src/services/interjection-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk-analysis-executor.d.ts","sourceRoot":"","sources":["../../src/services/risk-analysis-executor.ts"],"names":[],"mappings":"AAmGA,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,iBAAiB,CAAqB;;IAOxC,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAyE7C,UAAU;CAqCzB"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ExecutorFactory } from './executor-factory.js';
|
|
3
|
+
import { createRepositoryManager } from './service-factory.js';
|
|
4
|
+
import { claudeSpinner } from './interjection-manager.js';
|
|
5
|
+
const RISK_ANALYSIS_SYSTEM_PROMPT = `You are a principal engineer performing a pre-deployment risk analysis of a set of proposed changes.
|
|
6
|
+
|
|
7
|
+
Your job is NOT to review code style or suggest improvements — it is to identify what could go wrong when these changes ship, how likely that is, how bad the blast radius would be, and what mitigations should be in place before rollout.
|
|
8
|
+
|
|
9
|
+
For every risk you identify:
|
|
10
|
+
- State the specific failure mode (what breaks, for whom, and when)
|
|
11
|
+
- Rate its likelihood (low / medium / high) and impact (low / medium / high / critical)
|
|
12
|
+
- Note whether it is roll-backable, and what the rollback path is
|
|
13
|
+
- Recommend a concrete mitigation (guard, feature flag, deploy ordering, backup, monitoring, etc.)
|
|
14
|
+
|
|
15
|
+
Ground your analysis in the actual repository: use your tools to read the code, migrations, and configuration that the described changes would touch. Do not speculate about code you have not looked at — if a change references something you cannot find in the repo, say so explicitly rather than guessing.
|
|
16
|
+
|
|
17
|
+
Be direct and specific. If an area carries no meaningful risk, say so briefly and move on.`;
|
|
18
|
+
const RISK_DIMENSIONS = [
|
|
19
|
+
{
|
|
20
|
+
key: 'db-migration',
|
|
21
|
+
label: 'Analyze DB migration risk',
|
|
22
|
+
prompt: `Analyze the DATABASE MIGRATION risk of the proposed changes.
|
|
23
|
+
|
|
24
|
+
Focus on:
|
|
25
|
+
- Schema changes: dropped/renamed columns or tables, type changes, and whether old code running during the deploy window would break against the new schema (and vice versa)
|
|
26
|
+
- Destructive or irreversible operations: data loss potential, and whether a down-migration actually restores state
|
|
27
|
+
- Locking and downtime: long-running ALTERs, index builds on large tables (are indexes created concurrently?), table rewrites, migrations that block reads or writes
|
|
28
|
+
- Backfills and data transformations: volume, batching, idempotency if interrupted midway
|
|
29
|
+
- Deploy ordering: does the migration need to run before or after the code deploy, and what happens if that ordering is violated
|
|
30
|
+
- Rollback: can the migration be reverted safely once new data has been written
|
|
31
|
+
|
|
32
|
+
If the changes involve no database migrations, state that clearly and rate this dimension as no risk.`
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: 'infra-security',
|
|
36
|
+
label: 'Analyze infra/security risk',
|
|
37
|
+
prompt: `Analyze the INFRASTRUCTURE and SECURITY risk of the proposed changes.
|
|
38
|
+
|
|
39
|
+
Focus on:
|
|
40
|
+
- Security: new or widened endpoint exposure, authentication/authorization changes, secrets or credentials in code or config, injection surfaces, permission/IAM scope changes, sensitive data logging or exposure
|
|
41
|
+
- Dependencies: new or upgraded packages, known-vulnerable versions, supply-chain surface
|
|
42
|
+
- Infrastructure/config: environment variable changes, CI/CD workflow changes, networking or firewall implications, resource limits, quota or cost blowups
|
|
43
|
+
- Availability: single points of failure introduced, changes to health checks, restarts or reprovisioning required by the rollout
|
|
44
|
+
- Blast radius: if this change misbehaves, what else does it take down
|
|
45
|
+
|
|
46
|
+
If the changes touch no infrastructure or security surface, state that clearly and rate this dimension as no risk.`
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: 'backend-service',
|
|
50
|
+
label: 'Analyze backend service risk',
|
|
51
|
+
prompt: `Analyze the BACKEND SERVICE risk of the proposed changes.
|
|
52
|
+
|
|
53
|
+
Focus on:
|
|
54
|
+
- API contract changes: breaking request/response shape changes, removed or renamed fields, status code changes, and impact on existing clients
|
|
55
|
+
- Backwards compatibility: can old and new versions of the service run side by side during rollout
|
|
56
|
+
- Error handling: new failure paths, unhandled promise rejections or exceptions, retry storms, timeout behavior
|
|
57
|
+
- Performance: N+1 queries, unbounded loops or payloads, memory growth, hot-path latency regressions
|
|
58
|
+
- State and concurrency: race conditions, transactional integrity, idempotency of mutating endpoints
|
|
59
|
+
- Third-party integrations: changed assumptions about external services, rate limits, degraded-mode behavior
|
|
60
|
+
|
|
61
|
+
If the changes do not touch backend service code, state that clearly and rate this dimension as no risk.`
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'worker-frontend',
|
|
65
|
+
label: 'Analyze worker/frontend risk',
|
|
66
|
+
prompt: `Analyze the WORKER (background job) and FRONTEND risk of the proposed changes.
|
|
67
|
+
|
|
68
|
+
For workers/background jobs, focus on:
|
|
69
|
+
- Queue and job semantics: changed job payloads breaking in-flight jobs, retry and dead-letter behavior, idempotency of re-run jobs
|
|
70
|
+
- Scheduling: cron or interval changes, overlap of long-running jobs, thundering-herd effects
|
|
71
|
+
- Failure isolation: does a failing job poison the queue or block other work
|
|
72
|
+
|
|
73
|
+
For frontend/client code, focus on:
|
|
74
|
+
- Client-server compatibility: cached/stale frontend bundles calling changed APIs during and after deploy
|
|
75
|
+
- Breaking UX flows: removed or renamed routes, changed form contracts, state management regressions
|
|
76
|
+
- Caching: CDN/browser cache invalidation, localStorage/session schema changes breaking returning users
|
|
77
|
+
- Error surfaces: unhandled rejections that blank the page vs. degrade gracefully
|
|
78
|
+
|
|
79
|
+
If the changes touch neither workers nor frontend code, state that clearly and rate this dimension as no risk.`
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
function buildChangesSection(changes) {
|
|
83
|
+
const list = changes.map((c, i) => `${i + 1}. ${c}`).join('\n');
|
|
84
|
+
return `## Proposed changes under analysis
|
|
85
|
+
|
|
86
|
+
${list}`;
|
|
87
|
+
}
|
|
88
|
+
export class RiskAnalysisExecutor {
|
|
89
|
+
claudeExecutor;
|
|
90
|
+
repositoryManager;
|
|
91
|
+
constructor() {
|
|
92
|
+
this.claudeExecutor = ExecutorFactory.getExecutor();
|
|
93
|
+
this.repositoryManager = createRepositoryManager();
|
|
94
|
+
}
|
|
95
|
+
async executeRiskAnalysis(changes) {
|
|
96
|
+
try {
|
|
97
|
+
await this.claudeExecutor.validateClaudeCodeInstallation();
|
|
98
|
+
console.log(chalk.green('✅ Claude Code configured'));
|
|
99
|
+
const workingDir = await this.repositoryManager.getValidWorkingDirectory();
|
|
100
|
+
const repoInfo = this.repositoryManager.getRepositoryInfo(workingDir);
|
|
101
|
+
console.log(chalk.blue(`📂 Working in: ${repoInfo.name}`));
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(chalk.blue.bold('🛡️ Running risk analysis'));
|
|
104
|
+
for (const [i, change] of changes.entries()) {
|
|
105
|
+
console.log(chalk.gray(` ${i + 1}. ${change}`));
|
|
106
|
+
}
|
|
107
|
+
console.log('');
|
|
108
|
+
// Suppress the executor's per-turn logging so the tree output stays readable
|
|
109
|
+
this.claudeExecutor.quietMode = true;
|
|
110
|
+
const changesSection = buildChangesSection(changes);
|
|
111
|
+
const dimensionResults = [];
|
|
112
|
+
for (const [i, dimension] of RISK_DIMENSIONS.entries()) {
|
|
113
|
+
const branch = i === RISK_DIMENSIONS.length - 1 ? '└' : '├';
|
|
114
|
+
const spinner = claudeSpinner(`${branch} ${dimension.label}`).start();
|
|
115
|
+
try {
|
|
116
|
+
const result = await this.claudeExecutor.executeTurn(`${changesSection}\n\n${dimension.prompt}`, workingDir, {
|
|
117
|
+
systemPrompt: RISK_ANALYSIS_SYSTEM_PROMPT,
|
|
118
|
+
readOnly: true,
|
|
119
|
+
permissionMode: 'plan'
|
|
120
|
+
});
|
|
121
|
+
dimensionResults.push({
|
|
122
|
+
label: dimension.label,
|
|
123
|
+
analysis: result.lastMessage
|
|
124
|
+
});
|
|
125
|
+
spinner.succeed(`${branch} ${dimension.label}`);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
spinner.fail(`${branch} ${dimension.label}`);
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const synthesisSpinner = claudeSpinner('Synthesizing final risk analysis').start();
|
|
133
|
+
let finalReport;
|
|
134
|
+
try {
|
|
135
|
+
finalReport = await this.synthesize(changes, dimensionResults, workingDir);
|
|
136
|
+
synthesisSpinner.succeed('Final risk analysis ready');
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
synthesisSpinner.fail('Failed to synthesize final risk analysis');
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(chalk.blue.bold('📋 Final Risk Analysis'));
|
|
144
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
145
|
+
console.log(finalReport);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
this.repositoryManager.close();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async synthesize(changes, dimensionResults, workingDir) {
|
|
152
|
+
const findings = dimensionResults
|
|
153
|
+
.map((r) => `### ${r.label}\n\n${r.analysis}`)
|
|
154
|
+
.join('\n\n');
|
|
155
|
+
const synthesisPrompt = `${buildChangesSection(changes)}
|
|
156
|
+
|
|
157
|
+
Four specialist risk analyses have already been performed against these changes. Their findings are below.
|
|
158
|
+
|
|
159
|
+
${findings}
|
|
160
|
+
|
|
161
|
+
Synthesize these into ONE final risk analysis report. Do not re-investigate the code — work from the findings above. Structure the report as:
|
|
162
|
+
|
|
163
|
+
1. **Overall risk rating** — one of: NONE / LOW / MEDIUM / HIGH / CRITICAL, with a one-sentence justification
|
|
164
|
+
2. **Risk summary by area** — a table with columns: Area (DB migration, Infra/security, Backend service, Worker/frontend), Risk level, Key concern
|
|
165
|
+
3. **Top risks** — the most important risks across all areas, ordered by severity, each with its failure mode, likelihood/impact, and mitigation
|
|
166
|
+
4. **Required mitigations before rollout** — a concrete pre-deploy checklist derived from the findings
|
|
167
|
+
5. **Rollback plan** — how to revert if things go wrong, noting anything that is NOT cleanly reversible
|
|
168
|
+
|
|
169
|
+
Deduplicate risks that multiple analyses flagged, and drop dimensions that reported no risk (mention them only in the summary table). Your final message must be the complete report and nothing else.`;
|
|
170
|
+
const result = await this.claudeExecutor.executeTurn(synthesisPrompt, workingDir, {
|
|
171
|
+
systemPrompt: RISK_ANALYSIS_SYSTEM_PROMPT,
|
|
172
|
+
readOnly: true,
|
|
173
|
+
permissionMode: 'plan'
|
|
174
|
+
});
|
|
175
|
+
return result.lastMessage;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=risk-analysis-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk-analysis-executor.js","sourceRoot":"","sources":["../../src/services/risk-analysis-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/services/task-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/services/task-executor.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAc/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAOxE,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,cAAc,CAAgC;IACtD,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,iBAAiB,CAAU;;IAanC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAmBvB;;;;OAIG;YACW,iBAAiB;IA8B/B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,gBAAgB;IAOxB;;;;OAIG;YACW,iBAAiB;IAkI/B;;;;OAIG;YACW,aAAa;IA2DrB,eAAe,CACnB,aAAa,GAAE,OAAe,EAC9B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,GAAE,aAAwB,EAC9B,UAAU,GAAE,OAAe,EAC3B,cAAc,CAAC,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC;YAyRF,0BAA0B;YA6D1B,WAAW;YAwMX,wBAAwB;IAwNhC,6BAA6B,CACjC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;CAsJjB"}
|
|
@@ -7,6 +7,7 @@ import { ConfigManager } from '../config.js';
|
|
|
7
7
|
import { AddressTaskExecutor } from './address-task-executor.js';
|
|
8
8
|
import { createGitManager, createPRService, createRepositoryManager } from './service-factory.js';
|
|
9
9
|
import { PromptRewriter } from './prompt-rewriter.js';
|
|
10
|
+
import { claudeSpinner } from './interjection-manager.js';
|
|
10
11
|
import { CollaborativeExecutor } from './collaborative-executor.js';
|
|
11
12
|
const SELF_REVIEW_PROMPT = "ok review the changes in the current branch carefully. make sure it follows repo convention, reusable component/utils/etc. don't over-engineer, dont reinvent.";
|
|
12
13
|
export class TaskExecutor {
|
|
@@ -144,7 +145,7 @@ export class TaskExecutor {
|
|
|
144
145
|
// Prepare prompt for Claude to fix the errors
|
|
145
146
|
const fixPrompt = `Fix the following pre-commit hook errors:\n\n${errorDetails}\n\nPlease fix all TypeScript errors, linting issues, and any other problems preventing the commit.`;
|
|
146
147
|
if (!quiet)
|
|
147
|
-
spinner =
|
|
148
|
+
spinner = claudeSpinner('Running Claude to fix pre-commit errors...').start();
|
|
148
149
|
try {
|
|
149
150
|
// Run Claude to fix the errors (pass session ID to maintain context)
|
|
150
151
|
const fixResult = await this.getClaudeExecutor().executeTask(fixPrompt, executionPath, sessionId);
|
|
@@ -186,7 +187,7 @@ export class TaskExecutor {
|
|
|
186
187
|
async runSelfReview(executionPath, taskUuid, quiet, sessionId) {
|
|
187
188
|
let spinner = quiet
|
|
188
189
|
? null
|
|
189
|
-
:
|
|
190
|
+
: claudeSpinner('Running self-review with Claude Code...').start();
|
|
190
191
|
const reviewResult = await this.getClaudeExecutor().executeTask(SELF_REVIEW_PROMPT, executionPath, sessionId);
|
|
191
192
|
sessionId = reviewResult.sessionId;
|
|
192
193
|
if (spinner)
|
|
@@ -482,7 +483,7 @@ export class TaskExecutor {
|
|
|
482
483
|
spinner.succeed(`Worktree created: ${worktreePath}`);
|
|
483
484
|
await this.jobManager.updateTaskBranch(task.uuid, branchName);
|
|
484
485
|
if (!quiet)
|
|
485
|
-
spinner =
|
|
486
|
+
spinner = claudeSpinner('Executing task with Claude Code...').start();
|
|
486
487
|
// Append repository-specific instructions to the task if they exist
|
|
487
488
|
let taskWithInstructions = task.description;
|
|
488
489
|
if (this.repoInstructions) {
|
|
@@ -649,7 +650,7 @@ export class TaskExecutor {
|
|
|
649
650
|
if (spinner)
|
|
650
651
|
spinner.succeed('Task marked as active');
|
|
651
652
|
if (!quiet)
|
|
652
|
-
spinner =
|
|
653
|
+
spinner = claudeSpinner('Executing task with Claude Code...').start();
|
|
653
654
|
// Append repository-specific instructions to the task if they exist
|
|
654
655
|
let taskWithInstructions = task.description;
|
|
655
656
|
if (this.repoInstructions) {
|