@happy-nut/monacori 0.1.3 → 0.1.5

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 CHANGED
@@ -1,24 +1,10 @@
1
1
  # monacori
2
2
 
3
- **Validation control plane for AI-generated code changes.** After an AI edits your repository, `monacori` produces verification evidence a human can actually trust.
4
-
5
- It is *not* an agent orchestrator. It does not manage panes, sessions, worktrees, or model adapters. Its job is narrow on purpose: validate what the AI just did.
3
+ **A local desktop diff-review app for AI-generated code changes.** After an AI edits your repo, run `mo` to open a side-by-side diff read it, comment on it, and send your comments straight to an AI CLI running in the built-in terminal.
6
4
 
7
5
  ## Why
8
6
 
9
- AI coding output is hard to trust when review depends on chat memory or a vague "done" claim. `monacori` keeps the review artifacts inside the repository, so the state of a change can be inspected, rerun, and discussed instead of taken on faith.
10
-
11
- The loop is simple:
12
-
13
- ```text
14
- AI edits code.
15
- monacori runs verification.
16
- monacori creates a reviewable diff artifact.
17
- You inspect the evidence before accepting the change.
18
- ```
19
-
20
- ![Diff review](assets/screenshots/diff-review.png)
21
- *Local desktop review app: side-by-side diff with changed lines highlighted and IntelliJ-style git status colors in the sidebar.*
7
+ A chat log or a "done" claim is a poor way to review what an AI changed. monacori puts the change in front of you as a real diff you can read and annotate — then turns your comments into a prompt you hand right back to `claude` or `codex`, without leaving the app or copy-pasting between windows.
22
8
 
23
9
  ## Install
24
10
 
