monomind 2.1.9 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monomind",
3
- "version": "2.1.9",
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",
@@ -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', ['@monomind/cli@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
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', ['@monomind/cli@latest', 'guidance', 'setup', '--project-dir', ctx.cwd], { stdio: 'pipe', cwd: ctx.cwd, timeout: 10000 });
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
- execSync('npx @monomind/cli@latest memory init', {
178
- stdio: 'pipe',
179
- cwd: ctx.cwd,
180
- timeout: 30000
181
- });
182
- output.writeln(output.success(' Memory initialized'));
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(' Memory database already exists'));
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 @monomind/cli@latest swarm init --topology hierarchical', {
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', ['@monomind/cli@latest', 'embeddings', 'init', '--model', embeddingModel, '--no-download', '--force'], {
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 @monomind/cli@latest swarm init'));
521
- output.writeln(output.dim(' npx @monomind/cli@latest swarm start'));
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-edit'),
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.1.9",
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",