@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 CHANGED
@@ -1,24 +1,30 @@
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.
3
+ **A local desktop review workspace for AI-generated code changes.**
4
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.
5
+ Run `mo` after an AI edits your repository. monacori opens a side-by-side diff, lets you attach line-level questions or change requests, and bundles that feedback back into the AI CLI (command-line interface) session running in the built-in terminal.
6
6
 
7
- ## Why
7
+ ![monacori reviewing a diff, adding a change request, and targeting the built-in terminal](assets/monacori-demo.gif)
8
8
 
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.
9
+ ## Why monacori
10
10
 
11
- The loop is simple:
11
+ AI coding tools are fast, but their "done" message is not a review. monacori gives the human reviewer a dedicated control surface for the gap between generated code and trusted code:
12
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
- ```
13
+ - See every changed, added, and untracked file in an IntelliJ-style review sidebar.
14
+ - Review side-by-side diffs with syntax highlighting, changed-line emphasis, and keyboard navigation.
15
+ - Leave questions or change requests directly on the relevant line.
16
+ - Send all reviewer comments, with file paths and code context, into `claude`, `codex`, or another terminal session without copy-paste.
17
+ - Keep all generated review state local, plain, and inspectable under `.monacori/`.
18
+
19
+ ## Workflow
20
+
21
+ 1. Let an AI coding tool make changes in your repository.
22
+ 2. Run `mo` from that repository.
23
+ 3. Inspect the diff, mark files as viewed, and attach line comments where needed.
24
+ 4. Open the built-in terminal and keep your AI CLI session beside the review.
25
+ 5. Send the merged questions or change requests back to the session as a focused follow-up prompt.
19
26
 
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.*
27
+ The result is a tighter review loop: the AI produces changes, the human reviews the actual diff, and the next prompt is grounded in exact file and line context.
22
28
 
23
29
  ## Install
24
30
 
@@ -26,63 +32,61 @@ You inspect the evidence before accepting the change.
26
32
  npm install -g @happy-nut/monacori
27
33
  ```
28
34
 
29
- After install, the short command is `mo`. A Homebrew tap (`happy-nut/monacori/monacori`) is also available.
35
+ The short command is `mo`.
30
36
 
31
- ## What you get
37
+ Homebrew users can install from the tap as well:
32
38
 
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.*
39
+ ```bash
40
+ brew install happy-nut/monacori/monacori
41
+ ```
40
42
 
41
- ## Quick start
43
+ ## Quick Start
42
44
 
43
- Inside the repository you want to validate:
45
+ Inside any Git repository:
44
46
 
45
47
  ```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
48
+ mo
48
49
  ```
49
50
 
50
- On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes untracked files so new AI-created files show up immediately.
51
+ On first run, `mo` creates `.monacori/`, adds it to `.gitignore`, and includes untracked files so new AI-created files appear immediately.
52
+
53
+ ## Highlights
54
+
55
+ - **Desktop diff review**: reads the repository directly, refreshes from local Git state, and does not require a web server.
56
+ - **AI handoff comments**: questions and change requests are stored with their file, line, and code context.
57
+ - **Integrated terminal**: keep `claude`, `codex`, or a shell open inside the same window, with split panes when needed.
58
+ - **Source navigation**: jump between changed files, search indexed files, preview source, and move through hunks from the keyboard.
59
+ - **Plain local artifacts**: generated review files and state are Markdown, JSON, and static HTML under `.monacori/`.
51
60
 
52
61
  ## Commands
53
62
 
54
63
  | Command | What it does |
55
64
  | --- | --- |
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. |
60
- | `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. |
63
-
64
- ## Repository state
65
-
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
65
+ | `mo` | Open the desktop diff-review app for the current repository. Alias for `monacori open`. |
66
+ | `monacori open` | Launch the review app, auto-initialize `.monacori/`, and include untracked files by default. |
67
+ | `monacori app` | Launch the same desktop app explicitly. |
68
+ | `monacori init` | Initialize `.monacori/` in the current directory. |
69
+ | `monacori install` | Initialize and write agent instruction snippets. Use `--apply-agent-docs` to patch `AGENTS.md` or `CLAUDE.md`. |
70
+
71
+ Useful review options:
72
+
73
+ ```bash
74
+ mo --staged # review only staged changes
75
+ mo --tracked-only # exclude untracked files
76
+ mo --base main # compare against a specific base
77
+ mo --context 20 # show more context around each hunk
75
78
  ```
76
79
 
77
- Keep `.monacori/` ignored unless your team explicitly wants to commit validation state.
80
+ ## Local State
81
+
82
+ `monacori init` creates a git-ignored `.monacori/` directory for generated diff reviews, local config, comments, logs, and validation notes. Keep it ignored unless your team intentionally wants to version review artifacts.
78
83
 
79
- ## Design principles
84
+ ## Design Principles
80
85
 
81
- - Verification evidence beats chat memory.
82
- - Generated artifacts are plain Markdown, JSON, logs, or static HTML.
83
- - 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
+ - Real diffs beat chat summaries.
87
+ - Human review should stay close to the code and the running AI session.
88
+ - The core should be local, inspectable, and agent-agnostic.
89
+ - No required terminal multiplexer, editor plugin, hosted service, or worktree strategy.
86
90
 
87
91
  ## License
88
92
 
Binary file
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
  }