monomind 2.1.5 → 2.1.7
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 +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +89 -16
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +36 -9
- package/packages/@monomind/cli/dist/src/commands/doctor.js +10 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +2 -1
- package/packages/@monomind/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.7",
|
|
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",
|
|
@@ -45,9 +45,13 @@ module.exports = {
|
|
|
45
45
|
}
|
|
46
46
|
} catch (e) { /* @monomind/hooks not available or not built — skip */ }
|
|
47
47
|
|
|
48
|
-
// Stale helper
|
|
49
|
-
//
|
|
50
|
-
//
|
|
48
|
+
// Stale helper self-heal — silently refresh project helpers that drift from
|
|
49
|
+
// the bundled npm copy, so a `npm i -g monomind@latest` (or npx picking up a
|
|
50
|
+
// new version) takes effect on the very next session instead of requiring a
|
|
51
|
+
// manual `doctor --fix` / `init upgrade`. Skip when running inside the
|
|
52
|
+
// monomind dev repo itself: local helpers ARE the source of truth there, so
|
|
53
|
+
// any diff vs. the npm global install is expected (and would self-clobber
|
|
54
|
+
// in-progress edits).
|
|
51
55
|
try {
|
|
52
56
|
var _isDevRepo = fs.existsSync(path.join(CWD, 'packages', '@monomind', 'cli', 'package.json'));
|
|
53
57
|
if (!_isDevRepo) {
|
|
@@ -76,26 +80,95 @@ module.exports = {
|
|
|
76
80
|
return null;
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
function _hashFile(p) {
|
|
84
|
+
try { return crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'); }
|
|
85
|
+
catch (_) { return null; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Copy `bundledF` -> `localF` iff their contents differ (or local is missing).
|
|
89
|
+
// Atomic copy-via-rename so a partial write can never leave a broken hook.
|
|
90
|
+
function _healIfStale(localF, bundledF) {
|
|
91
|
+
if (!fs.existsSync(bundledF)) return null;
|
|
92
|
+
var hashB = _hashFile(bundledF);
|
|
93
|
+
var hashL = fs.existsSync(localF) ? _hashFile(localF) : null;
|
|
94
|
+
if (hashL === hashB) return null;
|
|
95
|
+
try {
|
|
96
|
+
var tmp = localF + '.' + process.pid + '.tmp';
|
|
97
|
+
fs.mkdirSync(path.dirname(localF), { recursive: true });
|
|
98
|
+
fs.copyFileSync(bundledF, tmp);
|
|
99
|
+
try { fs.chmodSync(tmp, 0o755); } catch (_) {}
|
|
100
|
+
fs.renameSync(tmp, localF);
|
|
101
|
+
return path.relative(path.join(CWD, '.claude', 'helpers'), localF);
|
|
102
|
+
} catch (_) { return null; }
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
var bundledDir = _findBundledHelpers();
|
|
80
106
|
if (bundledDir) {
|
|
81
|
-
var
|
|
82
|
-
|
|
107
|
+
var healed = [];
|
|
108
|
+
// Top-level critical files — mirrors executor.ts's `criticalHelpers` list.
|
|
109
|
+
var helpersToCheck = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'graphify-freshen.cjs', 'intelligence.cjs', 'auto-memory-hook.mjs'];
|
|
83
110
|
for (var hi = 0; hi < helpersToCheck.length; hi++) {
|
|
84
111
|
var hName = helpersToCheck[hi];
|
|
85
|
-
var
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
var hashB = crypto.createHash('sha256').update(fs.readFileSync(bundledF)).digest('hex');
|
|
91
|
-
if (hashL !== hashB) stale.push(hName);
|
|
92
|
-
} catch (_) {}
|
|
112
|
+
var healedName = _healIfStale(
|
|
113
|
+
path.join(CWD, '.claude', 'helpers', hName),
|
|
114
|
+
path.join(bundledDir, hName)
|
|
115
|
+
);
|
|
116
|
+
if (healedName) healed.push(healedName);
|
|
93
117
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
// handlers/ and utils/ subdirectories — where most real hook-behavior
|
|
119
|
+
// fixes actually live (capture-handler.cjs, gates-handler.cjs, etc.).
|
|
120
|
+
var subdirs = ['handlers', 'utils'];
|
|
121
|
+
for (var si = 0; si < subdirs.length; si++) {
|
|
122
|
+
var bundledSub = path.join(bundledDir, subdirs[si]);
|
|
123
|
+
if (!fs.existsSync(bundledSub)) continue;
|
|
124
|
+
var files;
|
|
125
|
+
try { files = fs.readdirSync(bundledSub).filter(function(f) { return !f.startsWith('._') && !fs.statSync(path.join(bundledSub, f)).isDirectory(); }); }
|
|
126
|
+
catch (_) { files = []; }
|
|
127
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
128
|
+
var healedName2 = _healIfStale(
|
|
129
|
+
path.join(CWD, '.claude', 'helpers', subdirs[si], files[fi]),
|
|
130
|
+
path.join(bundledSub, files[fi])
|
|
131
|
+
);
|
|
132
|
+
if (healedName2) healed.push(healedName2);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (healed.length > 0) {
|
|
136
|
+
console.log('[STALE_HELPERS] Refreshed ' + healed.length + ' helper(s) from bundled version: ' + healed.join(', '));
|
|
97
137
|
}
|
|
98
138
|
}
|
|
139
|
+
|
|
140
|
+
// Fallback for pure `npx monomind@latest ...` usage — the local scan above
|
|
141
|
+
// only finds a bundled copy via node_modules or a global npm install, but a
|
|
142
|
+
// per-invocation `npx` run leaves no reliably-discoverable copy there (each
|
|
143
|
+
// invocation caches under a hashed, non-predictable ~/.npm/_npx/<hash>/ dir
|
|
144
|
+
// that this process has no correct way to pick the newest of without risking
|
|
145
|
+
// healing "backward" to some older cached version). `npx monomind@latest`
|
|
146
|
+
// itself already resolves this correctly (it always fetches/uses latest),
|
|
147
|
+
// and `doctor --fix` reuses the exact same bundled-package resolution that
|
|
148
|
+
// `init upgrade` uses — so shell out to it instead of re-solving the same
|
|
149
|
+
// problem here. Rate-limited to once per 6h (mirrors the metrics-worker
|
|
150
|
+
// staleness gate below) and fully non-blocking: spawned detached+unref with
|
|
151
|
+
// stdio ignored, session start never waits on it, and a fresh session or
|
|
152
|
+
// hook a few seconds later just picks up whatever it left behind.
|
|
153
|
+
try {
|
|
154
|
+
var _healCheckPath = path.join(CWD, '.monomind', 'helpers-heal-check.json');
|
|
155
|
+
var _lastHealCheck = 0;
|
|
156
|
+
try { _lastHealCheck = JSON.parse(fs.readFileSync(_healCheckPath, 'utf-8')).ts || 0; } catch (_) {}
|
|
157
|
+
var HEAL_CHECK_STALE_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
158
|
+
if (Date.now() - _lastHealCheck > HEAL_CHECK_STALE_MS) {
|
|
159
|
+
fs.mkdirSync(path.dirname(_healCheckPath), { recursive: true });
|
|
160
|
+
try { fs.writeFileSync(_healCheckPath, JSON.stringify({ ts: Date.now() }), 'utf-8'); } catch (_) {}
|
|
161
|
+
var _spawn = require('child_process').spawn;
|
|
162
|
+
var _child = _spawn('npx', ['-y', 'monomind@latest', 'doctor', '--fix', '--component', 'helpers'], {
|
|
163
|
+
cwd: CWD,
|
|
164
|
+
detached: true,
|
|
165
|
+
stdio: 'ignore',
|
|
166
|
+
env: process.env,
|
|
167
|
+
});
|
|
168
|
+
_child.on('error', function() {}); // offline / npx unavailable — silently skip
|
|
169
|
+
_child.unref();
|
|
170
|
+
}
|
|
171
|
+
} catch (e) { /* non-fatal — background self-heal is best-effort */ }
|
|
99
172
|
}
|
|
100
173
|
} catch (e) { /* non-fatal */ }
|
|
101
174
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Doctor — project/monomind health checks
|
|
3
3
|
* Config, memory, API keys, MCP, monograph, helpers, routing, gates, gitignore, worker metrics
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync, readFileSync, statSync, mkdirSync, copyFileSync } from 'fs';
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'fs';
|
|
6
6
|
import { join, dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { execSync } from 'child_process';
|
|
@@ -234,21 +234,47 @@ function _resolveBundledHelper(relativePath) {
|
|
|
234
234
|
return null;
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
// Top-level critical helpers, plus every file bundled under handlers/ and utils/
|
|
238
|
+
// (capture-handler.cjs, gates-handler.cjs, route-handler.cjs, session-handler.cjs,
|
|
239
|
+
// etc. — where most actual hook-behavior fixes live). Subdir membership is
|
|
240
|
+
// discovered from the bundled package itself, not hardcoded, so a helper added
|
|
241
|
+
// upstream is picked up automatically instead of silently never syncing.
|
|
242
|
+
function _allTrackedHelperNames() {
|
|
243
|
+
const names = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'graphify-freshen.cjs'];
|
|
244
|
+
for (const sub of ['handlers', 'utils']) {
|
|
245
|
+
const bundledSubDir = _resolveBundledHelper(join('.claude', 'helpers', sub));
|
|
246
|
+
if (!bundledSubDir)
|
|
247
|
+
continue;
|
|
248
|
+
try {
|
|
249
|
+
for (const f of readdirSync(bundledSubDir)) {
|
|
250
|
+
if (f.startsWith('._'))
|
|
251
|
+
continue;
|
|
252
|
+
if (statSync(join(bundledSubDir, f)).isDirectory())
|
|
253
|
+
continue;
|
|
254
|
+
names.push(join(sub, f));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch { /* skip */ }
|
|
258
|
+
}
|
|
259
|
+
return names;
|
|
260
|
+
}
|
|
237
261
|
async function _detectStaleHelpers() {
|
|
238
262
|
const stale = [];
|
|
239
263
|
const missing = [];
|
|
240
|
-
const helpers = ['hook-handler.cjs', 'statusline.cjs', 'router.cjs', 'graphify-freshen.cjs'];
|
|
241
264
|
const crypto = await import('node:crypto');
|
|
242
|
-
for (const name of
|
|
243
|
-
const local = join(process.cwd(), '.claude', 'helpers', name);
|
|
244
|
-
if (!existsSync(local) || statSync(local).size > MAX_DOCTOR_HELPER_BYTES)
|
|
245
|
-
continue;
|
|
265
|
+
for (const name of _allTrackedHelperNames()) {
|
|
246
266
|
const bundled = _resolveBundledHelper(join('.claude', 'helpers', name));
|
|
247
|
-
if (!bundled) {
|
|
248
|
-
|
|
267
|
+
if (!bundled || statSync(bundled).size > MAX_DOCTOR_HELPER_BYTES) {
|
|
268
|
+
if (!bundled)
|
|
269
|
+
missing.push(name);
|
|
249
270
|
continue;
|
|
250
271
|
}
|
|
251
|
-
|
|
272
|
+
const local = join(process.cwd(), '.claude', 'helpers', name);
|
|
273
|
+
if (!existsSync(local)) {
|
|
274
|
+
stale.push(name);
|
|
275
|
+
continue;
|
|
276
|
+
} // bundled has it, project doesn't — needs creating
|
|
277
|
+
if (statSync(local).size > MAX_DOCTOR_HELPER_BYTES)
|
|
252
278
|
continue;
|
|
253
279
|
try {
|
|
254
280
|
const hashLocal = crypto.createHash('sha256').update(readFileSync(local)).digest('hex');
|
|
@@ -268,6 +294,7 @@ export async function fixStaleHelpers() {
|
|
|
268
294
|
const bundled = _resolveBundledHelper(join('.claude', 'helpers', name));
|
|
269
295
|
if (bundled) {
|
|
270
296
|
try {
|
|
297
|
+
mkdirSync(dirname(local), { recursive: true });
|
|
271
298
|
copyFileSync(bundled, local);
|
|
272
299
|
fixed++;
|
|
273
300
|
}
|
|
@@ -19,7 +19,7 @@ export const doctorCommand = {
|
|
|
19
19
|
name: 'doctor',
|
|
20
20
|
description: 'System diagnostics and health checks',
|
|
21
21
|
options: [
|
|
22
|
-
{ name: 'fix', short: 'f', description: '
|
|
22
|
+
{ name: 'fix', short: 'f', description: 'Apply local fixes (helper files, monoes tool shims) and show fix commands for the rest', type: 'boolean', default: false },
|
|
23
23
|
{ name: 'install', short: 'i', description: 'Auto-install missing dependencies (Claude Code CLI)', type: 'boolean', default: false },
|
|
24
24
|
{
|
|
25
25
|
name: 'component', short: 'c',
|
|
@@ -122,9 +122,16 @@ export const doctorCommand = {
|
|
|
122
122
|
spinner.stop();
|
|
123
123
|
output.writeln(output.error('Failed to run health checks'));
|
|
124
124
|
}
|
|
125
|
-
|
|
125
|
+
// `--fix` applies the lightweight, local, no-network fixes (helper files,
|
|
126
|
+
// monoes CLI tool shims) — its description says "show fix commands" but
|
|
127
|
+
// silently doing nothing for these two beyond printing a hint is a worse
|
|
128
|
+
// outcome than just fixing them, and copying a bundled file locally is
|
|
129
|
+
// nothing like installing a package. `--install` additionally covers the
|
|
130
|
+
// Claude Code CLI, which is a real install (network fetch + binary setup)
|
|
131
|
+
// — kept opt-in separately so `--fix` alone never triggers that.
|
|
132
|
+
if (autoInstall || showFix) {
|
|
126
133
|
const claudeResult = results.find(r => r.name === 'Claude Code CLI');
|
|
127
|
-
if (claudeResult && claudeResult.status !== 'pass') {
|
|
134
|
+
if (autoInstall && claudeResult && claudeResult.status !== 'pass') {
|
|
128
135
|
if (await installClaudeCode()) {
|
|
129
136
|
const newCheck = await checkClaudeCode();
|
|
130
137
|
const idx = results.findIndex(r => r.name === 'Claude Code CLI');
|
|
@@ -574,7 +574,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
574
574
|
if (sourceHelpersForUpgrade) {
|
|
575
575
|
const destHelpersDir = path.join(targetDir, '.claude', 'helpers');
|
|
576
576
|
// Copy top-level critical files atomically
|
|
577
|
-
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'intelligence.cjs', 'statusline.cjs', 'graphify-freshen.cjs'];
|
|
577
|
+
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'intelligence.cjs', 'statusline.cjs', 'graphify-freshen.cjs', 'router.cjs'];
|
|
578
578
|
// Generated fallback for any critical helper missing from the source dir itself
|
|
579
579
|
// (e.g. the published npm template lacking auto-memory-hook.mjs).
|
|
580
580
|
const criticalGenerators = {
|
|
@@ -630,6 +630,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
630
630
|
'hook-handler.cjs': generateHookHandler(),
|
|
631
631
|
'intelligence.cjs': generateIntelligenceStub(),
|
|
632
632
|
'auto-memory-hook.mjs': generateAutoMemoryHook(),
|
|
633
|
+
'router.cjs': generateAgentRouter(),
|
|
633
634
|
};
|
|
634
635
|
for (const [helperName, content] of Object.entries(generatedCritical)) {
|
|
635
636
|
const targetPath = path.join(targetDir, '.claude', 'helpers', helperName);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI engine for Monomind — 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",
|