create-walle 0.1.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.
- package/bin/create-walle.js +134 -0
- package/package.json +18 -0
- package/template/.env.example +40 -0
- package/template/CLAUDE.md +12 -0
- package/template/LICENSE +21 -0
- package/template/README.md +167 -0
- package/template/bin/setup.js +100 -0
- package/template/claude-code-skill.md +60 -0
- package/template/claude-task-manager/api-prompts.js +1841 -0
- package/template/claude-task-manager/api-reviews.js +275 -0
- package/template/claude-task-manager/approval-agent.js +454 -0
- package/template/claude-task-manager/bin/restart-ctm.sh +16 -0
- package/template/claude-task-manager/db.js +1721 -0
- package/template/claude-task-manager/docs/PROMPT-MANAGEMENT-DESIGN.md +631 -0
- package/template/claude-task-manager/git-utils.js +214 -0
- package/template/claude-task-manager/package-lock.json +1607 -0
- package/template/claude-task-manager/package.json +31 -0
- package/template/claude-task-manager/prompt-harvest.js +1148 -0
- package/template/claude-task-manager/public/css/prompts.css +880 -0
- package/template/claude-task-manager/public/css/reviews.css +430 -0
- package/template/claude-task-manager/public/css/walle.css +732 -0
- package/template/claude-task-manager/public/favicon.ico +0 -0
- package/template/claude-task-manager/public/icon.svg +37 -0
- package/template/claude-task-manager/public/index.html +8346 -0
- package/template/claude-task-manager/public/js/prompts.js +3159 -0
- package/template/claude-task-manager/public/js/reviews.js +1292 -0
- package/template/claude-task-manager/public/js/walle.js +3081 -0
- package/template/claude-task-manager/public/manifest.json +13 -0
- package/template/claude-task-manager/public/prompts.html +4353 -0
- package/template/claude-task-manager/public/setup.html +216 -0
- package/template/claude-task-manager/queue-engine.js +404 -0
- package/template/claude-task-manager/server-state.js +5 -0
- package/template/claude-task-manager/server.js +2254 -0
- package/template/claude-task-manager/session-utils.js +124 -0
- package/template/claude-task-manager/start.sh +17 -0
- package/template/claude-task-manager/tests/test-ai-search.js +61 -0
- package/template/claude-task-manager/tests/test-editor-ux.js +76 -0
- package/template/claude-task-manager/tests/test-editor-ux2.js +51 -0
- package/template/claude-task-manager/tests/test-features-v2.js +127 -0
- package/template/claude-task-manager/tests/test-insights-cached.js +78 -0
- package/template/claude-task-manager/tests/test-insights.js +124 -0
- package/template/claude-task-manager/tests/test-permissions-v2.js +127 -0
- package/template/claude-task-manager/tests/test-permissions.js +122 -0
- package/template/claude-task-manager/tests/test-pin.js +51 -0
- package/template/claude-task-manager/tests/test-prompts.js +164 -0
- package/template/claude-task-manager/tests/test-recent-sessions.js +96 -0
- package/template/claude-task-manager/tests/test-review.js +104 -0
- package/template/claude-task-manager/tests/test-send-dropdown.js +76 -0
- package/template/claude-task-manager/tests/test-send-final.js +30 -0
- package/template/claude-task-manager/tests/test-send-fixes.js +76 -0
- package/template/claude-task-manager/tests/test-send-integration.js +107 -0
- package/template/claude-task-manager/tests/test-send-visual.js +34 -0
- package/template/claude-task-manager/tests/test-session-create.js +147 -0
- package/template/claude-task-manager/tests/test-sidebar-ux.js +83 -0
- package/template/claude-task-manager/tests/test-url-hash.js +68 -0
- package/template/claude-task-manager/tests/test-ux-crop.js +34 -0
- package/template/claude-task-manager/tests/test-ux-review.js +130 -0
- package/template/claude-task-manager/tests/test-zoom-card.js +76 -0
- package/template/claude-task-manager/tests/test-zoom.js +92 -0
- package/template/claude-task-manager/tests/test-zoom2.js +67 -0
- package/template/docs/site/api/README.md +187 -0
- package/template/docs/site/guides/claude-code.md +58 -0
- package/template/docs/site/guides/configuration.md +96 -0
- package/template/docs/site/guides/quickstart.md +158 -0
- package/template/docs/site/index.md +14 -0
- package/template/docs/site/skills/README.md +135 -0
- package/template/wall-e/.dockerignore +11 -0
- package/template/wall-e/Dockerfile +25 -0
- package/template/wall-e/adapters/adapter-base.js +37 -0
- package/template/wall-e/adapters/ctm.js +193 -0
- package/template/wall-e/adapters/slack.js +56 -0
- package/template/wall-e/agent.js +319 -0
- package/template/wall-e/api-walle.js +1073 -0
- package/template/wall-e/brain.js +1235 -0
- package/template/wall-e/channels/agent-api.js +172 -0
- package/template/wall-e/channels/channel-base.js +14 -0
- package/template/wall-e/channels/imessage-channel.js +113 -0
- package/template/wall-e/channels/slack-channel.js +118 -0
- package/template/wall-e/chat.js +778 -0
- package/template/wall-e/decision/confidence.js +93 -0
- package/template/wall-e/deploy.sh +35 -0
- package/template/wall-e/docs/specs/2026-04-01-publish-plan.md +112 -0
- package/template/wall-e/docs/specs/SKILL-FORMAT.md +326 -0
- package/template/wall-e/extraction/contradiction.js +168 -0
- package/template/wall-e/extraction/knowledge-extractor.js +190 -0
- package/template/wall-e/fly.toml +24 -0
- package/template/wall-e/loops/ingest.js +34 -0
- package/template/wall-e/loops/reflect.js +63 -0
- package/template/wall-e/loops/tasks.js +487 -0
- package/template/wall-e/loops/think.js +125 -0
- package/template/wall-e/package-lock.json +533 -0
- package/template/wall-e/package.json +18 -0
- package/template/wall-e/scripts/ingest-slack-search.js +85 -0
- package/template/wall-e/scripts/pull-slack-via-claude.js +98 -0
- package/template/wall-e/scripts/slack-backfill.js +295 -0
- package/template/wall-e/scripts/slack-channel-history.js +454 -0
- package/template/wall-e/server.js +93 -0
- package/template/wall-e/skills/_bundled/email-digest/SKILL.md +95 -0
- package/template/wall-e/skills/_bundled/email-sync/SKILL.md +65 -0
- package/template/wall-e/skills/_bundled/email-sync/mail-reader.jxa +104 -0
- package/template/wall-e/skills/_bundled/email-sync/run.js +213 -0
- package/template/wall-e/skills/_bundled/google-calendar/SKILL.md +73 -0
- package/template/wall-e/skills/_bundled/google-calendar/cal-reader.swift +81 -0
- package/template/wall-e/skills/_bundled/google-calendar/run.js +181 -0
- package/template/wall-e/skills/_bundled/memory-search/SKILL.md +92 -0
- package/template/wall-e/skills/_bundled/morning-briefing/SKILL.md +131 -0
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +264 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +60 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +55 -0
- package/template/wall-e/skills/claude-code-reader.js +144 -0
- package/template/wall-e/skills/mcp-client.js +407 -0
- package/template/wall-e/skills/skill-executor.js +163 -0
- package/template/wall-e/skills/skill-loader.js +410 -0
- package/template/wall-e/skills/skill-planner.js +88 -0
- package/template/wall-e/skills/slack-ingest.js +329 -0
- package/template/wall-e/skills/slack-pull-live.js +270 -0
- package/template/wall-e/skills/tool-executor.js +188 -0
- package/template/wall-e/tests/adapter-base.test.js +20 -0
- package/template/wall-e/tests/adapter-ctm.test.js +122 -0
- package/template/wall-e/tests/adapter-slack.test.js +98 -0
- package/template/wall-e/tests/agent-api.test.js +256 -0
- package/template/wall-e/tests/api-walle.test.js +222 -0
- package/template/wall-e/tests/brain.test.js +602 -0
- package/template/wall-e/tests/channels.test.js +104 -0
- package/template/wall-e/tests/chat.test.js +103 -0
- package/template/wall-e/tests/confidence.test.js +134 -0
- package/template/wall-e/tests/contradiction.test.js +217 -0
- package/template/wall-e/tests/ingest.test.js +113 -0
- package/template/wall-e/tests/mcp-client.test.js +71 -0
- package/template/wall-e/tests/reflect.test.js +103 -0
- package/template/wall-e/tests/server.test.js +111 -0
- package/template/wall-e/tests/skills.test.js +198 -0
- package/template/wall-e/tests/slack-ingest.test.js +103 -0
- package/template/wall-e/tests/think.test.js +435 -0
- package/template/wall-e/tools/local-tools.js +697 -0
- package/template/wall-e/tools/slack-mcp.js +290 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const db = require('./db');
|
|
5
|
+
const gitUtils = require('./git-utils');
|
|
6
|
+
|
|
7
|
+
// --- Helpers ---
|
|
8
|
+
function isValidProjectPath(p) {
|
|
9
|
+
if (!p || typeof p !== 'string') return false;
|
|
10
|
+
const resolved = path.resolve(p);
|
|
11
|
+
const home = process.env.HOME || '/';
|
|
12
|
+
if (!resolved.startsWith(home + path.sep)) return false;
|
|
13
|
+
// Check for .git dir (standalone repo) or use git rev-parse (subdirectory of a repo)
|
|
14
|
+
if (fs.existsSync(path.join(resolved, '.git'))) return true;
|
|
15
|
+
try {
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
execFileSync('git', ['rev-parse', '--git-dir'], { cwd: resolved, stdio: 'pipe' });
|
|
18
|
+
return true;
|
|
19
|
+
} catch { return false; }
|
|
20
|
+
}
|
|
21
|
+
function jsonResponse(res, code, data) {
|
|
22
|
+
const body = JSON.stringify(data);
|
|
23
|
+
res.writeHead(code, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
24
|
+
res.end(body);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readBody(req, limit = 1024 * 1024) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let body = '';
|
|
30
|
+
req.on('data', c => {
|
|
31
|
+
body += c;
|
|
32
|
+
if (body.length > limit) { req.destroy(); reject(new Error('Body too large')); }
|
|
33
|
+
});
|
|
34
|
+
req.on('end', () => { try { resolve(JSON.parse(body || '{}')); } catch (e) { reject(e); } });
|
|
35
|
+
req.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Change Detection for Badge Polling ---
|
|
40
|
+
// Track last known diff stats per project to detect changes
|
|
41
|
+
const changeTracker = new Map(); // projectPath -> { files: [...], hash: string }
|
|
42
|
+
|
|
43
|
+
function computeStatsHash(files) {
|
|
44
|
+
return files.map(f => `${f.path}:${f.additions}:${f.deletions}`).sort().join('|');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function checkForChanges(projectPath) {
|
|
48
|
+
try {
|
|
49
|
+
const files = await gitUtils.getDiffStat(projectPath);
|
|
50
|
+
const hash = computeStatsHash(files);
|
|
51
|
+
const prev = changeTracker.get(projectPath);
|
|
52
|
+
changeTracker.set(projectPath, { files, hash });
|
|
53
|
+
const changed = !prev || prev.hash !== hash;
|
|
54
|
+
return { changed, fileCount: files.length, files };
|
|
55
|
+
} catch {
|
|
56
|
+
return { changed: false, fileCount: 0, files: [] };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Route Handler ---
|
|
61
|
+
function handleReviewApi(req, res, url) {
|
|
62
|
+
const p = url.pathname;
|
|
63
|
+
const m = req.method;
|
|
64
|
+
|
|
65
|
+
// GET /api/reviews/diff?project=...&base=...
|
|
66
|
+
if (p === '/api/reviews/diff' && m === 'GET') {
|
|
67
|
+
const project = url.searchParams.get('project');
|
|
68
|
+
const base = url.searchParams.get('base') || '';
|
|
69
|
+
if (!project) return jsonResponse(res, 400, { error: 'project required' });
|
|
70
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
71
|
+
const diffFn = base === '--staged'
|
|
72
|
+
? gitUtils.getStagedDiff(project)
|
|
73
|
+
: gitUtils.getFullDiff(project, base || undefined);
|
|
74
|
+
diffFn.then(files => jsonResponse(res, 200, { files }))
|
|
75
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// GET /api/reviews/diff-stat?project=...&base=...
|
|
80
|
+
if (p === '/api/reviews/diff-stat' && m === 'GET') {
|
|
81
|
+
const project = url.searchParams.get('project');
|
|
82
|
+
if (!project) return jsonResponse(res, 400, { error: 'project required' });
|
|
83
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
84
|
+
const base = url.searchParams.get('base') || '';
|
|
85
|
+
gitUtils.getDiffStat(project, base || undefined)
|
|
86
|
+
.then(files => jsonResponse(res, 200, { files }))
|
|
87
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// GET /api/reviews/commits?project=...
|
|
92
|
+
if (p === '/api/reviews/commits' && m === 'GET') {
|
|
93
|
+
const project = url.searchParams.get('project');
|
|
94
|
+
if (!project) return jsonResponse(res, 400, { error: 'project required' });
|
|
95
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
96
|
+
gitUtils.getCommits(project, 30)
|
|
97
|
+
.then(commits => jsonResponse(res, 200, { commits }))
|
|
98
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// GET /api/reviews/branch?project=...
|
|
103
|
+
if (p === '/api/reviews/branch' && m === 'GET') {
|
|
104
|
+
const project = url.searchParams.get('project');
|
|
105
|
+
if (!project) return jsonResponse(res, 400, { error: 'project required' });
|
|
106
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
107
|
+
gitUtils.getBranch(project)
|
|
108
|
+
.then(branch => jsonResponse(res, 200, { branch }))
|
|
109
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// GET /api/reviews/tracked-projects - list all tracked project folders with change counts
|
|
114
|
+
if (p === '/api/reviews/tracked-projects' && m === 'GET') {
|
|
115
|
+
const getTrackedProjects = module.exports._getTrackedProjects;
|
|
116
|
+
if (!getTrackedProjects) return jsonResponse(res, 500, { error: 'tracker not initialized' });
|
|
117
|
+
getTrackedProjects()
|
|
118
|
+
.then(projects => jsonResponse(res, 200, { projects }))
|
|
119
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// GET /api/reviews/changes?project=... - badge polling endpoint
|
|
124
|
+
if (p === '/api/reviews/changes' && m === 'GET') {
|
|
125
|
+
const project = url.searchParams.get('project');
|
|
126
|
+
if (!project) return jsonResponse(res, 400, { error: 'project required' });
|
|
127
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
128
|
+
checkForChanges(project)
|
|
129
|
+
.then(result => jsonResponse(res, 200, result))
|
|
130
|
+
.catch(e => jsonResponse(res, 500, { error: e.message }));
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// GET /api/reviews/file?project=...&path=... - serve image files from project
|
|
135
|
+
if (p === '/api/reviews/file' && m === 'GET') {
|
|
136
|
+
const project = url.searchParams.get('project');
|
|
137
|
+
const filePath = url.searchParams.get('path');
|
|
138
|
+
if (!project || !filePath) return jsonResponse(res, 400, { error: 'project and path required' });
|
|
139
|
+
if (!isValidProjectPath(project)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
140
|
+
// Only allow image extensions
|
|
141
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
142
|
+
const imageExts = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp', '.ico': 'image/x-icon', '.bmp': 'image/bmp' };
|
|
143
|
+
if (!imageExts[ext]) return jsonResponse(res, 400, { error: 'Only image files are supported' });
|
|
144
|
+
// Prevent path traversal
|
|
145
|
+
const resolved = path.resolve(project, filePath);
|
|
146
|
+
if (!resolved.startsWith(path.resolve(project) + path.sep)) return jsonResponse(res, 403, { error: 'Invalid file path' });
|
|
147
|
+
try {
|
|
148
|
+
const data = fs.readFileSync(resolved);
|
|
149
|
+
res.writeHead(200, { 'Content-Type': imageExts[ext], 'Content-Length': data.length, 'Cache-Control': 'no-cache' });
|
|
150
|
+
res.end(data);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
153
|
+
res.end('File not found');
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Review CRUD ---
|
|
159
|
+
|
|
160
|
+
// POST /api/reviews - create a new review
|
|
161
|
+
if (p === '/api/reviews' && m === 'POST') {
|
|
162
|
+
readBody(req).then(body => {
|
|
163
|
+
const { session_id, project_path, base_ref } = body;
|
|
164
|
+
if (!project_path) return jsonResponse(res, 400, { error: 'project_path required' });
|
|
165
|
+
if (!isValidProjectPath(project_path)) return jsonResponse(res, 403, { error: 'Invalid project path' });
|
|
166
|
+
const id = db.createReview({ session_id, project_path, base_ref });
|
|
167
|
+
jsonResponse(res, 201, { id });
|
|
168
|
+
}).catch(e => jsonResponse(res, 400, { error: e.message }));
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// GET /api/reviews - list reviews
|
|
173
|
+
if (p === '/api/reviews' && m === 'GET') {
|
|
174
|
+
const project_path = url.searchParams.get('project');
|
|
175
|
+
const session_id = url.searchParams.get('session');
|
|
176
|
+
const reviews = db.listReviews({ project_path, session_id });
|
|
177
|
+
return jsonResponse(res, 200, reviews);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// GET /api/reviews/:id
|
|
181
|
+
const reviewMatch = p.match(/^\/api\/reviews\/(\d+)$/);
|
|
182
|
+
if (reviewMatch && m === 'GET') {
|
|
183
|
+
const review = db.getReview(parseInt(reviewMatch[1]));
|
|
184
|
+
if (!review) return jsonResponse(res, 404, { error: 'Review not found' });
|
|
185
|
+
return jsonResponse(res, 200, review);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// PUT /api/reviews/:id
|
|
189
|
+
if (reviewMatch && m === 'PUT') {
|
|
190
|
+
readBody(req).then(body => {
|
|
191
|
+
db.updateReview(parseInt(reviewMatch[1]), body);
|
|
192
|
+
jsonResponse(res, 200, { ok: true });
|
|
193
|
+
}).catch(e => jsonResponse(res, 400, { error: e.message }));
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// DELETE /api/reviews/:id
|
|
198
|
+
if (reviewMatch && m === 'DELETE') {
|
|
199
|
+
db.deleteReview(parseInt(reviewMatch[1]));
|
|
200
|
+
return jsonResponse(res, 200, { ok: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- Review Comments ---
|
|
204
|
+
|
|
205
|
+
// POST /api/reviews/:id/comments
|
|
206
|
+
const commentsMatch = p.match(/^\/api\/reviews\/(\d+)\/comments$/);
|
|
207
|
+
if (commentsMatch && m === 'POST') {
|
|
208
|
+
readBody(req).then(body => {
|
|
209
|
+
body.review_id = parseInt(commentsMatch[1]);
|
|
210
|
+
const id = db.addReviewComment(body);
|
|
211
|
+
jsonResponse(res, 201, { id });
|
|
212
|
+
}).catch(e => jsonResponse(res, 400, { error: e.message }));
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// PUT /api/review-comments/:id
|
|
217
|
+
const commentMatch = p.match(/^\/api\/review-comments\/(\d+)$/);
|
|
218
|
+
if (commentMatch && m === 'PUT') {
|
|
219
|
+
readBody(req).then(body => {
|
|
220
|
+
db.updateReviewComment(parseInt(commentMatch[1]), body);
|
|
221
|
+
jsonResponse(res, 200, { ok: true });
|
|
222
|
+
}).catch(e => jsonResponse(res, 400, { error: e.message }));
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// DELETE /api/review-comments/:id
|
|
227
|
+
if (commentMatch && m === 'DELETE') {
|
|
228
|
+
db.deleteReviewComment(parseInt(commentMatch[1]));
|
|
229
|
+
return jsonResponse(res, 200, { ok: true });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Compose review prompt for sending to Claude ---
|
|
233
|
+
const composeMatch = p.match(/^\/api\/reviews\/(\d+)\/compose$/);
|
|
234
|
+
if (composeMatch && m === 'GET') {
|
|
235
|
+
const review = db.getReview(parseInt(composeMatch[1]));
|
|
236
|
+
if (!review) return jsonResponse(res, 404, { error: 'Review not found' });
|
|
237
|
+
const prompt = composeReviewPrompt(review);
|
|
238
|
+
return jsonResponse(res, 200, { prompt });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false; // Not handled
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Compose structured review prompt from comments
|
|
245
|
+
function composeReviewPrompt(review) {
|
|
246
|
+
const comments = review.comments.filter(c => c.status === 'open');
|
|
247
|
+
if (comments.length === 0) return 'No review comments to address.';
|
|
248
|
+
|
|
249
|
+
const byFile = {};
|
|
250
|
+
for (const c of comments) {
|
|
251
|
+
if (!byFile[c.file_path]) byFile[c.file_path] = [];
|
|
252
|
+
byFile[c.file_path].push(c);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const lines = ['Please address these code review comments:\n'];
|
|
256
|
+
|
|
257
|
+
for (const [filePath, fileComments] of Object.entries(byFile)) {
|
|
258
|
+
lines.push(`## ${filePath}\n`);
|
|
259
|
+
for (const c of fileComments) {
|
|
260
|
+
const lineRef = c.line_end && c.line_end !== c.line_start
|
|
261
|
+
? `Lines ${c.line_start}-${c.line_end}`
|
|
262
|
+
: `Line ${c.line_start}`;
|
|
263
|
+
const severity = c.severity !== 'comment' ? ` [${c.severity}]` : '';
|
|
264
|
+
lines.push(`**${lineRef}**${severity}: ${c.body}\n`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (review.summary) {
|
|
269
|
+
lines.push(`\n## Overall\n\n${review.summary}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return lines.join('\n');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = { handleReviewApi, checkForChanges };
|