@happy-nut/monacori 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/assets/icon.png +0 -0
- package/dist/app-main.js +52 -3
- package/dist/assets.d.ts +4 -0
- package/dist/assets.js +30 -0
- package/dist/build.d.ts +12 -0
- package/dist/build.js +74 -0
- package/dist/cli.d.ts +5 -33
- package/dist/cli.js +7 -3529
- package/dist/commands.d.ts +1 -0
- package/dist/commands.js +678 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +11 -0
- package/dist/diff.d.ts +12 -0
- package/dist/diff.js +355 -0
- package/dist/git.d.ts +4 -0
- package/dist/git.js +23 -0
- package/dist/highlight.d.ts +1 -0
- package/dist/highlight.js +85 -0
- package/dist/preload.cjs +22 -0
- package/dist/preload.d.cts +1 -0
- package/dist/render.d.ts +32 -0
- package/dist/render.js +334 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +175 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +18 -0
- package/dist/util.js +144 -0
- package/dist/viewer.client.js +3343 -0
- package/dist/viewer.css +939 -0
- package/package.json +4 -2
- package/scripts/patch-electron-name.mjs +57 -0
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ npm install -g @happy-nut/monacori
|
|
|
35
35
|
After installation, the short command is:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
|
|
38
|
+
mo
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Homebrew tap distribution is prepared through `Formula/monacori.rb`. Once the scoped npm package is published and the formula is copied to the `happy-nut/homebrew-monacori` tap, the intended install path is:
|
|
@@ -59,11 +59,11 @@ npm link
|
|
|
59
59
|
Inside the repository you want to validate:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
|
|
62
|
+
mo
|
|
63
63
|
monacori check --include-untracked
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`
|
|
66
|
+
`mo` opens the local desktop review app for the current directory. On first run it creates `.monacori/`, updates Git ignore rules for `.monacori/`, and includes untracked files so new AI-created files show up immediately.
|
|
67
67
|
|
|
68
68
|
`check` runs configured verification commands, writes a log, creates a browser diff review, and records a compact report.
|
|
69
69
|
|
|
@@ -76,7 +76,7 @@ monacori check -- npm test
|
|
|
76
76
|
For live diff review while an AI is still editing:
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
|
|
79
|
+
mo
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
## Diff Review
|
|
@@ -100,7 +100,7 @@ The browser artifact path is still available through `monacori diff`. Add `--wat
|
|
|
100
100
|
monacori open [--base HEAD] [--staged] [--tracked-only] [--context 12] [--no-watch]
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Opens the local desktop review app for the current directory. `
|
|
103
|
+
Opens the local desktop review app for the current directory. `mo` and bare `monacori` are aliases for this default flow. It auto-initializes local state when needed and includes untracked files by default; pass `--tracked-only` to inspect tracked changes only.
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
monacori check [--include-untracked] [--staged] [--base HEAD] [--context 12] [--open] [--no-verify] [--no-diff] [-- <command>]
|
|
@@ -130,7 +130,7 @@ Runs configured verification commands and stores the log in `.monacori/logs/`. E
|
|
|
130
130
|
monacori app [--base HEAD] [--staged] [--include-untracked] [--context 12] [--no-watch]
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
Launches the local desktop review app. `
|
|
133
|
+
Launches the local desktop review app. `mo`, `monacori open`, and `monacori review` are aliases. Prefer `mo` for normal use.
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
136
|
monacori diff [--base HEAD] [--staged] [--include-untracked] [--context 12] [--output review.html] [--open] [--watch] [--port 0]
|
package/assets/icon.png
CHANGED
|
Binary file
|
package/dist/app-main.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { app, BrowserWindow, Menu, nativeImage } from "electron";
|
|
5
|
-
import { buildDiffReview } from "./cli.js";
|
|
4
|
+
import { app, BrowserWindow, ipcMain, Menu, nativeImage } from "electron";
|
|
5
|
+
import { buildDiffReview, performHttpRequest } from "./cli.js";
|
|
6
6
|
const FLOW_DIR = ".monacori";
|
|
7
7
|
const REVIEW_FILE = "app-review.html";
|
|
8
8
|
const WATCH_INTERVAL_MS = 1000;
|
|
9
9
|
app.setName("monacori");
|
|
10
|
+
ipcMain.handle("monacori:http-send", (_event, request) => performHttpRequest(request));
|
|
11
|
+
// Phase 2 lazy-LOAD: serve a single file's diff body to the renderer on demand. Retained from the
|
|
12
|
+
// most recent writeReviewFile() build so navigation/scroll can materialize bodies without embedding.
|
|
13
|
+
let currentBodies = [];
|
|
14
|
+
let currentSourceData = "[]";
|
|
15
|
+
ipcMain.handle("monacori:get-file", (_event, request) => {
|
|
16
|
+
const i = Number(request?.index);
|
|
17
|
+
return Number.isInteger(i) && i >= 0 && i < currentBodies.length ? currentBodies[i] : "";
|
|
18
|
+
});
|
|
19
|
+
// Phase 2b lazy-LOAD: serve the full source files JSON (with content) on demand.
|
|
20
|
+
ipcMain.handle("monacori:get-source-data", () => currentSourceData);
|
|
10
21
|
const iconPath = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "icon.png");
|
|
22
|
+
const preloadPath = join(dirname(fileURLToPath(import.meta.url)), "preload.cjs");
|
|
11
23
|
const options = parseArgs(process.argv.slice(2));
|
|
12
24
|
let mainWindow;
|
|
13
25
|
let currentSignature = "";
|
|
@@ -19,7 +31,38 @@ if (!existsSync(options.root)) {
|
|
|
19
31
|
app.whenReady().then(async () => {
|
|
20
32
|
process.chdir(options.root);
|
|
21
33
|
mkdirSync(FLOW_DIR, { recursive: true });
|
|
22
|
-
|
|
34
|
+
// Keep the standard Edit/Window roles so Cmd+C/V/X/A (copy comments into prompts) and Cmd+Q work.
|
|
35
|
+
// The in-window menu bar stays hidden on Windows/Linux via autoHideMenuBar; macOS shows it in the top bar.
|
|
36
|
+
const sendMerged = (kind) => mainWindow?.webContents.send("monacori:merged-view", kind);
|
|
37
|
+
const menuTemplate = [];
|
|
38
|
+
if (process.platform === "darwin")
|
|
39
|
+
menuTemplate.push({ role: "appMenu" });
|
|
40
|
+
menuTemplate.push({ role: "editMenu" });
|
|
41
|
+
// Claim Cmd/Ctrl+Shift+/ ("?") and Cmd/Ctrl+Shift+. (">") as menu accelerators so macOS does not
|
|
42
|
+
// swallow Cmd+? for its Help search; clicking routes to the renderer's merged comment views.
|
|
43
|
+
menuTemplate.push({
|
|
44
|
+
label: "Review",
|
|
45
|
+
submenu: [
|
|
46
|
+
{ label: "All questions", accelerator: "CommandOrControl+Shift+/", click: () => sendMerged("q") },
|
|
47
|
+
{ label: "All change requests", accelerator: "CommandOrControl+Shift+.", click: () => sendMerged("c") },
|
|
48
|
+
{ type: "separator" },
|
|
49
|
+
// Whitespace-ignore re-runs git diff with --ignore-all-space and reloads (main-process action,
|
|
50
|
+
// so a menu checkbox is simpler than a renderer IPC round-trip).
|
|
51
|
+
{
|
|
52
|
+
label: "Ignore whitespace",
|
|
53
|
+
type: "checkbox",
|
|
54
|
+
checked: options.ignoreWhitespace,
|
|
55
|
+
accelerator: "CommandOrControl+Shift+W",
|
|
56
|
+
click: (item) => {
|
|
57
|
+
options.ignoreWhitespace = item.checked;
|
|
58
|
+
currentSignature = writeReviewFile(options).signature;
|
|
59
|
+
mainWindow?.webContents.reloadIgnoringCache();
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
menuTemplate.push({ role: "windowMenu" });
|
|
65
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate));
|
|
23
66
|
const appIcon = nativeImage.createFromPath(iconPath);
|
|
24
67
|
if (process.platform === "darwin" && app.dock && !appIcon.isEmpty()) {
|
|
25
68
|
app.dock.setIcon(appIcon);
|
|
@@ -37,6 +80,7 @@ app.whenReady().then(async () => {
|
|
|
37
80
|
backgroundColor: "#2b2b2b",
|
|
38
81
|
autoHideMenuBar: true,
|
|
39
82
|
webPreferences: {
|
|
83
|
+
preload: preloadPath,
|
|
40
84
|
contextIsolation: true,
|
|
41
85
|
nodeIntegration: false,
|
|
42
86
|
sandbox: true,
|
|
@@ -83,8 +127,12 @@ function writeReviewFile(input) {
|
|
|
83
127
|
includeUntracked: input.includeUntracked,
|
|
84
128
|
context: input.context,
|
|
85
129
|
title: "monacori",
|
|
130
|
+
ignoreWhitespace: input.ignoreWhitespace,
|
|
131
|
+
lazyLoad: true, // Electron streams per-file bodies/source over IPC (monacori:get-file / get-source)
|
|
86
132
|
});
|
|
87
133
|
writeFileSync(reviewPath(), build.html);
|
|
134
|
+
currentBodies = build.lazyBodies ?? [];
|
|
135
|
+
currentSourceData = build.lazySourceData ?? "[]";
|
|
88
136
|
return { signature: build.signature };
|
|
89
137
|
}
|
|
90
138
|
function reviewPath() {
|
|
@@ -100,6 +148,7 @@ function parseArgs(args) {
|
|
|
100
148
|
includeUntracked: args.includes("--include-untracked"),
|
|
101
149
|
context: contextValue ? parsePositiveInteger(contextValue, "--context") : 12,
|
|
102
150
|
watch: !args.includes("--no-watch"),
|
|
151
|
+
ignoreWhitespace: args.includes("--ignore-whitespace"),
|
|
103
152
|
};
|
|
104
153
|
}
|
|
105
154
|
function readOption(args, name) {
|
package/dist/assets.d.ts
ADDED
package/dist/assets.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
6
|
+
const viewerAssetCache = new Map();
|
|
7
|
+
// Client viewer script/stylesheet live in sibling files (copied to dist/ at build) so this
|
|
8
|
+
// module stays small and the client code can use template literals freely (no String.raw).
|
|
9
|
+
export function readViewerAsset(name) {
|
|
10
|
+
let cached = viewerAssetCache.get(name);
|
|
11
|
+
if (cached === undefined) {
|
|
12
|
+
cached = readFileSync(join(dirname(fileURLToPath(import.meta.url)), name), "utf8");
|
|
13
|
+
viewerAssetCache.set(name, cached);
|
|
14
|
+
}
|
|
15
|
+
return cached;
|
|
16
|
+
}
|
|
17
|
+
export function diff2HtmlCss() {
|
|
18
|
+
try {
|
|
19
|
+
return readFileSync(nodeRequire.resolve("diff2html/bundles/css/diff2html.min.css"), "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function diffCss() {
|
|
26
|
+
return readViewerAsset("viewer.css");
|
|
27
|
+
}
|
|
28
|
+
export function diffScript() {
|
|
29
|
+
return readViewerAsset("viewer.client.js");
|
|
30
|
+
}
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DiffReviewBuild } from "./types.js";
|
|
2
|
+
export declare function buildDiffReview(input: {
|
|
3
|
+
base?: string;
|
|
4
|
+
staged: boolean;
|
|
5
|
+
includeUntracked: boolean;
|
|
6
|
+
context: number;
|
|
7
|
+
title: string;
|
|
8
|
+
watch?: boolean;
|
|
9
|
+
ignoreWhitespace?: boolean;
|
|
10
|
+
lazy?: boolean;
|
|
11
|
+
lazyLoad?: boolean;
|
|
12
|
+
}): DiffReviewBuild;
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { isGitRepository } from "./git.js";
|
|
4
|
+
import { collectHttpEnvironments, collectReviewFileStates, collectSourceFiles, parseUnifiedDiff, readUnifiedDiff } from "./diff.js";
|
|
5
|
+
import { renderDiff2Html } from "./highlight.js";
|
|
6
|
+
import { diffSubtitle, renderDiffHtml, renderNotGitRepoHtml, shouldLazyRender, splitDiffForLazy } from "./render.js";
|
|
7
|
+
export function buildDiffReview(input) {
|
|
8
|
+
if (!isGitRepository(process.cwd())) {
|
|
9
|
+
return {
|
|
10
|
+
html: renderNotGitRepoHtml(process.cwd()),
|
|
11
|
+
files: 0,
|
|
12
|
+
hunks: 0,
|
|
13
|
+
signature: "not-a-git-repo",
|
|
14
|
+
generatedAt: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const diffText = readUnifiedDiff({
|
|
18
|
+
base: input.base,
|
|
19
|
+
staged: input.staged,
|
|
20
|
+
context: input.context,
|
|
21
|
+
includeUntracked: input.includeUntracked,
|
|
22
|
+
ignoreWhitespace: input.ignoreWhitespace,
|
|
23
|
+
});
|
|
24
|
+
const files = parseUnifiedDiff(diffText);
|
|
25
|
+
const sourceFiles = collectSourceFiles(files);
|
|
26
|
+
const fileStates = collectReviewFileStates(files, sourceFiles);
|
|
27
|
+
const httpEnvironments = collectHttpEnvironments(process.cwd());
|
|
28
|
+
const hunks = files.reduce((sum, file) => sum + file.hunks.length, 0);
|
|
29
|
+
const generatedAt = new Date().toISOString();
|
|
30
|
+
const diffHtml = renderDiff2Html(diffText);
|
|
31
|
+
const totalLines = files.reduce((sum, file) => sum + file.hunks.reduce((t, h) => t + h.lines.length, 0), 0);
|
|
32
|
+
// lazy-LOAD (Phase 2) serves each file body + source on demand instead of embedding them; it implies
|
|
33
|
+
// lazy (shells). Gated by size: only big reviews lazy-LOAD — small ones embed (no IPC round-trips, no
|
|
34
|
+
// "Loading source…" flash). The transport opts in (serve/Electron pass lazyLoad:true); standalone has
|
|
35
|
+
// no server. A big standalone review still lazy-materializes from embedded islands (Phase 1).
|
|
36
|
+
const big = shouldLazyRender(files.length, totalLines);
|
|
37
|
+
const lazyLoad = (input.lazyLoad ?? false) && big;
|
|
38
|
+
const lazy = lazyLoad || (input.lazy ?? big);
|
|
39
|
+
const diffSplit = lazy ? splitDiffForLazy(diffHtml, files) : { container: diffHtml, islands: "", bodies: [] };
|
|
40
|
+
const signature = createHash("sha1")
|
|
41
|
+
.update(diffText)
|
|
42
|
+
.update("\n")
|
|
43
|
+
.update(sourceFiles.map((file) => `${file.path}\0${file.size}\0${file.embedded ? file.content : file.skippedReason ?? ""}`).join("\n"))
|
|
44
|
+
.update("\n")
|
|
45
|
+
.update(JSON.stringify(httpEnvironments))
|
|
46
|
+
.digest("hex");
|
|
47
|
+
const html = renderDiffHtml({
|
|
48
|
+
files,
|
|
49
|
+
diffHtml: diffSplit.container,
|
|
50
|
+
diffIslands: lazyLoad ? "" : diffSplit.islands,
|
|
51
|
+
lazy,
|
|
52
|
+
lazyLoad,
|
|
53
|
+
sourceFiles,
|
|
54
|
+
fileStates,
|
|
55
|
+
httpEnvironments,
|
|
56
|
+
title: input.title,
|
|
57
|
+
subtitle: diffSubtitle(input),
|
|
58
|
+
projectName: basename(process.cwd()),
|
|
59
|
+
projectPath: process.cwd(),
|
|
60
|
+
watch: Boolean(input.watch),
|
|
61
|
+
ignoreWhitespace: Boolean(input.ignoreWhitespace),
|
|
62
|
+
signature,
|
|
63
|
+
generatedAt,
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
html,
|
|
67
|
+
files: files.length,
|
|
68
|
+
hunks,
|
|
69
|
+
signature,
|
|
70
|
+
generatedAt,
|
|
71
|
+
lazyBodies: diffSplit.bodies,
|
|
72
|
+
lazySourceData: lazyLoad ? JSON.stringify(sourceFiles) : undefined,
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
export type HttpSendResult = {
|
|
9
|
-
ok: boolean;
|
|
10
|
-
status?: number;
|
|
11
|
-
statusText?: string;
|
|
12
|
-
headers?: Record<string, string>;
|
|
13
|
-
body?: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
durationMs: number;
|
|
16
|
-
};
|
|
17
|
-
type DiffReviewBuild = {
|
|
18
|
-
html: string;
|
|
19
|
-
files: number;
|
|
20
|
-
hunks: number;
|
|
21
|
-
signature: string;
|
|
22
|
-
generatedAt: string;
|
|
23
|
-
};
|
|
24
|
-
export declare function main(): void;
|
|
25
|
-
export declare function buildDiffReview(input: {
|
|
26
|
-
base?: string;
|
|
27
|
-
staged: boolean;
|
|
28
|
-
includeUntracked: boolean;
|
|
29
|
-
context: number;
|
|
30
|
-
title: string;
|
|
31
|
-
watch?: boolean;
|
|
32
|
-
}): DiffReviewBuild;
|
|
33
|
-
export declare function performHttpRequest(request: HttpSendRequest): Promise<HttpSendResult>;
|
|
34
|
-
export {};
|
|
2
|
+
import { main } from "./commands.js";
|
|
3
|
+
export { main };
|
|
4
|
+
export { buildDiffReview } from "./build.js";
|
|
5
|
+
export { performHttpRequest } from "./server.js";
|
|
6
|
+
export type { HttpSendRequest, HttpSendResult } from "./types.js";
|