@happy-nut/monacori 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/assets/icon.png +0 -0
- package/bin/monacori.js +5 -0
- package/dist/app-main.d.ts +1 -0
- package/dist/app-main.js +121 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.js +3545 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 happy-nut
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# monacori
|
|
2
|
+
|
|
3
|
+
Validation control plane for AI-generated code changes.
|
|
4
|
+
|
|
5
|
+
`monacori` is not an agent orchestrator. It does not manage panes, sessions, worktrees, or model adapters. Its job is narrower: after an AI edits a repository, produce verification evidence that a human can trust.
|
|
6
|
+
|
|
7
|
+
It gives you:
|
|
8
|
+
|
|
9
|
+
- detected verification commands in `.monacori/config.json`
|
|
10
|
+
- repeatable verification logs under `.monacori/logs/`
|
|
11
|
+
- a local desktop review app for live diff inspection
|
|
12
|
+
- diff2html review pages under `.monacori/diffs/`
|
|
13
|
+
- searchable source previews, including unchanged indexed files
|
|
14
|
+
- compact validation reports under `.monacori/reports/`
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
AI coding output is hard to trust when review depends on chat memory or vague "done" claims. `monacori` keeps the review artifacts in the repository so the state can be inspected, rerun, and discussed.
|
|
19
|
+
|
|
20
|
+
The intended loop is simple:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
AI edits code.
|
|
24
|
+
monacori runs verification.
|
|
25
|
+
monacori creates a reviewable diff artifact.
|
|
26
|
+
You inspect evidence before accepting the change.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g @happy-nut/monacori
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
After installation, the short command is:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
dg
|
|
39
|
+
```
|
|
40
|
+
|
|
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:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
brew install happy-nut/monacori/monacori
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For local development:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/happy-nut/monacori.git
|
|
51
|
+
cd monacori
|
|
52
|
+
npm install
|
|
53
|
+
npm run build
|
|
54
|
+
npm link
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
Inside the repository you want to validate:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
dg
|
|
63
|
+
monacori check --include-untracked
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`dg` 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
|
+
|
|
68
|
+
`check` runs configured verification commands, writes a log, creates a browser diff review, and records a compact report.
|
|
69
|
+
|
|
70
|
+
For a one-off command:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
monacori check -- npm test
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For live diff review while an AI is still editing:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
dg
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Diff Review
|
|
83
|
+
|
|
84
|
+
The desktop review app is powered by Electron and diff2html. It reads Git diff and source files directly from the local repository, writes a local `.monacori/app-review.html` file, and refreshes when the working tree changes. It does not start an HTTP server.
|
|
85
|
+
|
|
86
|
+
- `F7`: show the next changed hunk, starting from the current source file when possible
|
|
87
|
+
- `Shift+F7`: previous changed hunk
|
|
88
|
+
- `]` / `[`: fallback keys if the browser captures function keys
|
|
89
|
+
- `Shift Shift`: search indexed files, including unchanged files
|
|
90
|
+
- `Cmd/Ctrl+E`: open recent files
|
|
91
|
+
- `Cmd/Ctrl+Down`: jump from the source cursor to a declaration-like match for the symbol under it
|
|
92
|
+
|
|
93
|
+
The app opens as a read-only source viewer by default. The `Files` tab opens indexed files even when they are unchanged, and `F7` switches into the diff view when you want to inspect changes. Drag-copying source text adds `path:line-range` plus a fenced code block to the clipboard so the snippet can be pasted into an AI prompt with context. Viewed marks are stored with file signatures, so a file becomes unviewed again when it changes.
|
|
94
|
+
|
|
95
|
+
The browser artifact path is still available through `monacori diff`. Add `--watch` there only when you specifically want a browser-served live review.
|
|
96
|
+
|
|
97
|
+
## Commands
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
monacori open [--base HEAD] [--staged] [--tracked-only] [--context 12] [--no-watch]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Opens the local desktop review app for the current directory. `dg` 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
|
+
|
|
105
|
+
```bash
|
|
106
|
+
monacori check [--include-untracked] [--staged] [--base HEAD] [--context 12] [--open] [--no-verify] [--no-diff] [-- <command>]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Runs verification and creates a validation report. By default it uses commands from `.monacori/config.json`; pass `-- <command>` to override for one run.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
monacori init [--force]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Creates `.monacori/` with config, state, decisions, reports, logs, and diff directories. Because these are local validation artifacts, monacori also adds `.monacori/` to `.gitignore` inside Git worktrees.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
monacori install [--force] [--apply-agent-docs]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Writes `.monacori/agent-snippet.md`, a short instruction block telling AI agents to run validation before claiming completion. With `--apply-agent-docs`, it updates `AGENTS.md` and `CLAUDE.md` marker blocks where available.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
monacori verify [-- <command>]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Runs configured verification commands and stores the log in `.monacori/logs/`. Exits non-zero on failure.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
monacori app [--base HEAD] [--staged] [--include-untracked] [--context 12] [--no-watch]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Launches the local desktop review app. `dg`, `monacori open`, and `monacori review` are aliases. Prefer `dg` for normal use.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
monacori diff [--base HEAD] [--staged] [--include-untracked] [--context 12] [--output review.html] [--open] [--watch] [--port 0]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Generates a browser-based side-by-side diff review. Add `--watch` to serve a live review that reloads when the working tree changes.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
monacori status
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Prints current branch, git status, diff stat, configured verification commands, recent reports, and recent logs.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
monacori report [--label manual] [--file report.md]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Stores a manual report under `.monacori/reports/` and appends a compact summary to `.monacori/state.md`.
|
|
152
|
+
|
|
153
|
+
## Repository State
|
|
154
|
+
|
|
155
|
+
`monacori init` creates:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
.monacori/
|
|
159
|
+
config.json verification commands and diff defaults
|
|
160
|
+
state.md compact validation history
|
|
161
|
+
decisions.md durable validation decisions
|
|
162
|
+
diffs/ generated browser diff reviews
|
|
163
|
+
reports/ validation reports
|
|
164
|
+
logs/ verification logs
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`monacori install` also writes `.monacori/agent-snippet.md` for projects that want to paste or apply agent-facing validation instructions.
|
|
168
|
+
|
|
169
|
+
Keep `.monacori/` ignored unless your team explicitly wants to commit validation state.
|
|
170
|
+
|
|
171
|
+
## Design Principles
|
|
172
|
+
|
|
173
|
+
- Verification evidence beats chat memory.
|
|
174
|
+
- Generated artifacts should be plain Markdown, JSON, logs, or static HTML.
|
|
175
|
+
- The tool should not require a specific AI agent, terminal multiplexer, editor, or worktree strategy.
|
|
176
|
+
- The default command should be useful after any AI-generated edit.
|
|
177
|
+
- A change is not accepted until verification evidence is clear or the gap is explicitly documented.
|
package/assets/icon.png
ADDED
|
Binary file
|
package/bin/monacori.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/app-main.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { app, BrowserWindow, Menu, nativeImage } from "electron";
|
|
5
|
+
import { buildDiffReview } from "./cli.js";
|
|
6
|
+
const FLOW_DIR = ".monacori";
|
|
7
|
+
const REVIEW_FILE = "app-review.html";
|
|
8
|
+
const WATCH_INTERVAL_MS = 1000;
|
|
9
|
+
app.setName("monacori");
|
|
10
|
+
const iconPath = join(dirname(fileURLToPath(import.meta.url)), "..", "assets", "icon.png");
|
|
11
|
+
const options = parseArgs(process.argv.slice(2));
|
|
12
|
+
let mainWindow;
|
|
13
|
+
let currentSignature = "";
|
|
14
|
+
let refreshTimer;
|
|
15
|
+
let refreshing = false;
|
|
16
|
+
if (!existsSync(options.root)) {
|
|
17
|
+
throw new Error(`Repository path does not exist: ${options.root}`);
|
|
18
|
+
}
|
|
19
|
+
app.whenReady().then(async () => {
|
|
20
|
+
process.chdir(options.root);
|
|
21
|
+
mkdirSync(FLOW_DIR, { recursive: true });
|
|
22
|
+
Menu.setApplicationMenu(null);
|
|
23
|
+
const appIcon = nativeImage.createFromPath(iconPath);
|
|
24
|
+
if (process.platform === "darwin" && app.dock && !appIcon.isEmpty()) {
|
|
25
|
+
app.dock.setIcon(appIcon);
|
|
26
|
+
}
|
|
27
|
+
const firstBuild = writeReviewFile(options);
|
|
28
|
+
currentSignature = firstBuild.signature;
|
|
29
|
+
mainWindow = new BrowserWindow({
|
|
30
|
+
width: 1440,
|
|
31
|
+
height: 960,
|
|
32
|
+
minWidth: 960,
|
|
33
|
+
minHeight: 640,
|
|
34
|
+
show: false,
|
|
35
|
+
title: "monacori",
|
|
36
|
+
icon: iconPath,
|
|
37
|
+
backgroundColor: "#2b2b2b",
|
|
38
|
+
autoHideMenuBar: true,
|
|
39
|
+
webPreferences: {
|
|
40
|
+
contextIsolation: true,
|
|
41
|
+
nodeIntegration: false,
|
|
42
|
+
sandbox: true,
|
|
43
|
+
spellcheck: false,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
|
47
|
+
mainWindow.once("ready-to-show", () => mainWindow?.show());
|
|
48
|
+
await mainWindow.loadFile(reviewPath());
|
|
49
|
+
if (options.watch) {
|
|
50
|
+
refreshTimer = setInterval(refreshIfChanged, WATCH_INTERVAL_MS);
|
|
51
|
+
}
|
|
52
|
+
}).catch((error) => {
|
|
53
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
54
|
+
app.quit();
|
|
55
|
+
});
|
|
56
|
+
app.on("window-all-closed", () => {
|
|
57
|
+
if (refreshTimer)
|
|
58
|
+
clearInterval(refreshTimer);
|
|
59
|
+
app.quit();
|
|
60
|
+
});
|
|
61
|
+
async function refreshIfChanged() {
|
|
62
|
+
if (refreshing || !mainWindow || mainWindow.isDestroyed())
|
|
63
|
+
return;
|
|
64
|
+
refreshing = true;
|
|
65
|
+
try {
|
|
66
|
+
const next = writeReviewFile(options);
|
|
67
|
+
if (next.signature !== currentSignature) {
|
|
68
|
+
currentSignature = next.signature;
|
|
69
|
+
mainWindow.webContents.reloadIgnoringCache();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
refreshing = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function writeReviewFile(input) {
|
|
80
|
+
const build = buildDiffReview({
|
|
81
|
+
base: input.base,
|
|
82
|
+
staged: input.staged,
|
|
83
|
+
includeUntracked: input.includeUntracked,
|
|
84
|
+
context: input.context,
|
|
85
|
+
title: "monacori",
|
|
86
|
+
});
|
|
87
|
+
writeFileSync(reviewPath(), build.html);
|
|
88
|
+
return { signature: build.signature };
|
|
89
|
+
}
|
|
90
|
+
function reviewPath() {
|
|
91
|
+
return join(options.root, FLOW_DIR, REVIEW_FILE);
|
|
92
|
+
}
|
|
93
|
+
function parseArgs(args) {
|
|
94
|
+
const root = readOption(args, "--cwd") ?? process.cwd();
|
|
95
|
+
const contextValue = readOption(args, "--context");
|
|
96
|
+
return {
|
|
97
|
+
root: resolve(root),
|
|
98
|
+
base: readOption(args, "--base"),
|
|
99
|
+
staged: args.includes("--staged"),
|
|
100
|
+
includeUntracked: args.includes("--include-untracked"),
|
|
101
|
+
context: contextValue ? parsePositiveInteger(contextValue, "--context") : 12,
|
|
102
|
+
watch: !args.includes("--no-watch"),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function readOption(args, name) {
|
|
106
|
+
const index = args.indexOf(name);
|
|
107
|
+
if (index < 0)
|
|
108
|
+
return undefined;
|
|
109
|
+
const value = args[index + 1];
|
|
110
|
+
if (!value || value.startsWith("--")) {
|
|
111
|
+
throw new Error(`Missing value for ${name}`);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
function parsePositiveInteger(value, optionName) {
|
|
116
|
+
const parsed = Number(value);
|
|
117
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
118
|
+
throw new Error(`${optionName} must be a non-negative integer`);
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export type HttpSendRequest = {
|
|
3
|
+
method: string;
|
|
4
|
+
url: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
body?: string;
|
|
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 {};
|