@@ -30,21 +16,16 @@ After install, the short command is `mo`. A Homebrew tap (`happy-nut/monacori/mo
30
16
 
31
17
  ## What you get
32
18
 
33
- - **Local desktop review app** — diff review with changed-line highlighting and an IntelliJ-style sidebar that colors files by git status. Reads the repo directly, refreshes on change, no HTTP server.
34
- - **Integrated terminal** — run AI CLIs like `claude` or `codex` inside the app, and send a "merged prompt" (your question or fix request bundled with the relevant code context) straight to the session.
35
- - **Inline comments** — annotate the diff while you review.
36
- - **Verification logs & reports** — repeatable verification under `.monacori/logs/` and compact validation reports under `.monacori/reports/`.
37
-
38
- ![Integrated terminal](assets/screenshots/terminal.png)
39
- *Run your AI CLI inside the review app, next to the diff it just produced.*
19
+ - **Desktop diff review** — side-by-side diff with changed-line highlighting and an IntelliJ-style sidebar that colors files by git status. Reads the repo directly, refreshes on change, no HTTP server.
20
+ - **Integrated terminal** — run AI CLIs like `claude` or `codex` right inside the app, split into panes.
21
+ - **Comments → session** — annotate any line, then send your comments (bundled with their code context) into a terminal pane as one merged prompt: pick the target pane visually and press Enter.
40
22
 
41
23
  ## Quick start
42
24
 
43
- Inside the repository you want to validate:
25
+ Inside the repository you want to review:
44
26
 
45
27
  ```bash
46
- mo # open the desktop review app for the current dir
47
- monacori check --include-untracked # run verification, write a log, diff, and report
28
+ mo
48
29
  ```
49
30
 
50
31
  On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes untracked files so new AI-created files show up immediately.
@@ -53,36 +34,21 @@ On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes u
53
34
 
54
35
  | Command | What it does |
55
36
  | --- | --- |
56
- | `mo` | Open the desktop review app (alias for `monacori open`). |
57
- | `monacori check` | Run verification, then create a diff and a validation report. `-- <cmd>` overrides commands for one run. |
58
- | `monacori verify` | Run configured verification commands and store the log. Exits non-zero on failure. |
59
- | `monacori diff` | Generate a browser-based side-by-side diff page. `--watch` serves a live-reloading review. |
37
+ | `mo` | Open the desktop diff-review app (alias for `monacori open`). |
60
38
  | `monacori app` | Launch the desktop review app (same as `mo`). |
61
- | `monacori report` | Store a manual report under `.monacori/reports/`. |
62
- | `monacori status` | Print branch, git status, diff stat, verification commands, and recent reports/logs. |
39
+ | `monacori init` | Initialize `.monacori/` in the current directory. |
40
+ | `monacori install` | Initialize and write agent instruction snippets. `--apply-agent-docs` patches `AGENTS.md` / `CLAUDE.md`. |
63
41
 
64
42
  ## Repository state
65
43
 
66
- `monacori init` (run automatically by `mo`) creates:
67
-
68
- ```text
69
- .monacori/
70
- config.json verification commands and diff defaults
71
- state.md compact validation history
72
- diffs/ generated browser diff reviews
73
- reports/ validation reports
74
- logs/ verification logs
75
- ```
76
-
77
- Keep `.monacori/` ignored unless your team explicitly wants to commit validation state.
44
+ `monacori init` (run automatically by `mo`) creates a git-ignored `.monacori/` directory holding generated diff reviews and local config. Keep it ignored unless your team explicitly wants to commit review state.
78
45
 
79
46
  ## Design principles
80
47
 
81
- - Verification evidence beats chat memory.
82
- - Generated artifacts are plain Markdown, JSON, logs, or static HTML.
48
+ - A real diff beats a chat log or a "done" claim.
49
+ - Review, comment, and hand-off live in one window no copy-paste loop.
50
+ - Generated artifacts are plain static HTML and JSON.
83
51
  - No required AI agent, terminal multiplexer, editor, or worktree strategy.
84
- - The default command should be useful after any AI-generated edit.
85
- - A change is not accepted until the evidence is clear or the gap is documented.
86
52
 
87
53
  ## License
88
54
 
package/dist/app-main.js CHANGED
@@ -71,8 +71,37 @@ ipcMain.handle("monacori:pty-spawn", (_event, size) => {
71
71
  if (mainWindow && !mainWindow.isDestroyed())
72
72
  mainWindow.webContents.send(channel, payload);
73
73
  };
74
- t.onData((data) => deliver("monacori:pty-data", { id, data }));
75
- t.onExit(() => { terms.delete(id); deliver("monacori:pty-exit", { id }); });
74
+ // pty output can arrive as many tiny chunks (thousands/sec under fast output). One IPC per chunk floods
75
+ // the renderer, so coalesce: buffer chunks and flush on a short timer, or immediately once the buffer is
76
+ // large — bounding both IPC traffic and added latency.
77
+ let outBuf = "";
78
+ let flushTimer;
79
+ const flushOut = () => {
80
+ flushTimer = undefined;
81
+ if (!outBuf)
82
+ return;
83
+ const data = outBuf;
84
+ outBuf = "";
85
+ deliver("monacori:pty-data", { id, data });
86
+ };
87
+ t.onData((data) => {
88
+ outBuf += data;
89
+ if (outBuf.length >= 64 * 1024) {
90
+ if (flushTimer)
91
+ clearTimeout(flushTimer);
92
+ flushOut();
93
+ }
94
+ else if (!flushTimer) {
95
+ flushTimer = setTimeout(flushOut, 12);
96
+ }
97
+ });
98
+ t.onExit(() => {
99
+ if (flushTimer)
100
+ clearTimeout(flushTimer);
101
+ flushOut(); // deliver any buffered tail before signaling exit
102
+ terms.delete(id);
103
+ deliver("monacori:pty-exit", { id });
104
+ });
76
105
  return { ok: true, id };
77
106
  });
78
107
  ipcMain.on("monacori:pty-write", (_event, msg) => { terms.get(msg?.id)?.write(msg.data); });
@@ -151,6 +180,8 @@ app.whenReady().then(async () => {
151
180
  submenu: [
152
181
  { label: "All questions", accelerator: "Control+Command+Shift+/", click: () => sendMerged("q") },
153
182
  { label: "All change requests", accelerator: "Control+Command+Shift+.", click: () => sendMerged("c") },
183
+ // Cmd/Ctrl+Shift+N opens (and toggles) the single freeform prompt memo — a Markdown scratchpad.
184
+ { label: "Prompt memo", accelerator: "CommandOrControl+Shift+N", click: () => mainWindow?.webContents.send("monacori:open-memo") },
154
185
  { type: "separator" },
155
186
  // Whitespace-ignore re-runs git diff with --ignore-all-space and reloads (main-process action,
156
187
  // so a menu checkbox is simpler than a renderer IPC round-trip).
@@ -190,7 +221,7 @@ app.whenReady().then(async () => {
190
221
  { type: "separator" },
191
222
  { label: "Focus Previous Pane", accelerator: "CommandOrControl+Alt+[", click: () => mainWindow?.webContents.send("monacori:terminal-pane-focus", -1) },
192
223
  { label: "Focus Next Pane", accelerator: "CommandOrControl+Alt+]", click: () => mainWindow?.webContents.send("monacori:terminal-pane-focus", 1) },
193
- { label: "Rename Pane", accelerator: "F2", click: () => mainWindow?.webContents.send("monacori:terminal-pane-rename") },
224
+ { label: "Rename Pane", accelerator: "CommandOrControl+Alt+R", click: () => mainWindow?.webContents.send("monacori:terminal-pane-rename") },
194
225
  ],
195
226
  });
196
227
  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate));
@@ -222,7 +253,10 @@ app.whenReady().then(async () => {
222
253
  // paint and swap it in. The first build used to run synchronously *before* the window existed, so the
223
254
  // screen stayed blank for the first few seconds of startup; now the user sees a loading screen instead.
224
255
  await mainWindow.loadURL("data:text/html;charset=utf-8," + encodeURIComponent(LOADING_HTML));
225
- setImmediate(() => {
256
+ // Give the loading spinner a few frames to actually paint before the (synchronous) first build blocks
257
+ // the main process — otherwise the spinner looks frozen until the build finishes. The boot overlay in
258
+ // the review HTML then takes over, so there's no blank gap when loadFile swaps the page in.
259
+ setTimeout(() => {
226
260
  try {
227
261
  const firstBuild = writeReviewFile(options);
228
262
  currentSignature = firstBuild.signature;
@@ -235,7 +269,7 @@ app.whenReady().then(async () => {
235
269
  console.error(error instanceof Error ? error.message : String(error));
236
270
  app.quit();
237
271
  }
238
- });
272
+ }, 60);
239
273
  }).catch((error) => {
240
274
  console.error(error instanceof Error ? error.message : String(error));
241
275
  app.quit();
@@ -260,7 +294,13 @@ async function refreshIfChanged() {
260
294
  const next = writeReviewFile(options);
261
295
  if (next.signature !== currentSignature) {
262
296
  currentSignature = next.signature;
263
- mainWindow.webContents.reloadIgnoringCache();
297
+ // Refresh the diff in place instead of reloading the window. A full reload re-runs the renderer,
298
+ // whose beforeunload kills every pty — so an integrated terminal running claude/codex would die on
299
+ // each working-tree change. We send only the compact update payload (diff/trees/status/data — no
300
+ // xterm blob), and the renderer transplants it + re-fetches per-file bodies/source over the existing
301
+ // IPC (currentBodies/currentSourceData were just refreshed by writeReviewFile above).
302
+ if (next.update)
303
+ mainWindow.webContents.send("monacori:diff-update", next.update);
264
304
  }
265
305
  }
266
306
  catch (error) {
@@ -284,7 +324,7 @@ function writeReviewFile(input) {
284
324
  writeFileSync(reviewPath(), build.html);
285
325
  currentBodies = build.lazyBodies ?? [];
286
326
  currentSourceData = build.lazySourceData ?? "[]";
287
- return { signature: build.signature };
327
+ return { signature: build.signature, html: build.html, update: build.update };
288
328
  }
289
329
  function reviewPath() {
290
330
  return join(options.root, FLOW_DIR, REVIEW_FILE);
package/dist/build.js CHANGED
@@ -3,7 +3,7 @@ import { basename } from "node:path";
3
3
  import { isGitRepository } from "./git.js";
4
4
  import { collectHttpEnvironments, collectReviewFileStates, collectSourceFiles, parseUnifiedDiff, readUnifiedDiff } from "./diff.js";
5
5
  import { renderDiff2Html } from "./highlight.js";
6
- import { diffSubtitle, renderDiffHtml, renderNotGitRepoHtml, shouldLazyRender, splitDiffForLazy } from "./render.js";
6
+ import { diffSubtitle, renderDiffHtml, renderDiffTree, renderNotGitRepoHtml, renderReviewStatus, renderSourceTree, shouldLazyRender, splitDiffForLazy } from "./render.js";
7
7
  export function buildDiffReview(input) {
8
8
  if (!isGitRepository(process.cwd())) {
9
9
  return {
@@ -65,6 +65,27 @@ export function buildDiffReview(input) {
65
65
  signature,
66
66
  generatedAt,
67
67
  });
68
+ // Compact payload for in-place refresh: just the regions the renderer swaps on a watch change. Reuses
69
+ // the same fragment renderers as the full page, minus the heavy bits (xterm blob, embedded source).
70
+ const update = {
71
+ signature,
72
+ generatedAt,
73
+ diffContainer: diffSplit.container || '<div class="empty" data-i18n="diff.noDiff">No diff to review.</div>',
74
+ changesPanel: renderDiffTree(files),
75
+ filesTree: renderSourceTree(sourceFiles),
76
+ reviewStatus: renderReviewStatus({
77
+ files: files.length,
78
+ hunks,
79
+ embeddedFiles: sourceFiles.filter((file) => file.embedded).length,
80
+ sourceFileCount: sourceFiles.length,
81
+ ignoreWhitespace: input.ignoreWhitespace,
82
+ watch: input.watch,
83
+ generatedAt,
84
+ }),
85
+ fileStates,
86
+ sourceFilesMeta: lazyLoad ? sourceFiles.map((file) => ({ ...file, content: "", image: "" })) : sourceFiles,
87
+ httpEnvironments,
88
+ };
68
89
  return {
69
90
  html,
70
91
  files: files.length,
@@ -73,5 +94,6 @@ export function buildDiffReview(input) {
73
94
  generatedAt,
74
95
  lazyBodies: diffSplit.bodies,
75
96
  lazySourceData: lazyLoad ? JSON.stringify(sourceFiles) : undefined,
97
+ update,
76
98
  };
77
99
  }
package/dist/commands.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { basename, dirname, join, relative } from "node:path";
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 { codeBlock, listRecentFiles, parsePositiveInteger, readOption, readStdin, sanitizeFilePart, summarizeForState, timestampForFile } from "./util.js";
8
- import { git, readGitSnapshot } from "./git.js";
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 `monacori app --include-untracked` to inspect changes, then `monacori check --include-untracked` to record verification.");
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 validation decisions here so future checks do not depend on chat memory.",
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 Validation",
192
+ "## monacori Diff Review",
453
193
  "",
454
- "This repository uses monacori to verify AI-generated code changes.",
194
+ "This repository uses monacori to help humans review AI-generated code changes side-by-side.",
455
195
  "",
456
- "Before claiming completion on a code change:",
196
+ "After making code changes:",
457
197
  "",
458
- "- Run `monacori check --include-untracked` or a more specific `monacori verify -- <command>`.",
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
- "- Report the verification commands, results, and remaining risks.",
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
- Validation control plane for AI-generated code changes.
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 check [--include-untracked] [--open] [--no-verify] [--no-diff] [-- <command>]
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