monomind 2.1.8 → 2.2.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/package.json +1 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +42 -1
- package/packages/@monomind/cli/dist/src/commands/init-wizard.js +2 -2
- package/packages/@monomind/cli/dist/src/commands/init.js +54 -10
- package/packages/@monomind/cli/dist/src/commands/swarm.js +2 -2
- package/packages/@monomind/cli/dist/src/init/settings-generator.js +8 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +28 -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.
|
|
3
|
+
"version": "2.2.0",
|
|
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",
|
|
@@ -393,8 +393,49 @@ async function handleSubagentStop(hookInput) {
|
|
|
393
393
|
// remaining event that carries a genuine failure signal, since post-task
|
|
394
394
|
// (TeammateIdle/TaskCompleted) is dead code (those aren't valid Claude
|
|
395
395
|
// Code hook events and are stripped from settings.json on init).
|
|
396
|
+
const _subagentSuccess = deriveSubagentSuccess(summary, lastToolError);
|
|
396
397
|
try {
|
|
397
|
-
require('../intelligence.cjs').feedback(
|
|
398
|
+
require('../intelligence.cjs').feedback(_subagentSuccess);
|
|
399
|
+
} catch (e) { /* non-fatal — feedback recording must never block subagent-stop */ }
|
|
400
|
+
|
|
401
|
+
// ── Routing Feedback Loop (per-subagent) ──────────────────────────────
|
|
402
|
+
// session-handler.cjs's SessionEnd writes one routing-feedback.jsonl entry
|
|
403
|
+
// per SESSION — but a session that never ends (e.g. a long-running
|
|
404
|
+
// `monomind org run` daemon session, observed running 2+ days without a
|
|
405
|
+
// SessionEnd in production) never contributes any entry at all, even
|
|
406
|
+
// though real subagent activity (and real success/failure signal) is
|
|
407
|
+
// happening continuously inside it. SubagentStop fires independently of
|
|
408
|
+
// session boundaries, so writing a routing-feedback entry HERE — one per
|
|
409
|
+
// actual routing decision (this subagent spawn), not one per session —
|
|
410
|
+
// keeps the "Routing Learning" signal live regardless of session length,
|
|
411
|
+
// instead of it going silently stale for anyone running persistent orgs.
|
|
412
|
+
try {
|
|
413
|
+
var _agentSlug = String(agentType || '').trim().toLowerCase().replace(/\s+/g, '-');
|
|
414
|
+
if (_agentSlug && _agentSlug !== 'ai-selecting' && _agentSlug !== 'unknown') {
|
|
415
|
+
var _rfEntry = {
|
|
416
|
+
timestamp: new Date().toISOString(),
|
|
417
|
+
suggestedAgent: _agentSlug,
|
|
418
|
+
sessionId: String(session || snap.session || hookInput.sessionId || hookInput.session_id || '').slice(0, 128),
|
|
419
|
+
intelligenceFeedback: _subagentSuccess,
|
|
420
|
+
};
|
|
421
|
+
// Best-effort confidence from the routing decision that led to this
|
|
422
|
+
// spawn — may not correspond exactly to THIS subagent if another
|
|
423
|
+
// routing decision overwrote last-route.json in between (same
|
|
424
|
+
// imprecision session-handler.cjs's session-level entries already had).
|
|
425
|
+
try {
|
|
426
|
+
var _lastRoutePath = path.join(CWD, '.monomind', 'last-route.json');
|
|
427
|
+
var _MAX_ROUTE = 64 * 1024;
|
|
428
|
+
if (fs.existsSync(_lastRoutePath) && fs.statSync(_lastRoutePath).size <= _MAX_ROUTE) {
|
|
429
|
+
var _lastRoute = JSON.parse(fs.readFileSync(_lastRoutePath, 'utf-8'));
|
|
430
|
+
if (typeof _lastRoute.confidence === 'number') _rfEntry.confidence = _lastRoute.confidence;
|
|
431
|
+
}
|
|
432
|
+
} catch (_) {}
|
|
433
|
+
appendJsonlWithRotation(
|
|
434
|
+
path.join(CWD, '.monomind', 'routing-feedback.jsonl'),
|
|
435
|
+
JSON.stringify(_rfEntry),
|
|
436
|
+
1000
|
|
437
|
+
);
|
|
438
|
+
}
|
|
398
439
|
} catch (e) { /* non-fatal — feedback recording must never block subagent-stop */ }
|
|
399
440
|
|
|
400
441
|
if (!org && !session) {
|
|
@@ -179,7 +179,7 @@ export const wizardCommand = {
|
|
|
179
179
|
}
|
|
180
180
|
const { execFileSync } = await import('child_process');
|
|
181
181
|
try {
|
|
182
|
-
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['
|
|
182
|
+
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
|
|
183
183
|
stdio: 'pipe',
|
|
184
184
|
cwd: ctx.cwd,
|
|
185
185
|
timeout: 30000
|
|
@@ -199,7 +199,7 @@ export const wizardCommand = {
|
|
|
199
199
|
if (enableGates) {
|
|
200
200
|
try {
|
|
201
201
|
const { execFileSync } = await import('child_process');
|
|
202
|
-
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['
|
|
202
|
+
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'guidance', 'setup', '--project-dir', ctx.cwd], { stdio: 'pipe', cwd: ctx.cwd, timeout: 10000 });
|
|
203
203
|
gatesEnabled = true;
|
|
204
204
|
output.writeln(output.success(' ✓ Enforcement gates wired'));
|
|
205
205
|
}
|
|
@@ -10,6 +10,7 @@ import { executeInit, DEFAULT_INIT_OPTIONS, MINIMAL_INIT_OPTIONS, FULL_INIT_OPTI
|
|
|
10
10
|
import { wizardCommand } from './init-wizard.js';
|
|
11
11
|
import { upgradeCommand } from './init-upgrade.js';
|
|
12
12
|
import { checkCommand, skillsCommand, hooksCommand } from './init-subcommands.js';
|
|
13
|
+
import { initializeMemoryDatabase } from '../memory/memory-initializer.js';
|
|
13
14
|
function isInitialized(cwd) {
|
|
14
15
|
const claudePath = path.join(cwd, '.claude', 'settings.json');
|
|
15
16
|
const monomindPath = path.join(cwd, '.monomind', 'config.yaml');
|
|
@@ -172,23 +173,30 @@ const initAction = async (ctx) => {
|
|
|
172
173
|
output.printInfo('Starting services...');
|
|
173
174
|
const { execSync } = await import('child_process');
|
|
174
175
|
if (startAll) {
|
|
176
|
+
// In-process, not a subprocess: `npx @monomind/cli@latest memory init`
|
|
177
|
+
// (the previous approach) shelled out to a package name that has
|
|
178
|
+
// never been published — every fresh init silently 404'd here and
|
|
179
|
+
// fell into the catch block's "already exists" message even when no
|
|
180
|
+
// database existed at all. initializeMemoryDatabase() is the same
|
|
181
|
+
// function `monomind memory init` itself calls, run directly.
|
|
175
182
|
try {
|
|
176
183
|
output.writeln(output.dim(' Initializing memory database...'));
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
const memResult = await initializeMemoryDatabase({ dbPath: path.join(ctx.cwd, '.swarm', 'memory.db') });
|
|
185
|
+
if (memResult.success) {
|
|
186
|
+
output.writeln(output.success(' ✓ Memory initialized'));
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
output.writeln(output.dim(` Memory database init skipped (${memResult.error || 'unknown reason'})`));
|
|
190
|
+
}
|
|
183
191
|
}
|
|
184
|
-
catch {
|
|
185
|
-
output.writeln(output.dim(
|
|
192
|
+
catch (e) {
|
|
193
|
+
output.writeln(output.dim(` Memory database init skipped (${e instanceof Error ? e.message : String(e)})`));
|
|
186
194
|
}
|
|
187
195
|
}
|
|
188
196
|
if (startAll) {
|
|
189
197
|
try {
|
|
190
198
|
output.writeln(output.dim(' Initializing swarm...'));
|
|
191
|
-
execSync('npx
|
|
199
|
+
execSync('npx monomind@latest swarm init --topology hierarchical', {
|
|
192
200
|
stdio: 'pipe',
|
|
193
201
|
cwd: ctx.cwd,
|
|
194
202
|
timeout: 30000
|
|
@@ -199,6 +207,42 @@ const initAction = async (ctx) => {
|
|
|
199
207
|
output.writeln(output.dim(' Swarm initialization skipped'));
|
|
200
208
|
}
|
|
201
209
|
}
|
|
210
|
+
if (startAll) {
|
|
211
|
+
// Seed .monomind/metrics/ immediately instead of waiting for the
|
|
212
|
+
// first Claude Code session-restore hook to run these workers —
|
|
213
|
+
// running `monomind doctor` right after `init` (before ever opening
|
|
214
|
+
// Claude Code) otherwise always shows "Worker Metrics"/"Security
|
|
215
|
+
// Audit" as unconfigured, even though nothing is actually broken.
|
|
216
|
+
try {
|
|
217
|
+
output.writeln(output.dim(' Seeding worker metrics...'));
|
|
218
|
+
const hooksMod = await import('@monomind/hooks').catch(() => null);
|
|
219
|
+
if (hooksMod && hooksMod.createWorkerManager) {
|
|
220
|
+
const manager = hooksMod.createWorkerManager(ctx.cwd);
|
|
221
|
+
await manager.ensureMetricsDir();
|
|
222
|
+
const seeded = [];
|
|
223
|
+
for (const workerName of ['map', 'audit']) {
|
|
224
|
+
try {
|
|
225
|
+
const r = await manager.runWorker(workerName);
|
|
226
|
+
if (r.success)
|
|
227
|
+
seeded.push(workerName);
|
|
228
|
+
}
|
|
229
|
+
catch { /* best-effort — doctor will report if this stays missing */ }
|
|
230
|
+
}
|
|
231
|
+
if (seeded.length > 0) {
|
|
232
|
+
output.writeln(output.success(` ✓ Worker metrics seeded (${seeded.join(', ')})`));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
output.writeln(output.dim(' Worker metrics seeding skipped'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
output.writeln(output.dim(' Worker metrics seeding skipped (@monomind/hooks unavailable)'));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
output.writeln(output.dim(` Worker metrics seeding skipped (${e instanceof Error ? e.message : String(e)})`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
202
246
|
output.writeln();
|
|
203
247
|
output.printSuccess('All services started');
|
|
204
248
|
}
|
|
@@ -216,7 +260,7 @@ const initAction = async (ctx) => {
|
|
|
216
260
|
try {
|
|
217
261
|
output.writeln(output.dim(` Model: ${embeddingModel}`));
|
|
218
262
|
output.writeln(output.dim(' Hyperbolic: Enabled (Poincaré ball)'));
|
|
219
|
-
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['
|
|
263
|
+
execFileSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['monomind@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
|
|
220
264
|
stdio: 'pipe',
|
|
221
265
|
cwd: ctx.cwd,
|
|
222
266
|
timeout: 30000
|
|
@@ -517,8 +517,8 @@ const statusCommand = {
|
|
|
517
517
|
output.writeln(output.warning('No active swarm'));
|
|
518
518
|
output.writeln();
|
|
519
519
|
output.writeln(output.dim('Start a swarm with:'));
|
|
520
|
-
output.writeln(output.dim(' npx
|
|
521
|
-
output.writeln(output.dim(' npx
|
|
520
|
+
output.writeln(output.dim(' npx monomind@latest swarm init'));
|
|
521
|
+
output.writeln(output.dim(' npx monomind@latest swarm start'));
|
|
522
522
|
output.writeln();
|
|
523
523
|
return { success: true, data: status };
|
|
524
524
|
}
|
|
@@ -229,8 +229,15 @@ function generateHooksConfig(config, graphify = true) {
|
|
|
229
229
|
matcher: 'Write|Edit|MultiEdit',
|
|
230
230
|
hooks: [
|
|
231
231
|
{
|
|
232
|
+
// Was 'pre-edit' — not a registered hook-handler.cjs dispatch
|
|
233
|
+
// command (only 'pre-write' is), so this silently no-op'd on
|
|
234
|
+
// every default init: hook-handler.cjs's dispatcher falls through
|
|
235
|
+
// to `else if (command) { console.log('[OK] Hook: ' + command); }`
|
|
236
|
+
// for any unrecognized subcommand, meaning the secrets-detection
|
|
237
|
+
// gate (gates-handler.cjs's handlePreWrite) never actually ran
|
|
238
|
+
// for any project set up via a default `monomind init`.
|
|
232
239
|
type: 'command',
|
|
233
|
-
command: hookHandlerCmd('pre-
|
|
240
|
+
command: hookHandlerCmd('pre-write'),
|
|
234
241
|
timeout: config.timeout,
|
|
235
242
|
},
|
|
236
243
|
],
|
|
@@ -8,6 +8,33 @@ import { randomBytes } from 'node:crypto';
|
|
|
8
8
|
const STORAGE_DIR = '.monomind';
|
|
9
9
|
const TERMINAL_DIR = 'terminals';
|
|
10
10
|
const TERMINAL_FILE = 'store.json';
|
|
11
|
+
// ── Secret-shaped env var filtering for terminal_execute ────────────────────
|
|
12
|
+
// terminal_execute runs arbitrary (metacharacter-denylisted) shell commands
|
|
13
|
+
// via execSync with `env: { ...process.env, ...session.env }` — the host
|
|
14
|
+
// process's real environment, which commonly holds provider API keys
|
|
15
|
+
// (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), CI/VCS tokens, and cloud
|
|
16
|
+
// credentials. A single unpiped command (the metacharacter denylist blocks
|
|
17
|
+
// piping/chaining, but not e.g. `curl` or `aws` invoked directly) can still
|
|
18
|
+
// read and exfiltrate whatever env vars it inherits. Stripping variables
|
|
19
|
+
// whose *names* match common secret conventions is defense-in-depth: it
|
|
20
|
+
// doesn't change legitimate terminal usage (PATH/HOME/etc. stay intact) but
|
|
21
|
+
// meaningfully shrinks what a malicious or hijacked command can read by
|
|
22
|
+
// default. Callers that genuinely need a specific secret can pass it via
|
|
23
|
+
// terminal_create's `env` option, which flows through untouched (session.env
|
|
24
|
+
// is layered on *after* the filtered process.env below).
|
|
25
|
+
const SECRET_ENV_NAME_PATTERN = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|_AUTH$|^AUTH_|PRIVATE_KEY|ACCESS_KEY)/i;
|
|
26
|
+
/** Returns a copy of `env` with secret-shaped variable names removed. */
|
|
27
|
+
function filterSecretEnvVars(env) {
|
|
28
|
+
const filtered = {};
|
|
29
|
+
for (const [k, v] of Object.entries(env)) {
|
|
30
|
+
if (v === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
if (SECRET_ENV_NAME_PATTERN.test(k))
|
|
33
|
+
continue;
|
|
34
|
+
filtered[k] = v;
|
|
35
|
+
}
|
|
36
|
+
return filtered;
|
|
37
|
+
}
|
|
11
38
|
function getTerminalDir() {
|
|
12
39
|
return join(getProjectCwd(), STORAGE_DIR, TERMINAL_DIR);
|
|
13
40
|
}
|
|
@@ -200,7 +227,7 @@ export const terminalTools = [
|
|
|
200
227
|
timeout,
|
|
201
228
|
maxBuffer: 5 * 1024 * 1024,
|
|
202
229
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
203
|
-
env: { ...process.env, ...session.env },
|
|
230
|
+
env: { ...filterSecretEnvVars(process.env), ...session.env },
|
|
204
231
|
});
|
|
205
232
|
exitCode = 0;
|
|
206
233
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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",
|