@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.
@@ -0,0 +1 @@
1
+ export declare function main(): void;
@@ -0,0 +1,678 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative } from "node:path";
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { createRequire } from "node:module";
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";
10
+ const nodeRequire = createRequire(import.meta.url);
11
+ export function main() {
12
+ const rawArgs = process.argv.slice(2);
13
+ const [command, ...args] = rawArgs;
14
+ try {
15
+ if (!command) {
16
+ openCurrentRepository([]);
17
+ return;
18
+ }
19
+ if (command !== "--help" && command !== "-h" && command.startsWith("-")) {
20
+ openCurrentRepository(rawArgs);
21
+ return;
22
+ }
23
+ switch (command) {
24
+ case "init":
25
+ initFlow(args);
26
+ break;
27
+ case "install":
28
+ installFlow(args);
29
+ 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
+ case "app":
41
+ case "review":
42
+ launchReviewApp(args);
43
+ break;
44
+ case "open":
45
+ openCurrentRepository(args);
46
+ break;
47
+ case "status":
48
+ printStatus();
49
+ break;
50
+ case "report":
51
+ recordReport(args);
52
+ break;
53
+ case "--help":
54
+ case "-h":
55
+ case "help":
56
+ printHelp();
57
+ break;
58
+ default:
59
+ throw new Error(`Unknown command: ${command}`);
60
+ }
61
+ }
62
+ catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ console.error(`monacori: ${message}`);
65
+ process.exit(1);
66
+ }
67
+ }
68
+ function initFlow(args) {
69
+ const force = args.includes("--force");
70
+ const quiet = args.includes("--quiet");
71
+ const root = process.cwd();
72
+ const flowPath = join(root, FLOW_DIR);
73
+ mkdirSync(flowPath, { recursive: true });
74
+ mkdirSync(join(flowPath, "reports"), { recursive: true });
75
+ mkdirSync(join(flowPath, "logs"), { recursive: true });
76
+ mkdirSync(join(flowPath, "diffs"), { recursive: true });
77
+ const config = {
78
+ version: 1,
79
+ projectName: basename(root),
80
+ verification: {
81
+ commands: detectVerificationCommands(root),
82
+ },
83
+ diff: {
84
+ context: 12,
85
+ includeUntracked: false,
86
+ },
87
+ };
88
+ writeIfMissing(join(flowPath, CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`, force);
89
+ writeIfMissing(join(flowPath, STATE_FILE), initialState(config), force);
90
+ writeIfMissing(join(flowPath, DECISIONS_FILE), initialDecisions(), force);
91
+ const ignored = ensureMonacoriGitignore(root);
92
+ if (!quiet) {
93
+ console.log(`Initialized ${FLOW_DIR}/ in ${root}`);
94
+ if (ignored) {
95
+ console.log(`Updated ${GITIGNORE_FILE} to ignore ${FLOW_DIR}/ validation artifacts.`);
96
+ }
97
+ console.log("Next: run `monacori app --include-untracked` to inspect changes, then `monacori check --include-untracked` to record verification.");
98
+ }
99
+ }
100
+ function installFlow(args) {
101
+ const force = args.includes("--force");
102
+ const applyAgentDocs = args.includes("--apply-agent-docs");
103
+ initFlow(["--quiet"]);
104
+ writeIfMissing(join(process.cwd(), FLOW_DIR, AGENT_SNIPPET_FILE), agentSnippet(), force);
105
+ if (applyAgentDocs) {
106
+ applyAgentDocSnippet("AGENTS.md");
107
+ applyAgentDocSnippet("CLAUDE.md");
108
+ }
109
+ console.log("Installed monacori validation instructions.");
110
+ console.log(`- ${FLOW_DIR}/${AGENT_SNIPPET_FILE}`);
111
+ if (applyAgentDocs) {
112
+ console.log("- Updated AGENTS.md / CLAUDE.md validation snippets where available.");
113
+ }
114
+ else {
115
+ console.log(`Next: add ${FLOW_DIR}/${AGENT_SNIPPET_FILE} to your agent instructions if desired.`);
116
+ }
117
+ }
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
+ function launchReviewApp(args) {
230
+ if (args.includes("--help") || args.includes("-h")) {
231
+ printAppHelp();
232
+ return;
233
+ }
234
+ ensureWritableFlowState();
235
+ const config = loadConfig();
236
+ const contextValue = readOption(args, "--context");
237
+ const context = contextValue ? parsePositiveInteger(contextValue, "--context") : config.diff.context;
238
+ const appArgs = [
239
+ appMainPath(),
240
+ "--cwd",
241
+ process.cwd(),
242
+ "--context",
243
+ String(context),
244
+ ];
245
+ const base = readOption(args, "--base");
246
+ if (base)
247
+ appArgs.push("--base", base);
248
+ if (args.includes("--staged"))
249
+ appArgs.push("--staged");
250
+ if (args.includes("--include-untracked") || config.diff.includeUntracked)
251
+ appArgs.push("--include-untracked");
252
+ if (args.includes("--no-watch"))
253
+ appArgs.push("--no-watch");
254
+ const electronBinary = resolveElectronBinary();
255
+ if (args.includes("--foreground")) {
256
+ const result = spawnSync(electronBinary, appArgs, { stdio: "inherit" });
257
+ process.exit(result.status ?? 0);
258
+ }
259
+ const child = spawn(electronBinary, appArgs, {
260
+ detached: true,
261
+ stdio: "ignore",
262
+ });
263
+ child.unref();
264
+ console.log("Opened monacori review app.");
265
+ }
266
+ function openCurrentRepository(args) {
267
+ if (args.includes("--help") || args.includes("-h")) {
268
+ printOpenHelp();
269
+ return;
270
+ }
271
+ const appArgs = args.filter((arg) => arg !== "--tracked-only");
272
+ if (!args.includes("--tracked-only") && !args.includes("--staged") && !args.includes("--include-untracked")) {
273
+ appArgs.push("--include-untracked");
274
+ }
275
+ launchReviewApp(appArgs);
276
+ }
277
+ function resolveElectronBinary() {
278
+ const electronModule = nodeRequire("electron");
279
+ if (typeof electronModule === "string") {
280
+ return electronModule;
281
+ }
282
+ if (electronModule && typeof electronModule === "object" && "default" in electronModule) {
283
+ const value = electronModule.default;
284
+ if (typeof value === "string") {
285
+ return value;
286
+ }
287
+ }
288
+ throw new Error("Electron runtime is not available. Run `npm install` and try again.");
289
+ }
290
+ function appMainPath() {
291
+ return join(dirname(fileURLToPath(import.meta.url)), "app-main.js");
292
+ }
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
+ function initialState(config) {
426
+ return [
427
+ "# Monacori Validation State",
428
+ "",
429
+ `Project: ${config.projectName}`,
430
+ `Initialized: ${new Date().toISOString()}`,
431
+ "",
432
+ "## Goal",
433
+ "- Keep AI-generated changes reviewable, test-backed, and easy to inspect.",
434
+ "",
435
+ "## Checks",
436
+ "",
437
+ "## Reports",
438
+ "",
439
+ ].join("\n");
440
+ }
441
+ function initialDecisions() {
442
+ return [
443
+ "# Monacori Decisions",
444
+ "",
445
+ "Record durable validation decisions here so future checks do not depend on chat memory.",
446
+ "",
447
+ ].join("\n");
448
+ }
449
+ function agentSnippet() {
450
+ return [
451
+ "<!-- MONACORI:START -->",
452
+ "## monacori Validation",
453
+ "",
454
+ "This repository uses monacori to verify AI-generated code changes.",
455
+ "",
456
+ "Before claiming completion on a code change:",
457
+ "",
458
+ "- Run `monacori check --include-untracked` or a more specific `monacori verify -- <command>`.",
459
+ "- Use `monacori app --include-untracked` while changes are still moving.",
460
+ "- Inspect changed hunks with F7 / Shift+F7.",
461
+ "- Use Shift Shift in the diff review to search indexed files, including unchanged files.",
462
+ "- 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.",
466
+ "<!-- MONACORI:END -->",
467
+ "",
468
+ ].join("\n");
469
+ }
470
+ function applyAgentDocSnippet(fileName) {
471
+ const path = join(process.cwd(), fileName);
472
+ const snippet = agentSnippet();
473
+ if (!existsSync(path)) {
474
+ writeFileSync(path, `# ${fileName}\n\n${snippet}`);
475
+ return;
476
+ }
477
+ const current = readFileSync(path, "utf8");
478
+ const markerPattern = /<!-- MONACORI:START -->[\s\S]*?<!-- MONACORI:END -->\n?/;
479
+ const next = markerPattern.test(current)
480
+ ? current.replace(markerPattern, snippet)
481
+ : `${current.trimEnd()}\n\n${snippet}`;
482
+ writeFileSync(path, next);
483
+ }
484
+ function ensureInitialized() {
485
+ if (!existsSync(join(process.cwd(), FLOW_DIR, CONFIG_FILE))) {
486
+ throw new Error(`Missing ${FLOW_DIR}/. Run \`monacori init\` first.`);
487
+ }
488
+ }
489
+ function ensureWritableFlowState() {
490
+ if (!existsSync(join(process.cwd(), FLOW_DIR, CONFIG_FILE))) {
491
+ initFlow(["--quiet"]);
492
+ return;
493
+ }
494
+ ensureMonacoriGitignore(process.cwd());
495
+ }
496
+ function loadConfig() {
497
+ ensureInitialized();
498
+ const raw = JSON.parse(readFileSync(join(process.cwd(), FLOW_DIR, CONFIG_FILE), "utf8"));
499
+ return {
500
+ version: 1,
501
+ projectName: raw.projectName ?? basename(process.cwd()),
502
+ verification: {
503
+ commands: Array.isArray(raw.verification?.commands) ? raw.verification.commands : [],
504
+ },
505
+ diff: {
506
+ context: typeof raw.diff?.context === "number" ? raw.diff.context : 12,
507
+ includeUntracked: typeof raw.diff?.includeUntracked === "boolean" ? raw.diff.includeUntracked : false,
508
+ },
509
+ };
510
+ }
511
+ function getVerificationCommands(config) {
512
+ return config.verification.commands.filter((command) => command.trim().length > 0);
513
+ }
514
+ function writeIfMissing(path, content, force) {
515
+ if (!force && existsSync(path)) {
516
+ return;
517
+ }
518
+ writeFileSync(path, content);
519
+ }
520
+ function ensureMonacoriGitignore(root) {
521
+ if (git(root, ["rev-parse", "--is-inside-work-tree"]) !== "true") {
522
+ return false;
523
+ }
524
+ const path = join(root, GITIGNORE_FILE);
525
+ const content = existsSync(path) ? readFileSync(path, "utf8") : "";
526
+ const hasEntry = content
527
+ .split(/\r?\n/)
528
+ .map((line) => line.trim())
529
+ .some((line) => line === FLOW_DIR || line === `${FLOW_DIR}/`);
530
+ if (hasEntry) {
531
+ return false;
532
+ }
533
+ const prefix = content.length === 0 ? "" : content.endsWith("\n") ? "\n" : "\n\n";
534
+ writeFileSync(path, `${content}${prefix}# monacori local validation artifacts\n${FLOW_DIR}/\n`);
535
+ return true;
536
+ }
537
+ function detectVerificationCommands(root) {
538
+ const commands = new Set();
539
+ const packagePath = join(root, "package.json");
540
+ if (existsSync(packagePath)) {
541
+ const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
542
+ const packageManager = detectPackageManager(root);
543
+ const scripts = packageJson.scripts ?? {};
544
+ for (const script of ["typecheck", "lint", "test", "build"]) {
545
+ if (scripts[script]) {
546
+ commands.add(packageScriptCommand(packageManager, script));
547
+ }
548
+ }
549
+ }
550
+ if (existsSync(join(root, "pyproject.toml"))) {
551
+ commands.add(existsSync(join(root, "poetry.lock")) ? "poetry run pytest" : "pytest");
552
+ }
553
+ if (existsSync(join(root, "Cargo.toml"))) {
554
+ commands.add("cargo test");
555
+ }
556
+ if (existsSync(join(root, "go.mod"))) {
557
+ commands.add("go test ./...");
558
+ }
559
+ return Array.from(commands);
560
+ }
561
+ function detectPackageManager(root) {
562
+ if (existsSync(join(root, "pnpm-lock.yaml")))
563
+ return "pnpm";
564
+ if (existsSync(join(root, "yarn.lock")))
565
+ return "yarn";
566
+ if (existsSync(join(root, "bun.lock")) || existsSync(join(root, "bun.lockb")))
567
+ return "bun";
568
+ return "npm";
569
+ }
570
+ function packageScriptCommand(manager, script) {
571
+ if (manager === "npm") {
572
+ return script === "test" ? "npm test" : `npm run ${script}`;
573
+ }
574
+ if (manager === "yarn") {
575
+ return `yarn ${script}`;
576
+ }
577
+ if (manager === "bun") {
578
+ return `bun run ${script}`;
579
+ }
580
+ return `pnpm ${script}`;
581
+ }
582
+ function printHelp() {
583
+ console.log(`monacori
584
+
585
+ Validation control plane for AI-generated code changes.
586
+
587
+ Usage:
588
+ mo
589
+ monacori open [--base HEAD] [--staged] [--tracked-only]
590
+ monacori check [--include-untracked] [--open] [--no-verify] [--no-diff] [-- <command>]
591
+ monacori init [--force]
592
+ 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
+
606
+ Diff review keys:
607
+ F7 next changed hunk
608
+ Shift+F7 previous changed hunk
609
+ Shift Shift file search across indexed files
610
+ Cmd/Ctrl+E recent files
611
+ Cmd/Ctrl+Down jump to symbol under cursor
612
+ `);
613
+ }
614
+ function printOpenHelp() {
615
+ console.log(`monacori open
616
+
617
+ Open the local desktop review app for the current directory. This is the default command behind \`mo\` and \`monacori\` with no arguments.
618
+
619
+ It auto-initializes .monacori/ when needed, makes sure .monacori/ is ignored in Git worktrees, and includes untracked files by default so new AI-created files are visible.
620
+
621
+ Usage:
622
+ mo
623
+ monacori open [--base HEAD] [--staged] [--tracked-only] [--context 12] [--no-watch] [--foreground]
624
+
625
+ Options:
626
+ --tracked-only inspect tracked changes only
627
+ `);
628
+ }
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
+ function printAppHelp() {
666
+ console.log(`monacori app
667
+
668
+ Launch the local desktop review app. The app reads Git diff and source files directly from this repository, writes a local review file under .monacori/, and refreshes when the working tree changes. It does not start an HTTP server.
669
+
670
+ Usage:
671
+ monacori app [--base HEAD] [--staged] [--include-untracked] [--context 12] [--no-watch] [--foreground]
672
+
673
+ Aliases:
674
+ mo
675
+ monacori open
676
+ monacori review
677
+ `);
678
+ }
@@ -0,0 +1,10 @@
1
+ export declare const FLOW_DIR = ".monacori";
2
+ export declare const GITIGNORE_FILE = ".gitignore";
3
+ export declare const CONFIG_FILE = "config.json";
4
+ export declare const STATE_FILE = "state.md";
5
+ export declare const DECISIONS_FILE = "decisions.md";
6
+ export declare const AGENT_SNIPPET_FILE = "agent-snippet.md";
7
+ export declare const SOURCE_MAX_FILE_BYTES = 220000;
8
+ export declare const SOURCE_MAX_TOTAL_BYTES = 50000000;
9
+ export declare const SOURCE_MAX_FILES = 20000;
10
+ export declare const IMAGE_MAX_BYTES = 2000000;