ai-control-center 1.15.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/LICENSE +21 -0
- package/README.md +584 -0
- package/bin/aicc.js +772 -0
- package/lib/actions/approve.js +71 -0
- package/lib/actions/assign-project.js +132 -0
- package/lib/actions/browser-test.js +64 -0
- package/lib/actions/cleanup.js +174 -0
- package/lib/actions/debug.js +298 -0
- package/lib/actions/deploy.js +1229 -0
- package/lib/actions/fix-bug.js +134 -0
- package/lib/actions/new-feature.js +255 -0
- package/lib/actions/reject.js +307 -0
- package/lib/actions/review.js +706 -0
- package/lib/actions/status.js +47 -0
- package/lib/agents/browser-qa-agent.js +611 -0
- package/lib/agents/payment-agent.js +116 -0
- package/lib/agents/suggestion-agent.js +88 -0
- package/lib/cli.js +303 -0
- package/lib/config.js +243 -0
- package/lib/hub/hub-server.js +440 -0
- package/lib/hub/project-poller.js +75 -0
- package/lib/hub/skill-registry.js +89 -0
- package/lib/hub/state-aggregator.js +204 -0
- package/lib/index.js +471 -0
- package/lib/init/doctor.js +523 -0
- package/lib/init/presets.js +222 -0
- package/lib/init/skill-fetcher.js +77 -0
- package/lib/init/wizard.js +973 -0
- package/lib/integrations/codex-runner.js +128 -0
- package/lib/integrations/github-actions.js +248 -0
- package/lib/integrations/github-reporter.js +229 -0
- package/lib/integrations/screenshot-store.js +102 -0
- package/lib/openclaw/bridge.js +650 -0
- package/lib/openclaw/generate-skill.js +235 -0
- package/lib/openclaw/openclaw.json +64 -0
- package/lib/orchestrator/autonomous-loop.js +429 -0
- package/lib/orchestrator/thread-triggers.js +63 -0
- package/lib/roleplay/agent-messenger.js +75 -0
- package/lib/roleplay/discussion-threads.js +303 -0
- package/lib/roleplay/health-monitor.js +121 -0
- package/lib/roleplay/pm-agent.js +513 -0
- package/lib/roleplay/roleplay-config.js +25 -0
- package/lib/roleplay/room.js +164 -0
- package/lib/shared/action-runner.js +2330 -0
- package/lib/shared/event-bus.js +185 -0
- package/lib/slack/bot.js +378 -0
- package/lib/telegram/bot.js +416 -0
- package/lib/telegram/commands.js +1267 -0
- package/lib/telegram/keyboards.js +113 -0
- package/lib/telegram/notifications.js +247 -0
- package/lib/twitch/bot.js +354 -0
- package/lib/twitch/commands.js +302 -0
- package/lib/twitch/notifications.js +63 -0
- package/lib/utils/achievements.js +191 -0
- package/lib/utils/activity-log.js +182 -0
- package/lib/utils/agent-leaderboard.js +119 -0
- package/lib/utils/audit-logger.js +232 -0
- package/lib/utils/codebase-context.js +288 -0
- package/lib/utils/codebase-indexer.js +381 -0
- package/lib/utils/config-schema.js +230 -0
- package/lib/utils/context-compressor.js +172 -0
- package/lib/utils/correlation.js +63 -0
- package/lib/utils/cost-tracker.js +423 -0
- package/lib/utils/cron-scheduler.js +53 -0
- package/lib/utils/db-adapter.js +293 -0
- package/lib/utils/display.js +272 -0
- package/lib/utils/errors.js +116 -0
- package/lib/utils/format.js +134 -0
- package/lib/utils/intent-engine.js +464 -0
- package/lib/utils/mcp-client.js +238 -0
- package/lib/utils/model-ab-test.js +164 -0
- package/lib/utils/notify.js +122 -0
- package/lib/utils/persona-loader.js +80 -0
- package/lib/utils/pipeline-lock.js +73 -0
- package/lib/utils/pipeline.js +214 -0
- package/lib/utils/plugin-runner.js +234 -0
- package/lib/utils/rate-limiter.js +84 -0
- package/lib/utils/rbac.js +74 -0
- package/lib/utils/runner.js +1809 -0
- package/lib/utils/security.js +191 -0
- package/lib/utils/self-healer.js +144 -0
- package/lib/utils/skill-loader.js +255 -0
- package/lib/utils/spinner.js +132 -0
- package/lib/utils/stage-queue.js +50 -0
- package/lib/utils/state-machine.js +89 -0
- package/lib/utils/status-bar.js +327 -0
- package/lib/utils/token-estimator.js +101 -0
- package/lib/utils/ux-analyzer.js +101 -0
- package/lib/utils/webhook-emitter.js +83 -0
- package/lib/web/public/css/styles.css +417 -0
- package/lib/web/public/dark-mode.js +44 -0
- package/lib/web/public/hub/kanban.html +206 -0
- package/lib/web/public/index.html +45 -0
- package/lib/web/public/js/app.js +71 -0
- package/lib/web/public/js/ask.js +110 -0
- package/lib/web/public/js/dashboard.js +165 -0
- package/lib/web/public/js/deploy.js +72 -0
- package/lib/web/public/js/feature.js +79 -0
- package/lib/web/public/js/health.js +65 -0
- package/lib/web/public/js/logs.js +93 -0
- package/lib/web/public/js/review.js +123 -0
- package/lib/web/public/js/ws-client.js +82 -0
- package/lib/web/public/office/css/office.css +678 -0
- package/lib/web/public/office/index.html +148 -0
- package/lib/web/public/office/js/achievements-ui.js +117 -0
- package/lib/web/public/office/js/character.js +1056 -0
- package/lib/web/public/office/js/chat-bubbles.js +177 -0
- package/lib/web/public/office/js/cost-overlay.js +123 -0
- package/lib/web/public/office/js/day-night.js +68 -0
- package/lib/web/public/office/js/effects.js +632 -0
- package/lib/web/public/office/js/engine.js +146 -0
- package/lib/web/public/office/js/feature-ticket.js +216 -0
- package/lib/web/public/office/js/hub-client.js +60 -0
- package/lib/web/public/office/js/main.js +1757 -0
- package/lib/web/public/office/js/office-layout.js +1524 -0
- package/lib/web/public/office/js/pathfinding.js +144 -0
- package/lib/web/public/office/js/pixel-sprites.js +1454 -0
- package/lib/web/public/office/js/progress-bars.js +117 -0
- package/lib/web/public/office/js/replay.js +191 -0
- package/lib/web/public/office/js/sound-effects.js +91 -0
- package/lib/web/public/office/js/sprite-renderer.js +211 -0
- package/lib/web/public/office/js/stamina-system.js +89 -0
- package/lib/web/public/office/js/ui.js +107 -0
- package/lib/web/public/onboarding/index.html +243 -0
- package/lib/web/public/timeline/index.html +195 -0
- package/lib/web/routes/api.js +499 -0
- package/lib/web/routes/logs.js +20 -0
- package/lib/web/routes/metrics.js +99 -0
- package/lib/web/server.js +183 -0
- package/lib/web/ws/handler.js +65 -0
- package/package.json +67 -0
- package/templates/agent-architect.md +69 -0
- package/templates/agent-gemini-pm.md +49 -0
- package/templates/agent-gemini-reviewer.md +52 -0
- package/templates/copilot-instructions.md +36 -0
- package/templates/pipelines/mobile.json +27 -0
- package/templates/pipelines/nodejs-api.json +27 -0
- package/templates/pipelines/python.json +27 -0
- package/templates/pipelines/react.json +27 -0
- package/templates/pipelines/salesforce.json +27 -0
- package/templates/role-gemini.md +97 -0
- package/templates/skill-architect.md +114 -0
- package/templates/skill-browser-qa.md +50 -0
- package/templates/skill-bug-from-qa.md +58 -0
- package/templates/skill-chatbot.md +93 -0
- package/templates/skill-implement.md +78 -0
- package/templates/skill-openclaw.md +174 -0
- package/templates/skill-payment.md +110 -0
- package/templates/skill-pm-spec.md +77 -0
- package/templates/skill-requirement-capture.md +97 -0
- package/templates/skill-review.md +108 -0
- package/templates/skill-reviewer-qa.md +44 -0
- package/templates/skill-suggestion.md +45 -0
- package/templates/skill-template.md +142 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { logActivity, printActivityFooter, printActivityHeader } from '../utils/activity-log.js';
|
|
5
|
+
import { printError, printSuccess } from '../utils/display.js';
|
|
6
|
+
import { getStatus, getWorkflowDir, updateStatus } from '../utils/pipeline.js';
|
|
7
|
+
import { runCopilot } from '../utils/runner.js';
|
|
8
|
+
import { spinCycle, spinner } from '../utils/spinner.js';
|
|
9
|
+
import { statusBar } from '../utils/status-bar.js';
|
|
10
|
+
|
|
11
|
+
// ─── Multi-line reason input (paste-safe) ─────────────────────────────────────
|
|
12
|
+
// Behaves like Claude Code: paste freely including newlines.
|
|
13
|
+
// Submit: press Enter on a blank line, or Ctrl+D.
|
|
14
|
+
// Cancel: Ctrl+C exits the process.
|
|
15
|
+
//
|
|
16
|
+
// Raw-mode approach — reads stdin char-by-char so pasted \n chars are buffered
|
|
17
|
+
// rather than immediately submitted (inquirer 'input' submits on first \n).
|
|
18
|
+
|
|
19
|
+
function readReasonInput() {
|
|
20
|
+
return new Promise((resolve, _reject) => {
|
|
21
|
+
const stdin = process.stdin;
|
|
22
|
+
const isTTY = !!stdin.isTTY;
|
|
23
|
+
const savedRaw = isTTY ? stdin.isRaw : false;
|
|
24
|
+
|
|
25
|
+
if (isTTY) {
|
|
26
|
+
stdin.setRawMode(true);
|
|
27
|
+
stdin.resume();
|
|
28
|
+
}
|
|
29
|
+
stdin.setEncoding('utf8');
|
|
30
|
+
|
|
31
|
+
statusBar.pause();
|
|
32
|
+
|
|
33
|
+
process.stdout.write('\n');
|
|
34
|
+
process.stdout.write(
|
|
35
|
+
` ${chalk.cyan('◆')} ${chalk.bold('Reason for rejection')} ` +
|
|
36
|
+
chalk.dim('(be specific so Copilot can fix it)') + '\n'
|
|
37
|
+
);
|
|
38
|
+
process.stdout.write(
|
|
39
|
+
chalk.dim(' Paste freely · press Enter on a blank line or Ctrl+D to submit\n\n')
|
|
40
|
+
);
|
|
41
|
+
process.stdout.write(' ');
|
|
42
|
+
|
|
43
|
+
let buffer = ''; // full accumulated text
|
|
44
|
+
let currentLine = ''; // chars typed on the current visual line
|
|
45
|
+
let lastWasNL = false; // was the previous processed char a newline?
|
|
46
|
+
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
stdin.off('data', onData);
|
|
49
|
+
if (isTTY) stdin.setRawMode(savedRaw || false);
|
|
50
|
+
statusBar.resume();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const submit = () => {
|
|
54
|
+
cleanup();
|
|
55
|
+
process.stdout.write('\n');
|
|
56
|
+
resolve((buffer + currentLine).trim());
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const onData = (chunk) => {
|
|
60
|
+
// ── Normalise line endings ──────────────────────────────────────────────
|
|
61
|
+
// Replace \r\n (Windows) and bare \r (old Mac) with \n so the loop only
|
|
62
|
+
// ever sees a single newline per line break.
|
|
63
|
+
const normalised = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
64
|
+
|
|
65
|
+
// ── Paste vs. typed detection (same approach as Claude Code CLI) ────────
|
|
66
|
+
// A manually-typed Enter arrives as a 1-char chunk ("\n").
|
|
67
|
+
// Pasted text arrives as one large multi-char chunk with embedded newlines.
|
|
68
|
+
//
|
|
69
|
+
// Rule: only ARM the double-Enter-to-submit trigger when the newline came
|
|
70
|
+
// from a single-character chunk — i.e. the user physically pressed Enter.
|
|
71
|
+
// Newlines embedded inside a paste chunk are mid-paste line breaks and
|
|
72
|
+
// must NOT set lastWasNL, so a multi-paragraph paste never auto-submits.
|
|
73
|
+
const isTyped = normalised.length === 1;
|
|
74
|
+
|
|
75
|
+
for (const char of normalised) {
|
|
76
|
+
|
|
77
|
+
// ── Ctrl+C ─────────────────────────────────────────────────────────
|
|
78
|
+
if (char === '\u0003') {
|
|
79
|
+
cleanup();
|
|
80
|
+
process.stdout.write('\n');
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Ctrl+D (EOF) ────────────────────────────────────────────────────
|
|
85
|
+
if (char === '\u0004') {
|
|
86
|
+
if ((buffer + currentLine).trim()) submit();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Backspace ───────────────────────────────────────────────────────
|
|
91
|
+
if (char === '\u007f' || char === '\b') {
|
|
92
|
+
if (currentLine.length > 0) {
|
|
93
|
+
currentLine = currentLine.slice(0, -1);
|
|
94
|
+
process.stdout.write('\b \b');
|
|
95
|
+
}
|
|
96
|
+
lastWasNL = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Newline ─────────────────────────────────────────────────────────
|
|
101
|
+
if (char === '\n') {
|
|
102
|
+
// Double-Enter on blank line → submit, BUT only when both presses
|
|
103
|
+
// were typed (not pasted). lastWasNL is only true after a typed Enter.
|
|
104
|
+
if (currentLine === '' && lastWasNL && buffer.trim()) {
|
|
105
|
+
submit();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
buffer += currentLine + '\n';
|
|
109
|
+
currentLine = '';
|
|
110
|
+
// Arm double-Enter only for typed newlines; pasted newlines do NOT
|
|
111
|
+
// change lastWasNL so a fast multi-paragraph paste never fires submit.
|
|
112
|
+
lastWasNL = isTyped;
|
|
113
|
+
process.stdout.write('\r\n ');
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Printable character ─────────────────────────────────────────────
|
|
118
|
+
lastWasNL = false;
|
|
119
|
+
currentLine += char;
|
|
120
|
+
process.stdout.write(char);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
stdin.on('data', onData);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Stages that can enter this flow ───────────────────────────────────────────
|
|
129
|
+
const REJECT_STAGES = ['review_complete', 'arch_complete', 'implementation_complete'];
|
|
130
|
+
const REDISPATCH_STAGE = 'rejected'; // re-send to Copilot without asking for a new reason
|
|
131
|
+
|
|
132
|
+
export async function rejectAction() {
|
|
133
|
+
const status = getStatus();
|
|
134
|
+
const stage = status.stage;
|
|
135
|
+
const featureId = status.current_feature;
|
|
136
|
+
|
|
137
|
+
if (!REJECT_STAGES.includes(stage) && stage !== REDISPATCH_STAGE) {
|
|
138
|
+
printError(`Cannot dispatch fixes — current stage is "${stage}".`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
printActivityHeader('PIPELINE — Dispatch Fixes to Copilot');
|
|
143
|
+
|
|
144
|
+
const workflowDir = getWorkflowDir();
|
|
145
|
+
|
|
146
|
+
// ─── Stage: rejected ──────────────────────────────────────────────────────
|
|
147
|
+
// Feature already rejected. Re-dispatch the existing review to Copilot.
|
|
148
|
+
// No reason prompt, no reject.sh — just send the review comments to Copilot.
|
|
149
|
+
if (stage === REDISPATCH_STAGE) {
|
|
150
|
+
logActivity('PIPELINE', 'Feature already rejected. Re-dispatching review comments to Copilot...');
|
|
151
|
+
await dispatchReviewToCopilot(workflowDir, featureId, status.rejection_reason || 'Fix all blockers and warnings from the review.');
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─── Stage: review_complete / arch_complete / implementation_complete ─────
|
|
156
|
+
// First rejection: ask reason, update state, then dispatch to Copilot.
|
|
157
|
+
let reason = '';
|
|
158
|
+
while (reason.length < 10) {
|
|
159
|
+
reason = await readReasonInput();
|
|
160
|
+
if (reason.length < 10) {
|
|
161
|
+
process.stdout.write(chalk.red(' Please provide at least 10 characters.\n'));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create rejection record inline (no external .sh dependency)
|
|
166
|
+
const reasonOneLiner = reason.replace(/\s+/g, ' ').trim();
|
|
167
|
+
const _wfDir = getWorkflowDir();
|
|
168
|
+
const _rejectedDir = resolve(_wfDir, 'rejected');
|
|
169
|
+
if (!existsSync(_rejectedDir)) mkdirSync(_rejectedDir, { recursive: true });
|
|
170
|
+
const _rejReviewsDir = resolve(_wfDir, 'reviews');
|
|
171
|
+
if (existsSync(_rejReviewsDir)) {
|
|
172
|
+
const _rr = readdirSync(_rejReviewsDir).filter(f => f.endsWith('.md')).sort().reverse();
|
|
173
|
+
if (_rr.length) copyFileSync(resolve(_rejReviewsDir, _rr[0]), resolve(_rejectedDir, _rr[0]));
|
|
174
|
+
}
|
|
175
|
+
const _rts = new Date();
|
|
176
|
+
const _rpad = n => String(n).padStart(2, '0');
|
|
177
|
+
const _rstamp = `${_rts.getFullYear()}${_rpad(_rts.getMonth()+1)}${_rpad(_rts.getDate())}-${_rpad(_rts.getHours())}${_rpad(_rts.getMinutes())}${_rpad(_rts.getSeconds())}`;
|
|
178
|
+
writeFileSync(resolve(_rejectedDir, `REJECTION-${_rstamp}.md`), [
|
|
179
|
+
`# Rejection Record`,
|
|
180
|
+
`**Feature:** ${featureId}`,
|
|
181
|
+
`**Rejected at:** ${_rts.toLocaleString()}`,
|
|
182
|
+
`**Reason:** ${reasonOneLiner}`,
|
|
183
|
+
`**Status:** REJECTED ❌`,
|
|
184
|
+
``,
|
|
185
|
+
`## Required Fixes`,
|
|
186
|
+
reason.trim(),
|
|
187
|
+
``,
|
|
188
|
+
].join('\n'));
|
|
189
|
+
const _rejectHistory = [...(status.history || []), { id: featureId, stage: 'rejected', time: _rts.toISOString() }];
|
|
190
|
+
updateStatus({ stage: 'rejected', rejection_reason: reasonOneLiner, next: 'copilot_fix', history: _rejectHistory });
|
|
191
|
+
printSuccess('Feature rejected — dispatching fixes to Copilot.');
|
|
192
|
+
|
|
193
|
+
await dispatchReviewToCopilot(workflowDir, featureId, reason.trim());
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Core: find review → build prompt → send to Copilot ─────────────────────
|
|
198
|
+
|
|
199
|
+
async function dispatchReviewToCopilot(workflowDir, featureId, fallbackReason) {
|
|
200
|
+
// ── After Gemini review → pass review blockers to Copilot ────────────────
|
|
201
|
+
const reviewsDir = resolve(workflowDir, 'reviews');
|
|
202
|
+
if (existsSync(reviewsDir)) {
|
|
203
|
+
const reviewFiles = readdirSync(reviewsDir)
|
|
204
|
+
.filter(f => f.endsWith('.md') && readFileSync(resolve(reviewsDir, f), 'utf8').trim().length > 50)
|
|
205
|
+
.sort()
|
|
206
|
+
.reverse();
|
|
207
|
+
|
|
208
|
+
if (reviewFiles.length) {
|
|
209
|
+
const reviewRelPath = `.ai-workflow/reviews/${reviewFiles[0]}`;
|
|
210
|
+
const reviewAbsPath = resolve(reviewsDir, reviewFiles[0]);
|
|
211
|
+
const actionItems = parseActionItems(reviewAbsPath);
|
|
212
|
+
|
|
213
|
+
const copilotPrompt = buildFixPrompt(reviewRelPath, fallbackReason, actionItems);
|
|
214
|
+
|
|
215
|
+
logActivity('COPILOT', `Fixing review blockers from ${reviewRelPath}...`);
|
|
216
|
+
|
|
217
|
+
const stopCycle = spinCycle('Copilot', [
|
|
218
|
+
'Reading review blockers and warnings...',
|
|
219
|
+
'Fixing architecture issues...',
|
|
220
|
+
'Addressing code quality findings...',
|
|
221
|
+
'Updating test classes...',
|
|
222
|
+
'Finalizing fixes...',
|
|
223
|
+
], 5000, 'blue');
|
|
224
|
+
|
|
225
|
+
await runCopilot(
|
|
226
|
+
`${copilotPrompt}\n\nFix all the listed issues in the codebase. ` +
|
|
227
|
+
`Make targeted changes only — do not refactor anything beyond what is required to address the blockers.`,
|
|
228
|
+
{ featureId }
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
stopCycle();
|
|
232
|
+
spinner.succeed('Fixes dispatched to Copilot — run Review Code to verify');
|
|
233
|
+
|
|
234
|
+
updateStatus({ stage: 'implementation_complete', next: 'review' });
|
|
235
|
+
logActivity('COPILOT', 'Fixes applied — stage reset to implementation_complete', 'success');
|
|
236
|
+
logActivity('PIPELINE', 'Run "Review Code" next to verify the fixes.', 'info');
|
|
237
|
+
printActivityFooter();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── Fallback: no review file → fix from tasks context ─────────────────────
|
|
243
|
+
const tasksDir = resolve(workflowDir, 'tasks');
|
|
244
|
+
if (existsSync(tasksDir)) {
|
|
245
|
+
const files = readdirSync(tasksDir).filter(f => f.endsWith('.md')).sort().reverse();
|
|
246
|
+
if (files.length) {
|
|
247
|
+
const tasksRelPath = `.ai-workflow/tasks/${files[0]}`;
|
|
248
|
+
|
|
249
|
+
logActivity('COPILOT', `No review file found. Using tasks context: ${tasksRelPath}...`);
|
|
250
|
+
|
|
251
|
+
const stopCycle = spinCycle('Copilot', [
|
|
252
|
+
'Reading tasks and issue description...',
|
|
253
|
+
'Implementing the fix...',
|
|
254
|
+
'Updating affected files...',
|
|
255
|
+
], 5000, 'blue');
|
|
256
|
+
|
|
257
|
+
await runCopilot(
|
|
258
|
+
`Read ${tasksRelPath} for context and fix the following issue:\n\n${fallbackReason}\n\n` +
|
|
259
|
+
`Make targeted changes only — do not refactor anything unrelated to the issue.`,
|
|
260
|
+
{ featureId }
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
stopCycle();
|
|
264
|
+
spinner.succeed('Fix dispatched to Copilot — run Review Code to verify');
|
|
265
|
+
|
|
266
|
+
updateStatus({ stage: 'implementation_complete', next: 'review' });
|
|
267
|
+
logActivity('COPILOT', 'Fix applied — stage reset to implementation_complete', 'success');
|
|
268
|
+
logActivity('PIPELINE', 'Run "Review Code" next to verify the fix.', 'info');
|
|
269
|
+
} else {
|
|
270
|
+
printError('No review file or tasks file found. Cannot dispatch to Copilot.');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
printActivityFooter();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
function buildFixPrompt(reviewFile, reason, actionItems) {
|
|
280
|
+
const lines = [
|
|
281
|
+
`Read ${reviewFile}`,
|
|
282
|
+
`Fix all ✗ Blockers and ~ Warnings listed in the review.`,
|
|
283
|
+
'',
|
|
284
|
+
`Rejection reason: "${reason}"`,
|
|
285
|
+
];
|
|
286
|
+
if (actionItems.length) {
|
|
287
|
+
lines.push('');
|
|
288
|
+
lines.push('Action Items:');
|
|
289
|
+
actionItems.forEach((item, i) => lines.push(`${i + 1}. ${item}`));
|
|
290
|
+
}
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function parseActionItems(reviewFilePath) {
|
|
295
|
+
try {
|
|
296
|
+
const content = readFileSync(reviewFilePath, 'utf8');
|
|
297
|
+
const actionSection = content.split(/## Action Items for Copilot/i)[1];
|
|
298
|
+
if (!actionSection) return [];
|
|
299
|
+
return actionSection
|
|
300
|
+
.split('\n')
|
|
301
|
+
.filter(l => /^\d+\./.test(l.trim()))
|
|
302
|
+
.map(l => l.replace(/^\d+\.\s*\*?\*?/, '').replace(/\*?\*?$/, '').trim())
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
} catch {
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|