monomind 2.7.10 → 2.7.12
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/package.json +3 -3
- package/packages/@monomind/cli/.claude/helpers/token-tracker.cjs +95 -5
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +16 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/context-signals.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/context.mjs +11 -7
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/critique-storage.mjs +3 -3
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detect-csp.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/detector/fix/index.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-admin.mjs +10 -10
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-before-edit.mjs +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/hook-lib.mjs +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/manual-apply.mjs +3 -3
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live/svelte-component.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-accept.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-copy-edit-agent.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-insert.mjs +2 -2
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs +4 -4
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-server.mjs +1 -1
- package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-wrap.mjs +3 -3
- package/packages/@monomind/cli/bin/cli.js +59 -0
- package/packages/@monomind/cli/dist/src/commands/org.js +43 -0
- package/packages/@monomind/cli/package.json +6 -6
- package/scripts/build-fs.mjs +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.12",
|
|
4
4
|
"description": "Open-source CLI extension for Claude Code. Adds an MCP server with a codebase knowledge graph, persistent memory, multi-agent coordination, and reusable slash commands. MIT licensed, runs locally, no data leaves your machine.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@anthropic-ai/claude-agent-sdk": "^0.3.207",
|
|
66
66
|
"@monoes/monobrowse": "^1.0.6",
|
|
67
|
-
"@monoes/monodesign": "^1.2.
|
|
67
|
+
"@monoes/monodesign": "^1.2.2",
|
|
68
68
|
"@monoes/monograph": "^1.5.4",
|
|
69
69
|
"@noble/ed25519": "^2.1.0",
|
|
70
70
|
"mammoth": "^1.12.0",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"@protobufjs/utf8": ">=1.1.1",
|
|
110
110
|
"@grpc/grpc-js": ">=1.14.4",
|
|
111
111
|
"vite": ">=8.0.16",
|
|
112
|
-
"postcss": ">=8.5.
|
|
112
|
+
"postcss": ">=8.5.18",
|
|
113
113
|
"fast-uri": ">=4.1.1",
|
|
114
114
|
"form-data": ">=4.0.6",
|
|
115
115
|
"ip-address": ">=10.1.1",
|
|
@@ -403,6 +403,26 @@ function parseAllSessions(dateStart, dateEnd) {
|
|
|
403
403
|
|
|
404
404
|
var jsonlFiles = collectJsonlFiles(dirPath);
|
|
405
405
|
for (var j = 0; j < jsonlFiles.length; j++) {
|
|
406
|
+
// Skip transcripts last written before the window opened. Sessions are
|
|
407
|
+
// append-only, so a file whose mtime predates dateStart cannot hold an
|
|
408
|
+
// entry inside the range — reading and JSON.parsing it is pure waste.
|
|
409
|
+
//
|
|
410
|
+
// `tokens today` was parsing every transcript on the machine: ~1GB over
|
|
411
|
+
// ~2,200 files here, of which ~300 had been touched that day. That took
|
|
412
|
+
// ~10s, which is what made it unusable as a SessionStart hook (#42) —
|
|
413
|
+
// the documented hook sets a 10s timeout and `npx` adds its own
|
|
414
|
+
// registry-check overhead on top.
|
|
415
|
+
//
|
|
416
|
+
// Safe with respect to the seenMsgIds dedupe: parseSessionFile applies
|
|
417
|
+
// the date filter BEFORE groupAndClassify ever consults that set, so
|
|
418
|
+
// out-of-range entries never registered a message id in the first place.
|
|
419
|
+
// Skipping the file is exactly equivalent to reading it and discarding
|
|
420
|
+
// every line. Only applied when a start bound was actually requested.
|
|
421
|
+
if (dateStart) {
|
|
422
|
+
var fstat;
|
|
423
|
+
try { fstat = fs.statSync(jsonlFiles[j]); } catch (_) { fstat = null; }
|
|
424
|
+
if (fstat && fstat.mtime < dateStart) continue;
|
|
425
|
+
}
|
|
406
426
|
var session = parseSessionFile(jsonlFiles[j], dirName, seenMsgIds, dateStart, dateEnd);
|
|
407
427
|
if (session && session.apiCalls > 0) {
|
|
408
428
|
if (!projectMap[dirName]) {
|
|
@@ -543,14 +563,79 @@ function _computeQuickTotals() {
|
|
|
543
563
|
return { todayCost: todayCost, todayCalls: todayCalls, monthCost: monthCost, monthCalls: monthCalls };
|
|
544
564
|
}
|
|
545
565
|
|
|
566
|
+
function _formatQuickTotals(d) {
|
|
567
|
+
return '[TOKEN_USAGE] Today: ' + fmt$(d.todayCost) + ' (' + d.todayCalls + ' calls) | Month: ' + fmt$(d.monthCost) + ' (' + d.monthCalls + ' calls)';
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// The quick summary needs a MONTH window, and every transcript on an active
|
|
571
|
+
// machine has been touched this month — so unlike the day view it cannot be
|
|
572
|
+
// narrowed by mtime and inherently costs a full parse (~9.4s over ~1GB here).
|
|
573
|
+
//
|
|
574
|
+
// That is what broke `monomind tokens today` as a SessionStart hook (#42): the
|
|
575
|
+
// documented hook allows 10s, so the computation alone straddles the timeout
|
|
576
|
+
// before `npx` has even checked the registry, and Claude Code hangs waiting.
|
|
577
|
+
//
|
|
578
|
+
// A cost read-out does not need to be to-the-second, so serve it from cache and
|
|
579
|
+
// refresh out of band: a session start returns instantly, and the next one
|
|
580
|
+
// picks up the newly written value. A cold cache prints nothing rather than
|
|
581
|
+
// blocking the editor — never make the user wait on a status line.
|
|
582
|
+
var QUICK_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
583
|
+
|
|
584
|
+
function _quickCachePath() {
|
|
585
|
+
return path.join(path.dirname(getClaudeProjectsDir()), '.monomind-token-summary.json');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function _readQuickCache() {
|
|
589
|
+
try {
|
|
590
|
+
var raw = JSON.parse(fs.readFileSync(_quickCachePath(), 'utf8'));
|
|
591
|
+
if (!raw || typeof raw.computedAt !== 'number' || !raw.totals) return null;
|
|
592
|
+
return raw;
|
|
593
|
+
} catch (_) { return null; }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function _writeQuickCache(totals) {
|
|
597
|
+
try {
|
|
598
|
+
var tmp = _quickCachePath() + '.' + process.pid + '.tmp';
|
|
599
|
+
fs.writeFileSync(tmp, JSON.stringify({ computedAt: Date.now(), totals: totals }));
|
|
600
|
+
fs.renameSync(tmp, _quickCachePath()); // atomic: never expose a half-written cache
|
|
601
|
+
} catch (_) { /* cache is best-effort */ }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/** Recompute and persist the cached totals. Used by the background refresh. */
|
|
605
|
+
function refreshQuickSummary() {
|
|
606
|
+
var d = _computeQuickTotals();
|
|
607
|
+
if (d) _writeQuickCache(d);
|
|
608
|
+
return d;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function _spawnQuickRefresh() {
|
|
612
|
+
try {
|
|
613
|
+
var child = require('child_process').spawn(
|
|
614
|
+
process.execPath, [__filename, 'refresh-summary'],
|
|
615
|
+
{ detached: true, stdio: 'ignore' },
|
|
616
|
+
);
|
|
617
|
+
child.unref(); // must not hold the caller's event loop open
|
|
618
|
+
} catch (_) { /* best effort */ }
|
|
619
|
+
}
|
|
620
|
+
|
|
546
621
|
/**
|
|
547
622
|
* Returns a one-line token usage summary for the current day and month.
|
|
548
|
-
* Called at session-restore.
|
|
623
|
+
* Called at session-restore. Cache-first and non-blocking — see the note above.
|
|
549
624
|
*/
|
|
550
625
|
function quickSummary() {
|
|
551
|
-
var
|
|
552
|
-
|
|
553
|
-
|
|
626
|
+
var cached = _readQuickCache();
|
|
627
|
+
var fresh = cached && (Date.now() - cached.computedAt) < QUICK_CACHE_TTL_MS;
|
|
628
|
+
if (fresh) return _formatQuickTotals(cached.totals);
|
|
629
|
+
// Stale or missing: never recompute inline, or we reintroduce #42. Serve the
|
|
630
|
+
// stale figure (clearly better than nothing) and refresh for next time.
|
|
631
|
+
_spawnQuickRefresh();
|
|
632
|
+
return cached ? _formatQuickTotals(cached.totals) : null;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Synchronous full computation. For `tokens` CLI views, which may block. */
|
|
636
|
+
function quickSummaryBlocking() {
|
|
637
|
+
var d = refreshQuickSummary();
|
|
638
|
+
return d ? _formatQuickTotals(d) : null;
|
|
554
639
|
}
|
|
555
640
|
|
|
556
641
|
/**
|
|
@@ -913,6 +998,8 @@ function runInteractive() {
|
|
|
913
998
|
module.exports = {
|
|
914
999
|
parseAllSessions: parseAllSessions,
|
|
915
1000
|
quickSummary: quickSummary,
|
|
1001
|
+
quickSummaryBlocking: quickSummaryBlocking,
|
|
1002
|
+
refreshQuickSummary: refreshQuickSummary,
|
|
916
1003
|
quickSummaryData: quickSummaryData,
|
|
917
1004
|
renderDashboard: renderDashboard,
|
|
918
1005
|
runInteractive: runInteractive,
|
|
@@ -925,7 +1012,10 @@ module.exports = {
|
|
|
925
1012
|
if (require.main === module) {
|
|
926
1013
|
var args = process.argv.slice(2);
|
|
927
1014
|
var cmd = args[0] || 'dashboard';
|
|
928
|
-
if (cmd === 'summary') {
|
|
1015
|
+
if (cmd === 'refresh-summary') {
|
|
1016
|
+
// Detached background refresh spawned by quickSummary(). Silent by design.
|
|
1017
|
+
refreshQuickSummary();
|
|
1018
|
+
} else if (cmd === 'summary') {
|
|
929
1019
|
var s = quickSummary();
|
|
930
1020
|
process.stdout.write((s || 'No token data available') + '\n');
|
|
931
1021
|
} else if (cmd === 'report') {
|
|
@@ -368,8 +368,23 @@ function _graphGateWriteSessions(sessions) {
|
|
|
368
368
|
// one is reclaimed, we still perform an atomic-rename write — no worse than the
|
|
369
369
|
// old behavior, and never a hang. Hooks run on every tool call, so the total
|
|
370
370
|
// wait is deliberately tiny.
|
|
371
|
-
|
|
371
|
+
// The acquire budget MUST exceed the stale threshold. It used to be 250ms
|
|
372
|
+
// against a 2000ms stale window, which had two consequences:
|
|
373
|
+
//
|
|
374
|
+
// 1. A writer that could not get the lock within 250ms gave up and wrote
|
|
375
|
+
// UNLOCKED — precisely the lost-update this lock exists to prevent. CI
|
|
376
|
+
// landed 9 of 16 concurrent writers; a fast machine hides it, because the
|
|
377
|
+
// critical section is microseconds and the budget is never exhausted.
|
|
378
|
+
// 2. The stale-reclaim branch below was nearly unreachable. Reclaiming needs
|
|
379
|
+
// the lock to be older than 2000ms, but we stopped waiting after 250ms, so
|
|
380
|
+
// a crashed holder's lock was almost never actually reclaimed.
|
|
381
|
+
//
|
|
382
|
+
// With the budget above the stale window both paths work: a live holder is
|
|
383
|
+
// waited out (its critical section is a single read-modify-write), and a dead
|
|
384
|
+
// holder's lock is reclaimed at 2s and then taken. The waiting only happens
|
|
385
|
+
// under real contention — an uncontended acquire is one mkdir.
|
|
372
386
|
var _GRAPH_GATE_LOCK_STALE_MS = 2000;
|
|
387
|
+
var _GRAPH_GATE_LOCK_TIMEOUT_MS = 3000;
|
|
373
388
|
|
|
374
389
|
function _sleepSync(ms) {
|
|
375
390
|
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch (e) { /* no SAB */ }
|
|
@@ -93,7 +93,7 @@ function latestCritique(cwd) {
|
|
|
93
93
|
p0: numKey('p0', 'p0_count'),
|
|
94
94
|
p1: numKey('p1', 'p1_count'),
|
|
95
95
|
timestamp: get('timestamp'),
|
|
96
|
-
file: path.relative(cwd, path.join(dir, newest)),
|
|
96
|
+
file: path.relative(cwd, path.join(dir, newest)).split(path.sep).join('/'),
|
|
97
97
|
trend,
|
|
98
98
|
openP0,
|
|
99
99
|
};
|
|
@@ -75,10 +75,10 @@ export function loadContext(cwd = process.cwd(), options = {}) {
|
|
|
75
75
|
return {
|
|
76
76
|
hasProduct: !!product,
|
|
77
77
|
product,
|
|
78
|
-
productPath: productPath ? path.relative(absCwd, productPath) : null,
|
|
78
|
+
productPath: productPath ? path.relative(absCwd, productPath).split(path.sep).join('/') : null,
|
|
79
79
|
hasDesign: !!design,
|
|
80
80
|
design,
|
|
81
|
-
designPath: designPath ? path.relative(absCwd, designPath) : null,
|
|
81
|
+
designPath: designPath ? path.relative(absCwd, designPath).split(path.sep).join('/') : null,
|
|
82
82
|
contextDir: resolved.contextDir,
|
|
83
83
|
productContextDir: productPath ? path.dirname(productPath) : null,
|
|
84
84
|
designContextDir: designPath ? path.dirname(designPath) : null,
|
|
@@ -186,7 +186,7 @@ function resolveProject(cwd = process.cwd(), options = {}) {
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function isPathInside(candidate, root) {
|
|
189
|
-
const rel = path.relative(root, candidate);
|
|
189
|
+
const rel = path.relative(root, candidate).split(path.sep).join('/');
|
|
190
190
|
return !!rel && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -339,9 +339,9 @@ function contextSourceStatus(filePath, repoRoot, projectRoot) {
|
|
|
339
339
|
|
|
340
340
|
function contextSourcePath(filePath, repoRoot) {
|
|
341
341
|
if (!filePath) return null;
|
|
342
|
-
const rel = path.relative(repoRoot, filePath);
|
|
342
|
+
const rel = path.relative(repoRoot, filePath).split(path.sep).join('/');
|
|
343
343
|
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
|
|
344
|
-
return rel
|
|
344
|
+
return rel;
|
|
345
345
|
}
|
|
346
346
|
return filePath;
|
|
347
347
|
}
|
|
@@ -447,9 +447,13 @@ function findTargetExample(repoRoot, projectRoot) {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
function resolveWorkspaceProjectRoot(repoRoot, targetDir) {
|
|
450
|
-
const rel = path.relative(repoRoot, targetDir);
|
|
450
|
+
const rel = path.relative(repoRoot, targetDir).split(path.sep).join('/');
|
|
451
451
|
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return repoRoot;
|
|
452
|
-
|
|
452
|
+
// rel is POSIX-normalised above, so split on '/' — splitting on path.sep here
|
|
453
|
+
// returned the whole string as a single segment on Windows, no workspace
|
|
454
|
+
// pattern ever matched, and every child project silently resolved to the
|
|
455
|
+
// repo root instead.
|
|
456
|
+
const relSegments = rel.split('/').filter(Boolean);
|
|
453
457
|
const patterns = readWorkspacePatterns(repoRoot);
|
|
454
458
|
const excluded = isExcludedByWorkspacePattern(relSegments, patterns);
|
|
455
459
|
if (!excluded) {
|
|
@@ -64,7 +64,7 @@ export function slugFromTarget(resolved, { cwd = process.cwd() } = {}) {
|
|
|
64
64
|
// File path. Make it project-relative so two devs critiquing the same
|
|
65
65
|
// checkout get the same slug regardless of where their repo is cloned.
|
|
66
66
|
const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
|
|
67
|
-
let rel = path.relative(cwd, abs);
|
|
67
|
+
let rel = path.relative(cwd, abs).split(path.sep).join('/');
|
|
68
68
|
// If the target is outside cwd, fall back to the basename so we still
|
|
69
69
|
// produce a stable slug (vs the absolute path, which would include
|
|
70
70
|
// home dirs / usernames).
|
|
@@ -231,7 +231,7 @@ export function mirrorToMemory({ slug, meta = {}, filePath, cwd = process.cwd(),
|
|
|
231
231
|
p1: asFiniteNumber(meta.p1_count ?? meta.p1),
|
|
232
232
|
date: now.toISOString(),
|
|
233
233
|
slug,
|
|
234
|
-
path: filePath ? path.relative(cwd, filePath) : null,
|
|
234
|
+
path: filePath ? path.relative(cwd, filePath).split(path.sep).join('/') : null,
|
|
235
235
|
};
|
|
236
236
|
const project = kebab(path.basename(path.resolve(cwd))) || 'project';
|
|
237
237
|
const key = `${project}-${slug}`;
|
|
@@ -340,7 +340,7 @@ export function formatRecall(recall, { cwd = process.cwd() } = {}) {
|
|
|
340
340
|
if (!issues.p0.length && !issues.p1.length) {
|
|
341
341
|
lines.push('- No open P0/P1 lines found in the latest snapshot.');
|
|
342
342
|
}
|
|
343
|
-
lines.push(`- Snapshot: ${path.relative(cwd, latest.path)}`);
|
|
343
|
+
lines.push(`- Snapshot: ${path.relative(cwd, latest.path).split(path.sep).join('/')}`);
|
|
344
344
|
return lines.join('\n');
|
|
345
345
|
}
|
|
346
346
|
|
|
@@ -186,7 +186,7 @@ function walk(root, dir, depth, visit) {
|
|
|
186
186
|
body = buf.slice(0, n).toString('utf-8');
|
|
187
187
|
} finally { fs.closeSync(fd); }
|
|
188
188
|
} catch { continue; }
|
|
189
|
-
visit(abs, path.relative(root, abs), body);
|
|
189
|
+
visit(abs, path.relative(root, abs).split(path.sep).join('/'), body);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -257,7 +257,7 @@ async function runFix(targets, options = {}) {
|
|
|
257
257
|
const diffs = [];
|
|
258
258
|
if (dryRun) {
|
|
259
259
|
for (const [p, b] of changedFiles) {
|
|
260
|
-
diffs.push({ file: p, diff: unifiedDiff(b.original, b.content, path.relative(cwd, p) || p) });
|
|
260
|
+
diffs.push({ file: p, diff: unifiedDiff(b.original, b.content, path.relative(cwd, p).split(path.sep).join('/') || p) });
|
|
261
261
|
}
|
|
262
262
|
} else {
|
|
263
263
|
for (const [p, b] of changedFiles) writeFileAtomic(p, b.content);
|
|
@@ -275,9 +275,9 @@ function statusReport(cwd) {
|
|
|
275
275
|
const cfg = readConfig(cwd);
|
|
276
276
|
const envKill = process.env.MONODESIGN_HOOK_DISABLED;
|
|
277
277
|
const envState = envKill ? `MONODESIGN_HOOK_DISABLED=${envKill}` : 'unset';
|
|
278
|
-
const cfgPath = path.relative(cwd, getConfigPath(cwd)) || '.monodesign/config.json';
|
|
279
|
-
const localPath = path.relative(cwd, getLocalConfigPath(cwd)) || '.monodesign/config.local.json';
|
|
280
|
-
const cachePath = path.relative(cwd, getCachePath(cwd)) || '.monodesign/hook.cache.json';
|
|
278
|
+
const cfgPath = path.relative(cwd, getConfigPath(cwd)).split(path.sep).join('/') || '.monodesign/config.json';
|
|
279
|
+
const localPath = path.relative(cwd, getLocalConfigPath(cwd)).split(path.sep).join('/') || '.monodesign/config.local.json';
|
|
280
|
+
const cachePath = path.relative(cwd, getCachePath(cwd)).split(path.sep).join('/') || '.monodesign/hook.cache.json';
|
|
281
281
|
const fileState = (info, relPath, absent) => {
|
|
282
282
|
if (info.malformed) return `${relPath} (malformed; ignored)`;
|
|
283
283
|
if (info.exists) return relPath;
|
|
@@ -306,14 +306,14 @@ function setEnabled(cwd, value) {
|
|
|
306
306
|
config.enabled = value;
|
|
307
307
|
const target = writeHookConfig(cwd, config);
|
|
308
308
|
if (!value) {
|
|
309
|
-
return `Design hook disabled for this project (wrote ${path.relative(cwd, target) || target}).`;
|
|
309
|
+
return `Design hook disabled for this project (wrote ${path.relative(cwd, target).split(path.sep).join('/') || target}).`;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
const localTarget = writeHookConfig(cwd, { consent: 'accepted' }, { local: true });
|
|
313
313
|
const repaired = repairHookManifests(cwd);
|
|
314
314
|
const parts = [
|
|
315
|
-
`Design hook enabled for this project (wrote ${path.relative(cwd, target) || target}).`,
|
|
316
|
-
`Recorded local hook consent in ${path.relative(cwd, localTarget) || localTarget}.`,
|
|
315
|
+
`Design hook enabled for this project (wrote ${path.relative(cwd, target).split(path.sep).join('/') || target}).`,
|
|
316
|
+
`Recorded local hook consent in ${path.relative(cwd, localTarget).split(path.sep).join('/') || localTarget}.`,
|
|
317
317
|
];
|
|
318
318
|
if (repaired.written.length > 0) {
|
|
319
319
|
parts.push(`Installed or repaired hook manifests for: ${repaired.written.join(', ')}.`);
|
|
@@ -323,7 +323,7 @@ function setEnabled(cwd, value) {
|
|
|
323
323
|
parts.push('No installed provider skill folders found to repair.');
|
|
324
324
|
}
|
|
325
325
|
if (repaired.backups.length > 0) {
|
|
326
|
-
parts.push(`Backed up malformed manifest(s): ${repaired.backups.map((filePath) => path.relative(cwd, filePath) || filePath).join(', ')}.`);
|
|
326
|
+
parts.push(`Backed up malformed manifest(s): ${repaired.backups.map((filePath) => path.relative(cwd, filePath).split(path.sep).join('/') || filePath).join(', ')}.`);
|
|
327
327
|
}
|
|
328
328
|
return parts.join(' ');
|
|
329
329
|
}
|
|
@@ -596,7 +596,7 @@ function addIgnoreValue(cwd, args) {
|
|
|
596
596
|
|
|
597
597
|
const target = writeDetectorConfig(cwd, config, { local });
|
|
598
598
|
const scope = local ? 'local detector.ignoreValues' : 'shared detector.ignoreValues';
|
|
599
|
-
return `Added ${parsed.rule}=${parsed.value} to ${scope} (${path.relative(cwd, target) || target}).`;
|
|
599
|
+
return `Added ${parsed.rule}=${parsed.value} to ${scope} (${path.relative(cwd, target).split(path.sep).join('/') || target}).`;
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
function reset(cwd) {
|
|
@@ -613,7 +613,7 @@ function reset(cwd) {
|
|
|
613
613
|
} else {
|
|
614
614
|
fs.writeFileSync(filePath, JSON.stringify(rest, null, 2) + '\n');
|
|
615
615
|
}
|
|
616
|
-
removed.push(path.relative(cwd, filePath) || filePath);
|
|
616
|
+
removed.push(path.relative(cwd, filePath).split(path.sep).join('/') || filePath);
|
|
617
617
|
} catch { /* ignore */ }
|
|
618
618
|
}
|
|
619
619
|
// State files are wholly ours; delete outright.
|
|
@@ -621,7 +621,7 @@ function reset(cwd) {
|
|
|
621
621
|
try {
|
|
622
622
|
if (fs.existsSync(filePath)) {
|
|
623
623
|
fs.unlinkSync(filePath);
|
|
624
|
-
removed.push(path.relative(cwd, filePath) || filePath);
|
|
624
|
+
removed.push(path.relative(cwd, filePath).split(path.sep).join('/') || filePath);
|
|
625
625
|
}
|
|
626
626
|
} catch { /* ignore */ }
|
|
627
627
|
}
|
|
@@ -320,7 +320,7 @@ function escapeRegExp(value) {
|
|
|
320
320
|
|
|
321
321
|
function relativePath(filePath, cwd) {
|
|
322
322
|
try {
|
|
323
|
-
const rel = path.relative(cwd, filePath);
|
|
323
|
+
const rel = path.relative(cwd, filePath).split(path.sep).join('/');
|
|
324
324
|
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return filePath;
|
|
325
325
|
return rel.split(path.sep).join('/');
|
|
326
326
|
} catch {
|
|
@@ -330,7 +330,7 @@ function relativePath(filePath, cwd) {
|
|
|
330
330
|
|
|
331
331
|
function isInsideProject(filePath, cwd) {
|
|
332
332
|
try {
|
|
333
|
-
const rel = path.relative(cwd, filePath);
|
|
333
|
+
const rel = path.relative(cwd, filePath).split(path.sep).join('/');
|
|
334
334
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
335
335
|
} catch {
|
|
336
336
|
return false;
|
|
@@ -1015,7 +1015,7 @@ function quoteCommandArg(value) {
|
|
|
1015
1015
|
|
|
1016
1016
|
function relativize(filePath, cwd) {
|
|
1017
1017
|
try {
|
|
1018
|
-
const rel = path.relative(cwd, filePath);
|
|
1018
|
+
const rel = path.relative(cwd, filePath).split(path.sep).join('/');
|
|
1019
1019
|
if (!rel || rel.startsWith('..')) return filePath;
|
|
1020
1020
|
return rel.split(path.sep).join('/');
|
|
1021
1021
|
} catch {
|
|
@@ -1226,7 +1226,7 @@ function hasPathTraversal(filePath) {
|
|
|
1226
1226
|
function isInsideProject(filePath, projectCwd) {
|
|
1227
1227
|
if (!filePath || !projectCwd || hasPathTraversal(filePath)) return false;
|
|
1228
1228
|
try {
|
|
1229
|
-
const rel = path.relative(projectCwd, filePath);
|
|
1229
|
+
const rel = path.relative(projectCwd, filePath).split(path.sep).join('/');
|
|
1230
1230
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
1231
1231
|
} catch {
|
|
1232
1232
|
return false;
|
|
@@ -338,7 +338,7 @@ export function normalizeManualApplyEvidencePath(evidencePath, cwd = process.cwd
|
|
|
338
338
|
if (!evidencePath || typeof evidencePath !== 'string') return null;
|
|
339
339
|
const fullPath = path.isAbsolute(evidencePath) ? evidencePath : path.resolve(cwd, evidencePath);
|
|
340
340
|
const evidenceDir = manualApplyEvidenceDir(cwd);
|
|
341
|
-
const relative = path.relative(evidenceDir, fullPath);
|
|
341
|
+
const relative = path.relative(evidenceDir, fullPath).split(path.sep).join('/');
|
|
342
342
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
343
343
|
if (path.extname(relative) !== '.json') return null;
|
|
344
344
|
return fullPath;
|
|
@@ -837,7 +837,7 @@ export function collectManualApplyFiles(batch, extraFiles = [], cwd = process.cw
|
|
|
837
837
|
function normalizeProjectFile(file, cwd = process.cwd()) {
|
|
838
838
|
if (!file || typeof file !== 'string') return null;
|
|
839
839
|
const absolute = path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
840
|
-
const relative = path.relative(cwd, absolute);
|
|
840
|
+
const relative = path.relative(cwd, absolute).split(path.sep).join('/');
|
|
841
841
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
842
842
|
return relative;
|
|
843
843
|
}
|
|
@@ -927,7 +927,7 @@ export function summarizeManualDiagnostics(items, cwd = process.cwd()) {
|
|
|
927
927
|
export function summarizeManualLogFile(file, cwd = process.cwd()) {
|
|
928
928
|
if (!file || typeof file !== 'string') return undefined;
|
|
929
929
|
if (!path.isAbsolute(file)) return file;
|
|
930
|
-
const relative = path.relative(cwd, file);
|
|
930
|
+
const relative = path.relative(cwd, file).split(path.sep).join('/');
|
|
931
931
|
return relative && !relative.startsWith('..') && !path.isAbsolute(relative) ? relative : file;
|
|
932
932
|
}
|
|
933
933
|
|
|
@@ -265,7 +265,7 @@ export function resolveSourceFile(sourceFile, cwd = process.cwd()) {
|
|
|
265
265
|
throw new Error('Invalid svelte-component source file');
|
|
266
266
|
}
|
|
267
267
|
const full = path.resolve(cwd, sourceFile);
|
|
268
|
-
const rel = path.relative(cwd, full);
|
|
268
|
+
const rel = path.relative(cwd, full).split(path.sep).join('/');
|
|
269
269
|
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
270
270
|
throw new Error('Svelte-component source file escapes project root');
|
|
271
271
|
}
|
|
@@ -119,7 +119,7 @@ Output (JSON):
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const { file: targetFile, content, lines } = found;
|
|
122
|
-
const relFile = path.relative(process.cwd(), targetFile);
|
|
122
|
+
const relFile = path.relative(process.cwd(), targetFile).split(path.sep).join('/');
|
|
123
123
|
const previewBlock = findMarkerBlock(id, lines);
|
|
124
124
|
const sourceShadowPreview = previewBlock
|
|
125
125
|
? readSourceShadowPreviewMeta(content, id)
|
package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-commit-manual-edits.mjs
CHANGED
|
@@ -224,7 +224,7 @@ function buildRepairBatch(batch, repair) {
|
|
|
224
224
|
function normalizeProjectSourcePath(cwd, file, opts = {}) {
|
|
225
225
|
if (!file || typeof file !== 'string') return null;
|
|
226
226
|
const absolute = path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
227
|
-
const relative = path.relative(cwd, absolute);
|
|
227
|
+
const relative = path.relative(cwd, absolute).split(path.sep).join('/');
|
|
228
228
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
229
229
|
if (opts.requireExists && !fs.existsSync(absolute)) return null;
|
|
230
230
|
if (isGeneratedFile(absolute, { cwd })) return null;
|
|
@@ -622,7 +622,7 @@ function scanRollbackDir(dir, cwd, out, seenDirs, seenFiles, depth) {
|
|
|
622
622
|
try { realFile = fs.realpathSync(absolute); } catch { continue; }
|
|
623
623
|
if (seenFiles.has(realFile)) continue;
|
|
624
624
|
seenFiles.add(realFile);
|
|
625
|
-
const relative = path.relative(cwd, absolute);
|
|
625
|
+
const relative = path.relative(cwd, absolute).split(path.sep).join('/');
|
|
626
626
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) continue;
|
|
627
627
|
out.push(relative);
|
|
628
628
|
}
|
|
@@ -534,7 +534,7 @@ function runAgentProcess(command, args, stdin, { cwd, env, logPath, timeoutMs, m
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
function isPathInsideOrEqual(cwd, file) {
|
|
537
|
-
const relative = path.relative(path.resolve(cwd), path.resolve(file));
|
|
537
|
+
const relative = path.relative(path.resolve(cwd), path.resolve(file)).split(path.sep).join('/');
|
|
538
538
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
539
539
|
}
|
|
540
540
|
|
|
@@ -166,7 +166,7 @@ Output (JSON):
|
|
|
166
166
|
console.error(JSON.stringify({
|
|
167
167
|
error: 'file_is_generated',
|
|
168
168
|
fallback: 'agent-driven',
|
|
169
|
-
file: path.relative(process.cwd(), path.resolve(process.cwd(), targetFile)),
|
|
169
|
+
file: path.relative(process.cwd(), path.resolve(process.cwd(), targetFile)).split(path.sep).join('/'),
|
|
170
170
|
}));
|
|
171
171
|
process.exit(1);
|
|
172
172
|
}
|
|
@@ -179,7 +179,7 @@ Output (JSON):
|
|
|
179
179
|
console.error(JSON.stringify({
|
|
180
180
|
error: 'element_ambiguous',
|
|
181
181
|
fallback: 'agent-driven',
|
|
182
|
-
file: path.relative(process.cwd(), targetFile),
|
|
182
|
+
file: path.relative(process.cwd(), targetFile).split(path.sep).join('/'),
|
|
183
183
|
candidates: resolved.candidates.map((c) => ({
|
|
184
184
|
startLine: c.startLine + 1,
|
|
185
185
|
endLine: c.endLine + 1,
|
package/packages/@monomind/cli/.claude/skills/monodesign/scripts/live-manual-edit-evidence.mjs
CHANGED
|
@@ -64,7 +64,7 @@ export function buildManualEditEvidence({ cwd = process.cwd(), pageUrl = null }
|
|
|
64
64
|
ops,
|
|
65
65
|
context: {
|
|
66
66
|
cwd,
|
|
67
|
-
bufferPath: path.relative(cwd, getBufferPath(cwd)),
|
|
67
|
+
bufferPath: path.relative(cwd, getBufferPath(cwd)).split(path.sep).join('/'),
|
|
68
68
|
totalEntries: entries.length,
|
|
69
69
|
totalOps: opCount,
|
|
70
70
|
},
|
|
@@ -157,7 +157,7 @@ function analyzeSourceHint(op, cwd) {
|
|
|
157
157
|
const hint = normalizeSourceHint(op.sourceHint);
|
|
158
158
|
if (!hint.file) return null;
|
|
159
159
|
const file = path.resolve(cwd, hint.file);
|
|
160
|
-
const relativeFile = path.relative(cwd, file);
|
|
160
|
+
const relativeFile = path.relative(cwd, file).split(path.sep).join('/');
|
|
161
161
|
if (!isPathInsideOrEqual(cwd, file)) {
|
|
162
162
|
return { ...hint, status: 'outside_cwd', relativeFile: hint.file };
|
|
163
163
|
}
|
|
@@ -254,7 +254,7 @@ function maybeAddSearchFile(file, cwd, seenFiles, out) {
|
|
|
254
254
|
if (isGeneratedFile(file, { cwd })) return;
|
|
255
255
|
let content;
|
|
256
256
|
try { content = fs.readFileSync(file, 'utf-8'); } catch { return; }
|
|
257
|
-
out.push({ file, relativeFile: path.relative(cwd, file), content, lines: content.split('\n') });
|
|
257
|
+
out.push({ file, relativeFile: path.relative(cwd, file).split(path.sep).join('/'), content, lines: content.split('\n') });
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
function findLiteralMatches(searchFiles, needle, { max }) {
|
|
@@ -340,7 +340,7 @@ function matchForIndex(file, index, kind, needle) {
|
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
function isPathInsideOrEqual(cwd, file) {
|
|
343
|
-
const rel = path.relative(path.resolve(cwd), path.resolve(file));
|
|
343
|
+
const rel = path.relative(path.resolve(cwd), path.resolve(file)).split(path.sep).join('/');
|
|
344
344
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
345
345
|
}
|
|
346
346
|
|
|
@@ -789,7 +789,7 @@ function sessionFileMetadataFromPollReply(file) {
|
|
|
789
789
|
let full;
|
|
790
790
|
try {
|
|
791
791
|
full = path.resolve(process.cwd(), normalized);
|
|
792
|
-
const rel = path.relative(process.cwd(), full);
|
|
792
|
+
const rel = path.relative(process.cwd(), full).split(path.sep).join('/');
|
|
793
793
|
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return base;
|
|
794
794
|
} catch {
|
|
795
795
|
return base;
|
|
@@ -102,7 +102,7 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
102
102
|
console.error(JSON.stringify({
|
|
103
103
|
error: 'element_not_in_source',
|
|
104
104
|
fallback: 'agent-driven',
|
|
105
|
-
generatedMatch: path.relative(process.cwd(), generatedHit),
|
|
105
|
+
generatedMatch: path.relative(process.cwd(), generatedHit).split(path.sep).join('/'),
|
|
106
106
|
hint: 'Element found only in a generated file. See "Handle fallback" in live.md.',
|
|
107
107
|
}));
|
|
108
108
|
} else {
|
|
@@ -119,7 +119,7 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
119
119
|
console.error(JSON.stringify({
|
|
120
120
|
error: 'file_is_generated',
|
|
121
121
|
fallback: 'agent-driven',
|
|
122
|
-
file: path.relative(process.cwd(), path.resolve(process.cwd(), targetFile)),
|
|
122
|
+
file: path.relative(process.cwd(), path.resolve(process.cwd(), targetFile)).split(path.sep).join('/'),
|
|
123
123
|
hint: 'Explicit --file points at a generated file. Writing here gets wiped by the next build. See "Handle fallback" in live.md.',
|
|
124
124
|
}));
|
|
125
125
|
process.exit(1);
|
|
@@ -172,7 +172,7 @@ The agent should insert variant HTML at insertLine.`);
|
|
|
172
172
|
console.error(JSON.stringify({
|
|
173
173
|
error: 'element_ambiguous',
|
|
174
174
|
fallback: 'agent-driven',
|
|
175
|
-
file: path.relative(process.cwd(), targetFile),
|
|
175
|
+
file: path.relative(process.cwd(), targetFile).split(path.sep).join('/'),
|
|
176
176
|
candidates: filtered.map((c) => ({
|
|
177
177
|
startLine: c.startLine + 1,
|
|
178
178
|
endLine: c.endLine + 1,
|
|
@@ -264,13 +264,72 @@ if (isMCPMode) {
|
|
|
264
264
|
}
|
|
265
265
|
process.exit(1);
|
|
266
266
|
};
|
|
267
|
+
// Not every uncaught error is a bug in monomind, and filing a PUBLIC GitHub
|
|
268
|
+
// issue for one that isn't is worse than useless — it leaks the user's paths
|
|
269
|
+
// into a tracker and buries real crashes in noise. Two classes are user
|
|
270
|
+
// environment or normal usage, never a product defect:
|
|
271
|
+
//
|
|
272
|
+
// EPIPE / ERR_STREAM_DESTROYED — the reader closed the pipe first. This is
|
|
273
|
+
// what `monomind hooks worker list | head` does every single time, and
|
|
274
|
+
// what `| less` does when you quit early. Completely normal (issue #41).
|
|
275
|
+
//
|
|
276
|
+
// ERR_MODULE_NOT_FOUND — a dependency is missing from the install: a
|
|
277
|
+
// partial/corrupt node_modules, or the CLI being run straight out of an
|
|
278
|
+
// extracted tarball. The user needs an actionable message, not a bug
|
|
279
|
+
// report filed on their behalf (issues #46, #47).
|
|
280
|
+
//
|
|
281
|
+
// Anything else still reports as before.
|
|
282
|
+
const classifyFault = (err) => {
|
|
283
|
+
const code = err && err.code;
|
|
284
|
+
if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED') return 'broken-pipe';
|
|
285
|
+
if (code === 'ERR_MODULE_NOT_FOUND') return 'missing-dependency';
|
|
286
|
+
return 'crash';
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** Handles the non-bug classes. Returns true if it fully handled the error. */
|
|
290
|
+
const handleExpectedFault = (err) => {
|
|
291
|
+
switch (classifyFault(err)) {
|
|
292
|
+
case 'broken-pipe':
|
|
293
|
+
// Downstream went away — there is nothing left to say and nowhere to
|
|
294
|
+
// say it. Exiting 0 keeps `monomind ... | head` from looking failed.
|
|
295
|
+
process.exit(0);
|
|
296
|
+
return true;
|
|
297
|
+
case 'missing-dependency': {
|
|
298
|
+
const pkg = /Cannot find package '([^']+)'/.exec(safeMsg(err && err.message))?.[1];
|
|
299
|
+
console.error(
|
|
300
|
+
pkg
|
|
301
|
+
? `[monomind] Missing dependency: ${pkg}\n` +
|
|
302
|
+
` This is an install problem, not a crash — nothing was reported.\n` +
|
|
303
|
+
` Try: npm install ${pkg} (or reinstall monomind: npm i -g monomind@latest)`
|
|
304
|
+
: `[monomind] A dependency could not be resolved: ${safeMsg(err && err.message)}\n` +
|
|
305
|
+
` This is an install problem, not a crash — nothing was reported.`,
|
|
306
|
+
);
|
|
307
|
+
if (process.env.DEBUG) console.error(err && err.stack);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
default:
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Belt and braces for the pipe case: handling 'error' on the streams keeps a
|
|
317
|
+
// mid-write EPIPE from becoming an uncaughtException at all.
|
|
318
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
319
|
+
stream.on('error', (err) => {
|
|
320
|
+
if (classifyFault(err) === 'broken-pipe') process.exit(0);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
267
324
|
process.on('uncaughtException', (err) => {
|
|
325
|
+
if (handleExpectedFault(err)) return;
|
|
268
326
|
console.error(`[${new Date().toISOString()}] FATAL [monomind] uncaughtException: ${safeMsg(err && err.message)}`);
|
|
269
327
|
if (process.env.DEBUG) console.error(err && err.stack);
|
|
270
328
|
reportAndExit(safeMsg(err && err.message) || 'uncaughtException', err && err.stack);
|
|
271
329
|
return;
|
|
272
330
|
});
|
|
273
331
|
process.on('unhandledRejection', (reason) => {
|
|
332
|
+
if (reason instanceof Error && handleExpectedFault(reason)) return;
|
|
274
333
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
275
334
|
console.error(`[${new Date().toISOString()}] FATAL [monomind] unhandledRejection: ${safeMsg(msg)}`);
|
|
276
335
|
if (process.env.DEBUG && reason instanceof Error) console.error(reason.stack);
|
|
@@ -300,6 +300,49 @@ const serveAction = async (ctx) => {
|
|
|
300
300
|
};
|
|
301
301
|
process.on('uncaughtException', (err) => { crashExit('uncaughtException', err); process.exit(1); });
|
|
302
302
|
process.on('unhandledRejection', (err) => { crashExit('unhandledRejection', err); process.exit(1); });
|
|
303
|
+
// Termination diagnostics (#45). The two handlers above only cover errors
|
|
304
|
+
// raised *inside* the daemon. A report of the daemon vanishing after hours
|
|
305
|
+
// had a log holding nothing but its startup lines, because the ways a daemon
|
|
306
|
+
// usually dies were all unhandled:
|
|
307
|
+
//
|
|
308
|
+
// - a signal (SIGTERM from a supervisor/OS, SIGHUP when a terminal closes)
|
|
309
|
+
// - the event loop simply draining, which exits 0 and says nothing at all
|
|
310
|
+
//
|
|
311
|
+
// Both now announce themselves. Note what this deliberately cannot cover:
|
|
312
|
+
// SIGKILL, which is what the OOM killer sends, is uncatchable by design — no
|
|
313
|
+
// in-process handler can ever log it. That case is instead made *inferable*:
|
|
314
|
+
// every shutdown path below prints a terminal line, so a log that starts and
|
|
315
|
+
// then stops with no such line means the process was killed from outside
|
|
316
|
+
// (OOM being the usual culprit, and the reporter's org logs did show memory
|
|
317
|
+
// pressure). Absence of a shutdown line is now evidence, not ambiguity.
|
|
318
|
+
let shuttingDown = false;
|
|
319
|
+
const announceExit = (reason) => {
|
|
320
|
+
if (shuttingDown)
|
|
321
|
+
return;
|
|
322
|
+
shuttingDown = true;
|
|
323
|
+
try {
|
|
324
|
+
console.error(`[org serve] shutting down: ${reason}`);
|
|
325
|
+
}
|
|
326
|
+
catch { /* stderr gone */ }
|
|
327
|
+
};
|
|
328
|
+
for (const sig of ['SIGTERM', 'SIGINT', 'SIGHUP']) {
|
|
329
|
+
process.on(sig, () => {
|
|
330
|
+
announceExit(`received ${sig}`);
|
|
331
|
+
try {
|
|
332
|
+
daemon.persistCrashStateAll();
|
|
333
|
+
daemon.clearHeartbeat();
|
|
334
|
+
}
|
|
335
|
+
catch { /* best effort */ }
|
|
336
|
+
// A daemon holds ref'd timers, so it will not drain on its own; an
|
|
337
|
+
// explicit exit is required here and is the intended signal semantics.
|
|
338
|
+
process.exit(sig === 'SIGTERM' || sig === 'SIGINT' ? 0 : 1);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
process.on('exit', (code) => {
|
|
342
|
+
// Last word on the way out. Reached for the "event loop drained" case,
|
|
343
|
+
// which previously produced a completely silent disappearance.
|
|
344
|
+
announceExit(`process exiting with code ${code}`);
|
|
345
|
+
});
|
|
303
346
|
// Heartbeat: write every 30s so `org status` can tell "alive but busy" from
|
|
304
347
|
// "daemon gone" without relying on pid liveness alone.
|
|
305
348
|
daemon.writeHeartbeat();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI engine for Monomind \u2014 an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -81,13 +81,13 @@
|
|
|
81
81
|
"README.md"
|
|
82
82
|
],
|
|
83
83
|
"scripts": {
|
|
84
|
-
"prebuild": "
|
|
85
|
-
"build": "tsc &&
|
|
86
|
-
"build:loose": "tsc --noEmitOnError false || true &&
|
|
84
|
+
"prebuild": "node ../../../scripts/build-fs.mjs clean dist tsconfig.tsbuildinfo",
|
|
85
|
+
"build": "tsc && node ../../../scripts/build-fs.mjs copy-into dist/src/browser/dashboard src/browser/dashboard/ui.html && node ../../../scripts/build-fs.mjs copy-into dist/src/ui src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs src/ui/mastermind-diagram-fallback.html && node ../../../scripts/build-fs.mjs clean dist/src/ui/data && node ../../../scripts/build-fs.mjs copy-dir src/ui/data dist/src/ui/data",
|
|
86
|
+
"build:loose": "tsc --noEmitOnError false || true && node ../../../scripts/build-fs.mjs copy-into dist/src/browser/dashboard src/browser/dashboard/ui.html && node ../../../scripts/build-fs.mjs copy-into dist/src/ui src/ui/dashboard.html src/ui/server.mjs src/ui/collector.mjs src/ui/orgs.html src/ui/orgs-files.js src/ui/sse-manager.mjs src/ui/mastermind-diagram-fallback.html && node ../../../scripts/build-fs.mjs clean dist/src/ui/data && node ../../../scripts/build-fs.mjs copy-dir src/ui/data dist/src/ui/data",
|
|
87
87
|
"test": "vitest run",
|
|
88
88
|
"test:coverage": "vitest run --coverage",
|
|
89
89
|
"test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
|
|
90
|
-
"prepublishOnly": "node ../../@monoes/monodesign/scripts/sync-skill.mjs &&
|
|
90
|
+
"prepublishOnly": "node ../../@monoes/monodesign/scripts/sync-skill.mjs && node ../../../scripts/build-fs.mjs copy-into . ../../../README.md && npm run build",
|
|
91
91
|
"release": "npm version prerelease --preid=alpha && npm run publish:all",
|
|
92
92
|
"publish:all": "./scripts/publish.sh"
|
|
93
93
|
},
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"dependencies": {
|
|
100
100
|
"@anthropic-ai/claude-agent-sdk": "^0.3.207",
|
|
101
101
|
"@monoes/monobrowse": "^1.0.6",
|
|
102
|
-
"@monoes/monodesign": "^1.2.
|
|
102
|
+
"@monoes/monodesign": "^1.2.2",
|
|
103
103
|
"@monoes/monograph": "^1.5.4",
|
|
104
104
|
"@noble/ed25519": "^2.1.0",
|
|
105
105
|
"mammoth": "^1.12.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cross-platform filesystem primitives for npm build scripts.
|
|
4
|
+
*
|
|
5
|
+
* Every package's build used POSIX shell directly — recursive delete, mkdir -p,
|
|
6
|
+
* cp -r. npm runs scripts through cmd.exe on Windows, which does not understand
|
|
7
|
+
* any of them and answers "The syntax of the command is incorrect". Seven of
|
|
8
|
+
* eight packages had this shape, so monomind could not be BUILT on Windows at
|
|
9
|
+
* all. That does not affect users installing from npm (they get prebuilt dist
|
|
10
|
+
* output), but it locks out Windows contributors entirely.
|
|
11
|
+
*
|
|
12
|
+
* Node's own fs covers all of it, so this adds no dependency — rimraf, shx and
|
|
13
|
+
* friends would each be a new supply-chain edge for something the stdlib
|
|
14
|
+
* already does. Node >= 20 is the floor, where rmSync and cpSync are both
|
|
15
|
+
* available.
|
|
16
|
+
*
|
|
17
|
+
* Subcommands are deliberately explicit rather than mirroring `cp`'s
|
|
18
|
+
* file-vs-directory inference, so a caller can never get directory semantics by
|
|
19
|
+
* accident:
|
|
20
|
+
*
|
|
21
|
+
* clean <path...> recursive delete, no error if absent
|
|
22
|
+
* copy-into <destDir> <src...> copy each src INTO destDir, keeping basenames
|
|
23
|
+
* copy-dir <src> <dest> recursive directory copy
|
|
24
|
+
*/
|
|
25
|
+
import { rmSync, mkdirSync, cpSync, existsSync, statSync } from 'node:fs';
|
|
26
|
+
import { basename, join } from 'node:path';
|
|
27
|
+
|
|
28
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
29
|
+
|
|
30
|
+
const fail = (msg) => {
|
|
31
|
+
console.error(`build-fs: ${msg}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
switch (cmd) {
|
|
36
|
+
case 'clean': {
|
|
37
|
+
if (args.length === 0) fail('clean needs at least one path');
|
|
38
|
+
for (const target of args) {
|
|
39
|
+
rmSync(target, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case 'copy-into': {
|
|
45
|
+
const [destDir, ...sources] = args;
|
|
46
|
+
if (!destDir || sources.length === 0) {
|
|
47
|
+
fail('copy-into needs a destination directory and at least one source');
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(destDir, { recursive: true });
|
|
50
|
+
for (const src of sources) {
|
|
51
|
+
// Fail loudly on a missing source. The shell `cp` this replaces would
|
|
52
|
+
// also error, and a build that silently omits a file produces a dist
|
|
53
|
+
// that looks fine until something 404s at runtime.
|
|
54
|
+
if (!existsSync(src)) fail(`source does not exist: ${src}`);
|
|
55
|
+
cpSync(src, join(destDir, basename(src)), {
|
|
56
|
+
recursive: statSync(src).isDirectory(),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case 'copy-dir': {
|
|
63
|
+
const [src, dest] = args;
|
|
64
|
+
if (!src || !dest) fail('copy-dir needs a source and a destination');
|
|
65
|
+
if (!existsSync(src)) fail(`source does not exist: ${src}`);
|
|
66
|
+
cpSync(src, dest, { recursive: true });
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
fail(
|
|
72
|
+
`unknown subcommand ${cmd ? `"${cmd}"` : '(none given)'} — ` +
|
|
73
|
+
'expected clean, copy-into, or copy-dir',
|
|
74
|
+
);
|
|
75
|
+
}
|