@happy-nut/monacori 0.1.3 → 0.1.6
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 +57 -53
- package/assets/monacori-demo.gif +0 -0
- package/dist/app-main.js +47 -7
- package/dist/build.js +23 -1
- package/dist/commands.js +12 -326
- package/dist/i18n.js +10 -0
- package/dist/preload.cjs +9 -0
- package/dist/render.d.ts +11 -0
- package/dist/render.js +13 -5
- package/dist/server.js +6 -0
- package/dist/types.d.ts +12 -0
- package/dist/viewer.client.js +267 -29
- package/dist/viewer.css +56 -7
- package/package.json +1 -1
- package/assets/screenshots/diff-review.png +0 -0
- package/assets/screenshots/terminal.png +0 -0
package/dist/commands.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { AGENT_SNIPPET_FILE, CONFIG_FILE, DECISIONS_FILE, FLOW_DIR, GITIGNORE_FILE, STATE_FILE } from "./constants.js";
|
|
7
|
-
import {
|
|
8
|
-
import { git
|
|
9
|
-
import { createDiffReview, serveDiffWatch } from "./server.js";
|
|
7
|
+
import { parsePositiveInteger, readOption } from "./util.js";
|
|
8
|
+
import { git } from "./git.js";
|
|
10
9
|
const nodeRequire = createRequire(import.meta.url);
|
|
11
10
|
export function main() {
|
|
12
11
|
const rawArgs = process.argv.slice(2);
|
|
@@ -27,16 +26,6 @@ export function main() {
|
|
|
27
26
|
case "install":
|
|
28
27
|
installFlow(args);
|
|
29
28
|
break;
|
|
30
|
-
case "check":
|
|
31
|
-
case "go":
|
|
32
|
-
runCheck(args);
|
|
33
|
-
break;
|
|
34
|
-
case "verify":
|
|
35
|
-
runVerification(args);
|
|
36
|
-
break;
|
|
37
|
-
case "diff":
|
|
38
|
-
renderDiffReview(args);
|
|
39
|
-
break;
|
|
40
29
|
case "app":
|
|
41
30
|
case "review":
|
|
42
31
|
launchReviewApp(args);
|
|
@@ -44,12 +33,6 @@ export function main() {
|
|
|
44
33
|
case "open":
|
|
45
34
|
openCurrentRepository(args);
|
|
46
35
|
break;
|
|
47
|
-
case "status":
|
|
48
|
-
printStatus();
|
|
49
|
-
break;
|
|
50
|
-
case "report":
|
|
51
|
-
recordReport(args);
|
|
52
|
-
break;
|
|
53
36
|
case "--help":
|
|
54
37
|
case "-h":
|
|
55
38
|
case "help":
|
|
@@ -94,7 +77,7 @@ function initFlow(args) {
|
|
|
94
77
|
if (ignored) {
|
|
95
78
|
console.log(`Updated ${GITIGNORE_FILE} to ignore ${FLOW_DIR}/ validation artifacts.`);
|
|
96
79
|
}
|
|
97
|
-
console.log("Next: run `
|
|
80
|
+
console.log("Next: run `mo` to open the diff review app.");
|
|
98
81
|
}
|
|
99
82
|
}
|
|
100
83
|
function installFlow(args) {
|
|
@@ -115,117 +98,6 @@ function installFlow(args) {
|
|
|
115
98
|
console.log(`Next: add ${FLOW_DIR}/${AGENT_SNIPPET_FILE} to your agent instructions if desired.`);
|
|
116
99
|
}
|
|
117
100
|
}
|
|
118
|
-
function runCheck(args) {
|
|
119
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
120
|
-
printCheckHelp();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
ensureWritableFlowState();
|
|
124
|
-
const config = loadConfig();
|
|
125
|
-
const separator = args.indexOf("--");
|
|
126
|
-
const commandArgs = separator >= 0 ? args.slice(separator + 1) : [];
|
|
127
|
-
const optionArgs = separator >= 0 ? args.slice(0, separator) : args;
|
|
128
|
-
const noVerify = optionArgs.includes("--no-verify");
|
|
129
|
-
const noDiff = optionArgs.includes("--no-diff");
|
|
130
|
-
const openInBrowser = optionArgs.includes("--open");
|
|
131
|
-
const includeUntracked = optionArgs.includes("--include-untracked") || config.diff.includeUntracked;
|
|
132
|
-
const staged = optionArgs.includes("--staged");
|
|
133
|
-
const base = readOption(optionArgs, "--base");
|
|
134
|
-
const contextValue = readOption(optionArgs, "--context");
|
|
135
|
-
const context = contextValue ? parsePositiveInteger(contextValue, "--context") : config.diff.context;
|
|
136
|
-
const verification = noVerify
|
|
137
|
-
? { commands: [], failed: false, skipped: true }
|
|
138
|
-
: executeVerification(commandArgs.join(" "));
|
|
139
|
-
let review;
|
|
140
|
-
if (!noDiff) {
|
|
141
|
-
review = createDiffReview({
|
|
142
|
-
base,
|
|
143
|
-
staged,
|
|
144
|
-
includeUntracked,
|
|
145
|
-
context,
|
|
146
|
-
output: join(process.cwd(), FLOW_DIR, "diffs", `${timestampForFile()}-check.html`),
|
|
147
|
-
title: "monacori validation diff",
|
|
148
|
-
});
|
|
149
|
-
if (openInBrowser) {
|
|
150
|
-
spawnSync("open", [review.path], { stdio: "ignore" });
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
const reportPath = writeCheckReport({ verification, review });
|
|
154
|
-
console.log("# monacori check");
|
|
155
|
-
console.log(`Verification: ${verification.skipped ? "skipped" : verification.failed ? "failed" : "passed"}`);
|
|
156
|
-
if (verification.logPath) {
|
|
157
|
-
console.log(`Log: ${relative(process.cwd(), verification.logPath)}`);
|
|
158
|
-
}
|
|
159
|
-
if (review) {
|
|
160
|
-
console.log(`Diff review: ${relative(process.cwd(), review.path)}`);
|
|
161
|
-
console.log(`Files: ${review.files}`);
|
|
162
|
-
console.log(`Hunks: ${review.hunks}`);
|
|
163
|
-
}
|
|
164
|
-
console.log(`Report: ${relative(process.cwd(), reportPath)}`);
|
|
165
|
-
if (verification.failed) {
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function runVerification(args) {
|
|
170
|
-
const separator = args.indexOf("--");
|
|
171
|
-
const explicitCommand = separator >= 0 ? args.slice(separator + 1).join(" ") : "";
|
|
172
|
-
const result = executeVerification(explicitCommand, { requireCommands: true });
|
|
173
|
-
if (result.logPath) {
|
|
174
|
-
console.log(`Verification log: ${relative(process.cwd(), result.logPath)}`);
|
|
175
|
-
}
|
|
176
|
-
if (result.failed) {
|
|
177
|
-
console.error("Verification failed.");
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
console.log("Verification passed.");
|
|
181
|
-
}
|
|
182
|
-
function renderDiffReview(args) {
|
|
183
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
184
|
-
printDiffHelp();
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
ensureWritableFlowState();
|
|
188
|
-
const config = loadConfig();
|
|
189
|
-
const contextValue = readOption(args, "--context");
|
|
190
|
-
const context = contextValue ? parsePositiveInteger(contextValue, "--context") : config.diff.context;
|
|
191
|
-
const base = readOption(args, "--base");
|
|
192
|
-
const staged = args.includes("--staged");
|
|
193
|
-
const includeUntracked = args.includes("--include-untracked") || config.diff.includeUntracked;
|
|
194
|
-
const openInBrowser = args.includes("--open");
|
|
195
|
-
const watch = args.includes("--watch");
|
|
196
|
-
const ignoreWhitespace = args.includes("--ignore-whitespace");
|
|
197
|
-
if (watch) {
|
|
198
|
-
serveDiffWatch({
|
|
199
|
-
base,
|
|
200
|
-
staged,
|
|
201
|
-
includeUntracked,
|
|
202
|
-
context,
|
|
203
|
-
openInBrowser,
|
|
204
|
-
port: readOption(args, "--port"),
|
|
205
|
-
ignoreWhitespace,
|
|
206
|
-
});
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const output = readOption(args, "--output") ??
|
|
210
|
-
join(process.cwd(), FLOW_DIR, "diffs", `${timestampForFile()}-review.html`);
|
|
211
|
-
const result = createDiffReview({
|
|
212
|
-
base,
|
|
213
|
-
staged,
|
|
214
|
-
includeUntracked,
|
|
215
|
-
context,
|
|
216
|
-
output,
|
|
217
|
-
title: "monacori diff review",
|
|
218
|
-
ignoreWhitespace,
|
|
219
|
-
});
|
|
220
|
-
if (openInBrowser) {
|
|
221
|
-
spawnSync("open", [result.path], { stdio: "ignore" });
|
|
222
|
-
}
|
|
223
|
-
console.log(`Diff review: ${relative(process.cwd(), result.path)}`);
|
|
224
|
-
console.log(`URL: ${result.url}`);
|
|
225
|
-
console.log(`Files: ${result.files}`);
|
|
226
|
-
console.log(`Hunks: ${result.hunks}`);
|
|
227
|
-
console.log("Keys: F7 next hunk, Shift+F7 previous hunk, Shift Shift search files, Cmd/Ctrl+E recent files, Cmd/Ctrl+Down jump to symbol.");
|
|
228
|
-
}
|
|
229
101
|
function launchReviewApp(args) {
|
|
230
102
|
if (args.includes("--help") || args.includes("-h")) {
|
|
231
103
|
printAppHelp();
|
|
@@ -290,138 +162,6 @@ function resolveElectronBinary() {
|
|
|
290
162
|
function appMainPath() {
|
|
291
163
|
return join(dirname(fileURLToPath(import.meta.url)), "app-main.js");
|
|
292
164
|
}
|
|
293
|
-
function printStatus() {
|
|
294
|
-
ensureInitialized();
|
|
295
|
-
const config = loadConfig();
|
|
296
|
-
const git = readGitSnapshot(process.cwd());
|
|
297
|
-
const reports = listRecentFiles(join(process.cwd(), FLOW_DIR, "reports"), 5);
|
|
298
|
-
const logs = listRecentFiles(join(process.cwd(), FLOW_DIR, "logs"), 5);
|
|
299
|
-
console.log(`# ${config.projectName} validation status`);
|
|
300
|
-
console.log("");
|
|
301
|
-
console.log(`Branch: ${git.branch || "(unknown)"}`);
|
|
302
|
-
console.log("");
|
|
303
|
-
console.log("## Git status");
|
|
304
|
-
console.log(git.status || "clean");
|
|
305
|
-
console.log("");
|
|
306
|
-
console.log("## Diff stat");
|
|
307
|
-
console.log(git.diffStat || "no diff");
|
|
308
|
-
console.log("");
|
|
309
|
-
console.log("## Verification commands");
|
|
310
|
-
const commands = getVerificationCommands(config);
|
|
311
|
-
if (commands.length === 0) {
|
|
312
|
-
console.log("none configured");
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
for (const command of commands) {
|
|
316
|
-
console.log(`- ${command}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
console.log("");
|
|
320
|
-
console.log("## Recent reports");
|
|
321
|
-
console.log(reports.length === 0 ? "none" : reports.map((path) => `- ${relative(process.cwd(), path)}`).join("\n"));
|
|
322
|
-
console.log("");
|
|
323
|
-
console.log("## Recent logs");
|
|
324
|
-
console.log(logs.length === 0 ? "none" : logs.map((path) => `- ${relative(process.cwd(), path)}`).join("\n"));
|
|
325
|
-
}
|
|
326
|
-
function recordReport(args) {
|
|
327
|
-
ensureWritableFlowState();
|
|
328
|
-
const file = readOption(args, "--file");
|
|
329
|
-
const label = readOption(args, "--label") ?? "manual";
|
|
330
|
-
const body = file ? readFileSync(file, "utf8") : readStdin();
|
|
331
|
-
if (body.trim().length === 0) {
|
|
332
|
-
throw new Error("No report content provided. Pass --file or pipe report text on stdin.");
|
|
333
|
-
}
|
|
334
|
-
const timestamp = timestampForFile();
|
|
335
|
-
const reportDir = join(process.cwd(), FLOW_DIR, "reports");
|
|
336
|
-
mkdirSync(reportDir, { recursive: true });
|
|
337
|
-
const reportPath = join(reportDir, `${timestamp}-${sanitizeFilePart(label)}.md`);
|
|
338
|
-
writeFileSync(reportPath, [
|
|
339
|
-
`# Monacori Report: ${label}`,
|
|
340
|
-
"",
|
|
341
|
-
`Recorded: ${new Date().toISOString()}`,
|
|
342
|
-
"",
|
|
343
|
-
body.trim(),
|
|
344
|
-
"",
|
|
345
|
-
].join("\n"));
|
|
346
|
-
appendToState(`\n## Report ${timestamp} (${label})\n\n${summarizeForState(body)}\n`);
|
|
347
|
-
console.log(`Recorded ${relative(process.cwd(), reportPath)}`);
|
|
348
|
-
}
|
|
349
|
-
function executeVerification(explicitCommand = "", options = {}) {
|
|
350
|
-
ensureWritableFlowState();
|
|
351
|
-
const config = loadConfig();
|
|
352
|
-
const commands = explicitCommand.trim() ? [explicitCommand.trim()] : getVerificationCommands(config);
|
|
353
|
-
if (commands.length === 0) {
|
|
354
|
-
if (options.requireCommands) {
|
|
355
|
-
throw new Error(`No verification commands found. Add them to ${FLOW_DIR}/${CONFIG_FILE} or pass \`-- <command>\`.`);
|
|
356
|
-
}
|
|
357
|
-
return { commands: [], failed: false, skipped: true };
|
|
358
|
-
}
|
|
359
|
-
const logPath = join(process.cwd(), FLOW_DIR, "logs", `verify-${timestampForFile()}.log`);
|
|
360
|
-
const chunks = [];
|
|
361
|
-
let failed = false;
|
|
362
|
-
for (const command of commands) {
|
|
363
|
-
chunks.push(`$ ${command}\n`);
|
|
364
|
-
const result = spawnSync(command, {
|
|
365
|
-
cwd: process.cwd(),
|
|
366
|
-
shell: true,
|
|
367
|
-
encoding: "utf8",
|
|
368
|
-
env: process.env,
|
|
369
|
-
maxBuffer: 1024 * 1024 * 100,
|
|
370
|
-
});
|
|
371
|
-
chunks.push(result.stdout ?? "");
|
|
372
|
-
chunks.push(result.stderr ?? "");
|
|
373
|
-
chunks.push(`\nexit: ${result.status ?? 1}\n\n`);
|
|
374
|
-
if ((result.status ?? 1) !== 0) {
|
|
375
|
-
failed = true;
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
writeFileSync(logPath, chunks.join(""));
|
|
380
|
-
return { commands, failed, skipped: false, logPath };
|
|
381
|
-
}
|
|
382
|
-
function writeCheckReport(input) {
|
|
383
|
-
const timestamp = timestampForFile();
|
|
384
|
-
const git = readGitSnapshot(process.cwd());
|
|
385
|
-
const reportDir = join(process.cwd(), FLOW_DIR, "reports");
|
|
386
|
-
mkdirSync(reportDir, { recursive: true });
|
|
387
|
-
const reportPath = join(reportDir, `${timestamp}-check.md`);
|
|
388
|
-
const verificationStatus = input.verification.skipped
|
|
389
|
-
? "skipped"
|
|
390
|
-
: input.verification.failed
|
|
391
|
-
? "failed"
|
|
392
|
-
: "passed";
|
|
393
|
-
const report = [
|
|
394
|
-
"# Monacori Validation Check",
|
|
395
|
-
"",
|
|
396
|
-
`Recorded: ${new Date().toISOString()}`,
|
|
397
|
-
`Branch: ${git.branch || "(unknown)"}`,
|
|
398
|
-
`Verification: ${verificationStatus}`,
|
|
399
|
-
input.verification.logPath ? `Log: ${relative(process.cwd(), input.verification.logPath)}` : "",
|
|
400
|
-
input.review ? `Diff review: ${relative(process.cwd(), input.review.path)}` : "",
|
|
401
|
-
input.review ? `Changed files: ${input.review.files}` : "",
|
|
402
|
-
input.review ? `Changed hunks: ${input.review.hunks}` : "",
|
|
403
|
-
"",
|
|
404
|
-
"## Commands",
|
|
405
|
-
input.verification.commands.length === 0
|
|
406
|
-
? "- none"
|
|
407
|
-
: input.verification.commands.map((command) => `- \`${command}\``).join("\n"),
|
|
408
|
-
"",
|
|
409
|
-
"## Git Status",
|
|
410
|
-
codeBlock(git.status || "clean"),
|
|
411
|
-
"",
|
|
412
|
-
"## Diff Stat",
|
|
413
|
-
codeBlock(git.diffStat || "no diff"),
|
|
414
|
-
"",
|
|
415
|
-
].filter((line) => line !== "").join("\n");
|
|
416
|
-
writeFileSync(reportPath, report);
|
|
417
|
-
appendToState(`\n## Check ${timestamp}\n\n- Verification: ${verificationStatus}\n${input.review ? `- Diff review: ${relative(process.cwd(), input.review.path)}\n` : ""}`);
|
|
418
|
-
return reportPath;
|
|
419
|
-
}
|
|
420
|
-
function appendToState(content) {
|
|
421
|
-
const path = join(process.cwd(), FLOW_DIR, STATE_FILE);
|
|
422
|
-
const current = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
423
|
-
writeFileSync(path, `${current.trimEnd()}\n${content}`);
|
|
424
|
-
}
|
|
425
165
|
function initialState(config) {
|
|
426
166
|
return [
|
|
427
167
|
"# Monacori Validation State",
|
|
@@ -442,27 +182,24 @@ function initialDecisions() {
|
|
|
442
182
|
return [
|
|
443
183
|
"# Monacori Decisions",
|
|
444
184
|
"",
|
|
445
|
-
"Record durable
|
|
185
|
+
"Record durable review decisions here so they do not depend on chat memory.",
|
|
446
186
|
"",
|
|
447
187
|
].join("\n");
|
|
448
188
|
}
|
|
449
189
|
function agentSnippet() {
|
|
450
190
|
return [
|
|
451
191
|
"<!-- MONACORI:START -->",
|
|
452
|
-
"## monacori
|
|
192
|
+
"## monacori Diff Review",
|
|
453
193
|
"",
|
|
454
|
-
"This repository uses monacori to
|
|
194
|
+
"This repository uses monacori to help humans review AI-generated code changes side-by-side.",
|
|
455
195
|
"",
|
|
456
|
-
"
|
|
196
|
+
"After making code changes:",
|
|
457
197
|
"",
|
|
458
|
-
"-
|
|
459
|
-
"- Use `monacori app --include-untracked` while changes are still moving.",
|
|
198
|
+
"- The user can run `mo` to open the diff review app and inspect your changes.",
|
|
460
199
|
"- Inspect changed hunks with F7 / Shift+F7.",
|
|
461
200
|
"- Use Shift Shift in the diff review to search indexed files, including unchanged files.",
|
|
462
201
|
"- In source previews, use Cmd/Ctrl+Down to jump to the declaration-like match under the cursor.",
|
|
463
|
-
"-
|
|
464
|
-
"",
|
|
465
|
-
"Do not claim a change is done without verification evidence or a precise explanation of why verification could not run.",
|
|
202
|
+
"- Inline comments left in the review are bundled into a prompt and sent back to the session.",
|
|
466
203
|
"<!-- MONACORI:END -->",
|
|
467
204
|
"",
|
|
468
205
|
].join("\n");
|
|
@@ -508,9 +245,6 @@ function loadConfig() {
|
|
|
508
245
|
},
|
|
509
246
|
};
|
|
510
247
|
}
|
|
511
|
-
function getVerificationCommands(config) {
|
|
512
|
-
return config.verification.commands.filter((command) => command.trim().length > 0);
|
|
513
|
-
}
|
|
514
248
|
function writeIfMissing(path, content, force) {
|
|
515
249
|
if (!force && existsSync(path)) {
|
|
516
250
|
return;
|
|
@@ -582,26 +316,14 @@ function packageScriptCommand(manager, script) {
|
|
|
582
316
|
function printHelp() {
|
|
583
317
|
console.log(`monacori
|
|
584
318
|
|
|
585
|
-
|
|
319
|
+
Desktop review app for AI-generated code changes.
|
|
586
320
|
|
|
587
321
|
Usage:
|
|
588
322
|
mo
|
|
589
323
|
monacori open [--base HEAD] [--staged] [--tracked-only]
|
|
590
|
-
monacori
|
|
324
|
+
monacori app [--base HEAD] [--staged] [--include-untracked]
|
|
591
325
|
monacori init [--force]
|
|
592
326
|
monacori install [--force] [--apply-agent-docs]
|
|
593
|
-
monacori verify [-- <command>]
|
|
594
|
-
monacori diff [--base HEAD] [--staged] [--include-untracked] [--open] [--watch]
|
|
595
|
-
monacori app [--base HEAD] [--staged] [--include-untracked]
|
|
596
|
-
monacori review [--base HEAD] [--staged] [--include-untracked]
|
|
597
|
-
monacori status
|
|
598
|
-
monacori report [--label manual] [--file report.md]
|
|
599
|
-
|
|
600
|
-
Default loop:
|
|
601
|
-
1. Let an AI agent edit code.
|
|
602
|
-
2. Run: mo
|
|
603
|
-
3. Run: monacori check --include-untracked
|
|
604
|
-
4. Only accept the change when verification evidence is clear.
|
|
605
327
|
|
|
606
328
|
Diff review keys:
|
|
607
329
|
F7 next changed hunk
|
|
@@ -626,42 +348,6 @@ Options:
|
|
|
626
348
|
--tracked-only inspect tracked changes only
|
|
627
349
|
`);
|
|
628
350
|
}
|
|
629
|
-
function printCheckHelp() {
|
|
630
|
-
console.log(`monacori check
|
|
631
|
-
|
|
632
|
-
Run configured verification and create a reviewable diff artifact.
|
|
633
|
-
|
|
634
|
-
Usage:
|
|
635
|
-
monacori check [--include-untracked] [--staged] [--base HEAD] [--context 12] [--open] [--no-verify] [--no-diff] [-- <command>]
|
|
636
|
-
|
|
637
|
-
Examples:
|
|
638
|
-
monacori check --include-untracked --open
|
|
639
|
-
monacori check -- npm test
|
|
640
|
-
monacori check --no-verify --include-untracked
|
|
641
|
-
`);
|
|
642
|
-
}
|
|
643
|
-
function printDiffHelp() {
|
|
644
|
-
console.log(`monacori diff
|
|
645
|
-
|
|
646
|
-
Generate a browser-based side-by-side Git diff review.
|
|
647
|
-
|
|
648
|
-
Usage:
|
|
649
|
-
monacori diff [--base HEAD] [--staged] [--include-untracked] [--context 12] [--output review.html] [--open] [--watch] [--port 0]
|
|
650
|
-
|
|
651
|
-
Keys in the review page:
|
|
652
|
-
F7 next changed hunk
|
|
653
|
-
Shift+F7 previous changed hunk
|
|
654
|
-
] / [ fallback hunk navigation
|
|
655
|
-
Shift Shift search indexed files, including unchanged files
|
|
656
|
-
Cmd/Ctrl+E recent files
|
|
657
|
-
Cmd/Ctrl+Down jump to symbol under cursor
|
|
658
|
-
|
|
659
|
-
The sidebar groups changed files as a folder tree. Use Search to filter paths and indexed file contents.
|
|
660
|
-
The Files tab opens read-only source previews, including unchanged files when they fit the local review budget.
|
|
661
|
-
Viewed marks are tied to file signatures, so a changed file becomes unviewed again after reload.
|
|
662
|
-
Use --watch to serve a live review that reloads when the working tree changes.
|
|
663
|
-
`);
|
|
664
|
-
}
|
|
665
351
|
function printAppHelp() {
|
|
666
352
|
console.log(`monacori app
|
|
667
353
|
|
package/dist/i18n.js
CHANGED
|
@@ -94,6 +94,7 @@ export const MESSAGES = {
|
|
|
94
94
|
"kbd.allQuestionsChanges": "All questions / changes",
|
|
95
95
|
"kbd.ignoreWhitespace": "Ignore whitespace",
|
|
96
96
|
"kbd.saveComment": "Save comment",
|
|
97
|
+
"kbd.promptMemo": "Prompt memo",
|
|
97
98
|
"kbd.toggleTerminal": "Toggle terminal",
|
|
98
99
|
"kbd.splitPane": "Split pane",
|
|
99
100
|
"kbd.focusPane": "Focus prev / next pane",
|
|
@@ -127,6 +128,10 @@ export const MESSAGES = {
|
|
|
127
128
|
"merged.close": "Close",
|
|
128
129
|
"merged.qHeading": "# Questions",
|
|
129
130
|
"merged.cHeading": "# Change requests",
|
|
131
|
+
// Prompt memo (Cmd/Ctrl+Shift+N) — a single freeform Markdown scratchpad with a live split preview.
|
|
132
|
+
"memo.title": "Prompt memo",
|
|
133
|
+
"memo.placeholder": "Jot down what you're planning, in Markdown…",
|
|
134
|
+
"memo.previewEmpty": "Markdown preview shows up here as you type.",
|
|
130
135
|
// Merge-prompt default agent contracts (these follow the locale — a Korean user gets Korean defaults)
|
|
131
136
|
"mergePrompt.default.q": "The following are questions about code you just wrote. Answer each one — explain the intent, rationale, or context. Do not change any code; this clarifies understanding before any revisions.",
|
|
132
137
|
"mergePrompt.default.c": "The following are change requests for code you just wrote. For each, edit the code at the quoted location to satisfy the request. Keep changes minimal and focused; do not make unrelated edits.",
|
|
@@ -215,6 +220,7 @@ export const MESSAGES = {
|
|
|
215
220
|
"kbd.allQuestionsChanges": "전체 질문 / 변경요청",
|
|
216
221
|
"kbd.ignoreWhitespace": "공백 무시",
|
|
217
222
|
"kbd.saveComment": "코멘트 저장",
|
|
223
|
+
"kbd.promptMemo": "프롬프트 메모",
|
|
218
224
|
"kbd.toggleTerminal": "터미널 토글",
|
|
219
225
|
"kbd.splitPane": "패널 분할",
|
|
220
226
|
"kbd.focusPane": "이전 / 다음 패널로 이동",
|
|
@@ -249,6 +255,10 @@ export const MESSAGES = {
|
|
|
249
255
|
// Structural markers stay English in both locales (the preamble prose below follows the locale).
|
|
250
256
|
"merged.qHeading": "# Questions",
|
|
251
257
|
"merged.cHeading": "# Change requests",
|
|
258
|
+
// 프롬프트 메모 (Cmd/Ctrl+Shift+N) — 라이브 분할 미리보기가 있는 자유 형식 마크다운 메모 한 장.
|
|
259
|
+
"memo.title": "프롬프트 메모",
|
|
260
|
+
"memo.placeholder": "구상 중인 것을 마크다운으로 적어 보세요…",
|
|
261
|
+
"memo.previewEmpty": "입력하면 여기에 마크다운 미리보기가 나타납니다.",
|
|
252
262
|
// Merge-prompt default agent contracts (Korean default for Korean users)
|
|
253
263
|
"mergePrompt.default.q": "다음은 방금 작성한 코드에 대한 질문입니다. 각 질문에 답하면서 의도, 근거, 맥락을 설명하세요. 코드는 변경하지 마세요. 이 단계는 수정에 앞서 이해를 명확히 하기 위한 것입니다.",
|
|
254
264
|
"mergePrompt.default.c": "다음은 방금 작성한 코드에 대한 변경 요청입니다. 각 요청에 대해 인용된 위치의 코드를 수정하여 요구사항을 충족하세요. 변경은 최소한으로 집중해서 하고, 관련 없는 수정은 하지 마세요.",
|
package/dist/preload.cjs
CHANGED
|
@@ -13,6 +13,15 @@ electron_1.contextBridge.exposeInMainWorld("monacoriMenu", {
|
|
|
13
13
|
onMergedView: (cb) => {
|
|
14
14
|
electron_1.ipcRenderer.on("monacori:merged-view", (_event, kind) => cb(kind));
|
|
15
15
|
},
|
|
16
|
+
// Review menu's Cmd/Ctrl+Shift+N -> open/close the prompt memo in the renderer.
|
|
17
|
+
onOpenMemo: (cb) => {
|
|
18
|
+
electron_1.ipcRenderer.on("monacori:open-memo", () => cb());
|
|
19
|
+
},
|
|
20
|
+
// Electron watch: main pushes the rebuilt review HTML so the renderer refreshes the diff in place
|
|
21
|
+
// (no window reload), keeping the integrated terminal's pty sessions alive.
|
|
22
|
+
onDiffUpdate: (cb) => {
|
|
23
|
+
electron_1.ipcRenderer.on("monacori:diff-update", (_event, html) => cb(html));
|
|
24
|
+
},
|
|
16
25
|
// Cmd/Ctrl+W from the Window menu -> close the active Files-mode tab in the renderer.
|
|
17
26
|
onCloseTab: (cb) => {
|
|
18
27
|
electron_1.ipcRenderer.on("monacori:close-tab", () => cb());
|
package/dist/render.d.ts
CHANGED
|
@@ -6,6 +6,15 @@ export declare function splitDiffForLazy(diffHtml: string, files: DiffFile[]): {
|
|
|
6
6
|
islands: string;
|
|
7
7
|
bodies: string[];
|
|
8
8
|
};
|
|
9
|
+
export declare function renderReviewStatus(input: {
|
|
10
|
+
files: number;
|
|
11
|
+
hunks: number;
|
|
12
|
+
embeddedFiles: number;
|
|
13
|
+
sourceFileCount: number;
|
|
14
|
+
ignoreWhitespace?: boolean;
|
|
15
|
+
watch?: boolean;
|
|
16
|
+
generatedAt?: string;
|
|
17
|
+
}): string;
|
|
9
18
|
export declare function renderDiffHtml(input: {
|
|
10
19
|
files: DiffFile[];
|
|
11
20
|
diffHtml: string;
|
|
@@ -25,6 +34,8 @@ export declare function renderDiffHtml(input: {
|
|
|
25
34
|
signature?: string;
|
|
26
35
|
generatedAt?: string;
|
|
27
36
|
}): string;
|
|
37
|
+
export declare function renderDiffTree(files: DiffFile[]): string;
|
|
38
|
+
export declare function renderSourceTree(files: SourceFile[]): string;
|
|
28
39
|
export declare function diffSubtitle(options: {
|
|
29
40
|
base?: string;
|
|
30
41
|
staged: boolean;
|
package/dist/render.js
CHANGED
|
@@ -82,6 +82,11 @@ export function splitDiffForLazy(diffHtml, files) {
|
|
|
82
82
|
});
|
|
83
83
|
return { container: shells.join("\n"), islands: islands.join("\n"), bodies };
|
|
84
84
|
}
|
|
85
|
+
// The toolbar's review-status row (file/hunk counts, index + live status). Extracted so the in-place
|
|
86
|
+
// update path can re-render just this strip; renderDiffHtml wraps it in <div class="review-status">.
|
|
87
|
+
export function renderReviewStatus(input) {
|
|
88
|
+
return `<span>${input.files} <span data-i18n="status.files">files</span></span><span>${input.hunks} <span data-i18n="status.hunks">hunks</span></span>${input.ignoreWhitespace ? '<span class="ws-ignored" data-i18n="status.wsIgnored" data-i18n-title="status.wsIgnored.title" title="Whitespace ignored — Cmd/Ctrl+Shift+W">ws ignored</span>' : ""}<span class="index-status" id="index-status" data-i18n-title="status.index.title" title="Go-to-definition index">${input.embeddedFiles}/${input.sourceFileCount} indexed</span><span class="index-progress hidden" id="index-progress" aria-hidden="true"><span class="index-progress-bar"></span></span><span class="live-status ${input.watch ? "watching" : ""}" id="live-status"${input.watch ? ' data-i18n="status.watching"' : ""}>${input.watch ? "watching" : escapeHtml(input.generatedAt ?? new Date().toISOString())}</span>`;
|
|
89
|
+
}
|
|
85
90
|
export function renderDiffHtml(input) {
|
|
86
91
|
const totalHunks = input.files.reduce((sum, file) => sum + file.hunks.length, 0);
|
|
87
92
|
const fileNav = renderDiffTree(input.files);
|
|
@@ -102,6 +107,8 @@ export function renderDiffHtml(input) {
|
|
|
102
107
|
"</style>",
|
|
103
108
|
"</head>",
|
|
104
109
|
"<body>",
|
|
110
|
+
// Boot overlay (removed by the renderer once bootstrap has painted) covers the blank gap after loadFile.
|
|
111
|
+
'<div id="boot-overlay"><div class="boot-spinner"></div><div>monacori</div></div>',
|
|
105
112
|
'<aside class="sidebar" aria-label="Review navigation">',
|
|
106
113
|
'<div class="sidebar-scroll">',
|
|
107
114
|
`<div class="sidebar-brand" title="${escapeAttr(input.projectPath)}"><span class="brand-mark">monacori</span><span class="brand-project">${escapeHtml(input.projectName)}</span></div>`,
|
|
@@ -122,7 +129,7 @@ export function renderDiffHtml(input) {
|
|
|
122
129
|
'<section id="diff-view" class="hidden">',
|
|
123
130
|
'<div class="toolbar">',
|
|
124
131
|
'<div class="breadcrumb" id="diff-breadcrumb"></div>',
|
|
125
|
-
`<div class="review-status"
|
|
132
|
+
`<div class="review-status">${renderReviewStatus({ files: input.files.length, hunks: totalHunks, embeddedFiles, sourceFileCount: input.sourceFiles.length, ignoreWhitespace: input.ignoreWhitespace, watch: input.watch, generatedAt: input.generatedAt })}</div>`,
|
|
126
133
|
'<button type="button" id="diff-viewed-toggle" class="diff-viewed-toggle" aria-pressed="false" data-i18n="btn.viewed" data-i18n-title="btn.viewed.title" title="Toggle viewed (<)" hidden>Viewed</button>',
|
|
127
134
|
"</div>",
|
|
128
135
|
`<div id="diff2html-container" class="diff2html-container">${input.diffHtml || '<div class="empty" data-i18n="diff.noDiff">No diff to review.</div>'}</div>`,
|
|
@@ -194,13 +201,14 @@ export function renderDiffHtml(input) {
|
|
|
194
201
|
'<kbd>Cmd/Ctrl+Shift+/ .</kbd><span data-i18n="kbd.allQuestionsChanges">All questions / changes</span>' +
|
|
195
202
|
'<kbd>Cmd/Ctrl+Shift+W</kbd><span data-i18n="kbd.ignoreWhitespace">Ignore whitespace</span>' +
|
|
196
203
|
'<kbd>Cmd/Ctrl+Enter</kbd><span data-i18n="kbd.saveComment">Save comment</span>' +
|
|
204
|
+
'<kbd>Cmd/Ctrl+Shift+N</kbd><span data-i18n="kbd.promptMemo">Prompt memo</span>' +
|
|
197
205
|
'</div>' +
|
|
198
206
|
'<div class="keys-cat" data-i18n="settings.kbd.cat.terminal">Terminal</div>' +
|
|
199
207
|
'<div class="keys-grid">' +
|
|
200
208
|
'<kbd>Ctrl+`</kbd><span data-i18n="kbd.toggleTerminal">Toggle terminal</span>' +
|
|
201
209
|
'<kbd>Cmd/Ctrl+D</kbd><span data-i18n="kbd.splitPane">Split pane</span>' +
|
|
202
210
|
'<kbd>Cmd/Ctrl+Alt+[ / ]</kbd><span data-i18n="kbd.focusPane">Focus prev / next pane</span>' +
|
|
203
|
-
'<kbd>
|
|
211
|
+
'<kbd>Cmd/Ctrl+Alt+R</kbd><span data-i18n="kbd.renamePane">Rename pane</span>' +
|
|
204
212
|
'<kbd>Cmd/Ctrl+W</kbd><span data-i18n="kbd.closeTerminal">Close terminal (when focused)</span>' +
|
|
205
213
|
'</div>' +
|
|
206
214
|
'</div>',
|
|
@@ -234,7 +242,7 @@ export function renderDiffHtml(input) {
|
|
|
234
242
|
"</html>",
|
|
235
243
|
].join("\n");
|
|
236
244
|
}
|
|
237
|
-
function renderDiffTree(files) {
|
|
245
|
+
export function renderDiffTree(files) {
|
|
238
246
|
if (files.length === 0) {
|
|
239
247
|
return '<div class="empty-nav">No changed files</div>';
|
|
240
248
|
}
|
|
@@ -266,7 +274,7 @@ function renderDiffTree(files) {
|
|
|
266
274
|
});
|
|
267
275
|
return `<nav class="tree changes-flat">${rows.join("")}</nav>`;
|
|
268
276
|
}
|
|
269
|
-
function renderSourceTree(files) {
|
|
277
|
+
export function renderSourceTree(files) {
|
|
270
278
|
if (files.length === 0) {
|
|
271
279
|
return '<div class="empty-nav">No source files indexed</div>';
|
|
272
280
|
}
|
|
@@ -394,7 +402,7 @@ function renderSourceNode(node, depth) {
|
|
|
394
402
|
}
|
|
395
403
|
return [
|
|
396
404
|
`<details class="tree-dir source-dir" data-dir="${escapeAttr(labelNode.path)}" style="--depth:${depth}">`,
|
|
397
|
-
`<summary><span class="folder-icon"
|
|
405
|
+
`<summary><span class="folder-icon"><svg class="folder-ic fi-closed" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg><svg class="folder-ic fi-open" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 14 1.45-2.9A2 2 0 0 1 9.24 10H21a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg></span><span class="path">${escapeHtml(names.join("/"))}</span></summary>`,
|
|
398
406
|
renderSourceChildren(labelNode, depth + 1),
|
|
399
407
|
"</details>",
|
|
400
408
|
].join("\n");
|
package/dist/server.js
CHANGED
|
@@ -108,6 +108,12 @@ export function serveDiffWatch(input) {
|
|
|
108
108
|
});
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
|
+
// Compact in-place refresh payload — the poller fetches this only when the signature changed.
|
|
112
|
+
if (requestUrl.pathname === "/__ai_flow_update") {
|
|
113
|
+
const latest = lastBuild ?? build();
|
|
114
|
+
writeHttpJson(response, latest.update ?? {});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
111
117
|
if (requestUrl.pathname === "/__http_send" && request.method === "POST") {
|
|
112
118
|
void handleHttpProxy(request, response);
|
|
113
119
|
return;
|
package/dist/types.d.ts
CHANGED
|
@@ -78,12 +78,24 @@ export type DiffReviewResult = {
|
|
|
78
78
|
files: number;
|
|
79
79
|
hunks: number;
|
|
80
80
|
};
|
|
81
|
+
export type DiffReviewUpdate = {
|
|
82
|
+
signature: string;
|
|
83
|
+
generatedAt: string;
|
|
84
|
+
diffContainer: string;
|
|
85
|
+
changesPanel: string;
|
|
86
|
+
filesTree: string;
|
|
87
|
+
reviewStatus: string;
|
|
88
|
+
fileStates: ReviewFileState[];
|
|
89
|
+
sourceFilesMeta: SourceFile[];
|
|
90
|
+
httpEnvironments: Record<string, Record<string, string>>;
|
|
91
|
+
};
|
|
81
92
|
export type DiffReviewBuild = {
|
|
82
93
|
html: string;
|
|
83
94
|
files: number;
|
|
84
95
|
hunks: number;
|
|
85
96
|
signature: string;
|
|
86
97
|
generatedAt: string;
|
|
98
|
+
update?: DiffReviewUpdate;
|
|
87
99
|
lazyBodies?: string[];
|
|
88
100
|
lazySourceData?: string;
|
|
89
101
|
};
|