monomind 2.7.7 → 2.7.9
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 -2
- package/packages/@monomind/cli/.claude/agents/core/coder.md +9 -0
- package/packages/@monomind/cli/.claude/agents/core/coordinator.md +62 -0
- package/packages/@monomind/cli/.claude/agents/core/planner.md +9 -0
- package/packages/@monomind/cli/.claude/agents/core/reviewer.md +9 -0
- package/packages/@monomind/cli/.claude/agents/core/tester.md +8 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/gates-handler.cjs +180 -47
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +32 -3
- package/packages/@monomind/cli/.claude/helpers/hook-handler.cjs +55 -3
- package/packages/@monomind/cli/.claude/helpers/intelligence.cjs +3 -1
- package/packages/@monomind/cli/.claude/helpers/statusline.cjs +27 -3
- package/packages/@monomind/cli/.claude/helpers/utils/monograph.cjs +104 -18
- package/packages/@monomind/cli/.claude/settings.json +1 -1
- package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +6 -1
- package/packages/@monomind/cli/dist/src/capabilities/index.d.ts +0 -1
- package/packages/@monomind/cli/dist/src/capabilities/index.js +8 -1
- package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +5 -1
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +8 -1
- package/packages/@monomind/cli/dist/src/commands/guidance.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/init.js +18 -4
- package/packages/@monomind/cli/dist/src/commands/memory-crud.js +7 -1
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +17 -7
- package/packages/@monomind/cli/dist/src/commands/org.d.ts +11 -0
- package/packages/@monomind/cli/dist/src/commands/org.js +162 -15
- package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +30 -1
- package/packages/@monomind/cli/dist/src/commands/security-scan.js +182 -69
- package/packages/@monomind/cli/dist/src/commands/swarm.js +41 -14
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +11 -10
- package/packages/@monomind/cli/dist/src/init/executor.js +138 -22
- package/packages/@monomind/cli/dist/src/init/settings-generator.js +4 -1
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.d.ts +17 -0
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +62 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +16 -8
- package/packages/@monomind/cli/dist/src/mcp-tools/knowledge-tools.js +27 -13
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +5 -1
- package/packages/@monomind/cli/dist/src/memory/memory-read.d.ts +7 -2
- package/packages/@monomind/cli/dist/src/memory/memory-read.js +10 -2
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +25 -6
- package/packages/@monomind/cli/dist/src/orgrt/daemon.d.ts +21 -1
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +82 -11
- package/packages/@monomind/cli/dist/src/orgrt/inbox.js +53 -20
- package/packages/@monomind/cli/dist/src/parser.d.ts +4 -2
- package/packages/@monomind/cli/dist/src/parser.js +61 -25
- package/packages/@monomind/cli/dist/src/services/config-file-manager.d.ts +10 -2
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -2
- package/packages/@monomind/cli/dist/src/ui/collector.mjs +43 -3
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +27 -8
- package/packages/@monomind/cli/dist/src/ui/server.mjs +144 -14
- package/packages/@monomind/cli/package.json +4 -4
- package/packages/@monomind/cli/dist/src/capabilities/watcher.d.ts +0 -18
- package/packages/@monomind/cli/dist/src/capabilities/watcher.js +0 -107
- package/packages/@monomind/cli/dist/src/config-adapter.d.ts +0 -16
- package/packages/@monomind/cli/dist/src/config-adapter.js +0 -220
|
@@ -27,6 +27,7 @@ const RECENT_EDITS_FILE = path.join(DATA_DIR, 'recent-edits.jsonl');
|
|
|
27
27
|
|
|
28
28
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MiB guard
|
|
29
29
|
const RING_BUFFER_MAX = 50;
|
|
30
|
+
const MAX_ENTRIES = 200;
|
|
30
31
|
|
|
31
32
|
var _entries = []; // deduplicated memory entries loaded from store
|
|
32
33
|
var _recentEdits = []; // ring buffer of recently edited paths (in-memory, may be empty across subprocesses)
|
|
@@ -141,7 +142,7 @@ function init() {
|
|
|
141
142
|
if (seen.has(key)) return false;
|
|
142
143
|
seen.add(key);
|
|
143
144
|
return true;
|
|
144
|
-
});
|
|
145
|
+
}).slice(-MAX_ENTRIES);
|
|
145
146
|
|
|
146
147
|
// Bootstrap from monograph when store is sparse — called externally via bootstrapFromDb(db)
|
|
147
148
|
|
|
@@ -490,6 +491,7 @@ function bootstrapFromDb(db) {
|
|
|
490
491
|
files: h.file ? [h.file] : [],
|
|
491
492
|
ts: Date.now(),
|
|
492
493
|
};
|
|
494
|
+
if (_entries.length >= MAX_ENTRIES) _entries.shift();
|
|
493
495
|
_entries.push(hubEntry);
|
|
494
496
|
newHubEntries.push(hubEntry);
|
|
495
497
|
added++;
|
|
@@ -99,6 +99,22 @@ function readJSON(filePath) {
|
|
|
99
99
|
} catch { /* ignore */ }
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
|
+
/** Canonical data root — mirrors getMonomindDataRoot() in mcp-tools/types.ts. */
|
|
103
|
+
function monomindDataRoot(cwd) {
|
|
104
|
+
if (process.env.MONOMIND_DATA_DIR) return process.env.MONOMIND_DATA_DIR;
|
|
105
|
+
try {
|
|
106
|
+
const gitEntry = path.join(cwd, '.git');
|
|
107
|
+
const st = fs.statSync(gitEntry);
|
|
108
|
+
if (st.isDirectory()) return path.join(gitEntry, 'monomind');
|
|
109
|
+
const m = fs.readFileSync(gitEntry, 'utf8').match(/^gitdir:\s*(.+)/m);
|
|
110
|
+
if (m) {
|
|
111
|
+
const wt = path.resolve(cwd, m[1].trim());
|
|
112
|
+
return path.join(path.dirname(path.dirname(wt)), 'monomind');
|
|
113
|
+
}
|
|
114
|
+
} catch { /* not a git repo */ }
|
|
115
|
+
return path.join(cwd, '.monomind');
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
|
|
103
119
|
// Safe file stat (returns null on failure)
|
|
104
120
|
function safeStat(filePath) {
|
|
@@ -395,9 +411,17 @@ function getSwarmStatus() {
|
|
|
395
411
|
} catch { /* fall through */ }
|
|
396
412
|
}
|
|
397
413
|
|
|
398
|
-
// SECONDARY: swarm-state.json written by MCP swarm_init — trust if fresh
|
|
399
|
-
|
|
400
|
-
|
|
414
|
+
// SECONDARY: swarm-state.json written by MCP swarm_init — trust if fresh.
|
|
415
|
+
// The MCP tools resolve their data root via getMonomindDataRoot(), which
|
|
416
|
+
// inside a git repo is `<repo>/.git/monomind` — so reading only
|
|
417
|
+
// `<cwd>/.monomind` showed a stale or missing swarm in every real project.
|
|
418
|
+
// Canonical first, legacy second for projects written by an older CLI.
|
|
419
|
+
const swarmStateCandidates = [
|
|
420
|
+
path.join(monomindDataRoot(CWD), 'swarm', 'swarm-state.json'),
|
|
421
|
+
path.join(CWD, '.monomind', 'swarm', 'swarm-state.json'),
|
|
422
|
+
];
|
|
423
|
+
let swarmState = null;
|
|
424
|
+
for (const p of swarmStateCandidates) { swarmState = readJSON(p); if (swarmState) break; }
|
|
401
425
|
if (swarmState) {
|
|
402
426
|
const updatedAt = swarmState.updatedAt || swarmState.startedAt;
|
|
403
427
|
const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
|
|
@@ -330,25 +330,107 @@ function _graphGateReadSessions() {
|
|
|
330
330
|
return sessions;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
// Atomic replace: write a per-process temp file, then rename() over the
|
|
334
|
+
// target. rename() within a directory is atomic on POSIX and on Windows via
|
|
335
|
+
// Node's fs, so a concurrent reader sees either the old file or the new one
|
|
336
|
+
// — never a half-written one. (The previous plain writeFileSync truncated
|
|
337
|
+
// the live file first, so a reader could observe a truncated/torn document.)
|
|
333
338
|
function _graphGateWriteSessions(sessions) {
|
|
334
339
|
var ids = Object.keys(sessions);
|
|
335
340
|
if (ids.length > 20) {
|
|
336
341
|
ids.sort(function (a, b) { return (sessions[a].ts || 0) - (sessions[b].ts || 0); });
|
|
337
342
|
for (var i = 0; i < ids.length - 20; i++) delete sessions[ids[i]];
|
|
338
343
|
}
|
|
339
|
-
|
|
340
|
-
fs.
|
|
344
|
+
var dir = path.join(CWD, '.monomind');
|
|
345
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
346
|
+
var target = _graphGateStateFile();
|
|
347
|
+
var tmp = target + '.' + process.pid + '.' + Math.random().toString(36).slice(2, 8) + '.tmp';
|
|
348
|
+
try {
|
|
349
|
+
fs.writeFileSync(tmp, JSON.stringify({ sessions: sessions }));
|
|
350
|
+
fs.renameSync(tmp, target);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
353
|
+
throw e;
|
|
354
|
+
}
|
|
341
355
|
}
|
|
342
356
|
|
|
343
|
-
|
|
344
|
-
|
|
357
|
+
// Cross-process advisory lock around the read-modify-write above.
|
|
358
|
+
//
|
|
359
|
+
// Three concurrent paths touch this state (pre-bash, pre-search, and
|
|
360
|
+
// post-graph-tool's markQueried), each in its own short-lived process. Without
|
|
361
|
+
// a lock, two of them read the same snapshot and the second write erases the
|
|
362
|
+
// first — measured: 12 concurrent writers landed as few as 6 of 12 session
|
|
363
|
+
// records. A lost `queried:true` re-blocks a session that already called
|
|
364
|
+
// monograph; a lost `blockedOnce` can block it a second time.
|
|
365
|
+
//
|
|
366
|
+
// mkdir is the atomic primitive (works on every platform and over network FS).
|
|
367
|
+
// The lock is best-effort: if it can't be taken within the budget, or a stale
|
|
368
|
+
// one is reclaimed, we still perform an atomic-rename write — no worse than the
|
|
369
|
+
// old behavior, and never a hang. Hooks run on every tool call, so the total
|
|
370
|
+
// wait is deliberately tiny.
|
|
371
|
+
var _GRAPH_GATE_LOCK_TIMEOUT_MS = 250;
|
|
372
|
+
var _GRAPH_GATE_LOCK_STALE_MS = 2000;
|
|
373
|
+
|
|
374
|
+
function _sleepSync(ms) {
|
|
375
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); } catch (e) { /* no SAB */ }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function _graphGateAcquireLock() {
|
|
379
|
+
var lockDir = _graphGateStateFile() + '.lock';
|
|
380
|
+
var deadline = Date.now() + _GRAPH_GATE_LOCK_TIMEOUT_MS;
|
|
381
|
+
var held = false;
|
|
382
|
+
for (;;) {
|
|
383
|
+
try {
|
|
384
|
+
fs.mkdirSync(lockDir);
|
|
385
|
+
held = true;
|
|
386
|
+
break;
|
|
387
|
+
} catch (e) {
|
|
388
|
+
if (e && e.code !== 'EEXIST') break; // can't lock at all — proceed unlocked
|
|
389
|
+
try {
|
|
390
|
+
var st = fs.statSync(lockDir);
|
|
391
|
+
if (Date.now() - st.mtimeMs > _GRAPH_GATE_LOCK_STALE_MS) {
|
|
392
|
+
fs.rmSync(lockDir, { recursive: true, force: true }); // crashed holder
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
} catch (_) { continue; } // lock vanished — retry immediately
|
|
396
|
+
if (Date.now() >= deadline) break; // give up; write unlocked
|
|
397
|
+
_sleepSync(5);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return function release() {
|
|
401
|
+
if (!held) return;
|
|
402
|
+
try { fs.rmSync(lockDir, { recursive: true, force: true }); } catch (_) {}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Read-modify-write one session's entry under the lock. `mutate(s, ctx)`
|
|
408
|
+
* receives the session record (created if absent) and mutates it in place; its
|
|
409
|
+
* return value is passed through to the caller. A mutator that changed nothing
|
|
410
|
+
* can call `ctx.noWrite()` to skip the write entirely (the common read-only
|
|
411
|
+
* case: a session that already called monograph).
|
|
412
|
+
*/
|
|
413
|
+
function _graphGateUpdateSession(sessionId, mutate) {
|
|
414
|
+
var release = _graphGateAcquireLock();
|
|
345
415
|
try {
|
|
346
416
|
var sessions = _graphGateReadSessions();
|
|
347
|
-
var s = sessions[sessionId] || { blockedOnce: false };
|
|
348
|
-
|
|
417
|
+
var s = sessions[sessionId] || { queried: false, blockedOnce: false };
|
|
418
|
+
var write = true;
|
|
419
|
+
var out = mutate(s, { noWrite: function () { write = false; } });
|
|
420
|
+
if (!write) return out;
|
|
349
421
|
s.ts = Date.now();
|
|
350
422
|
sessions[sessionId] = s;
|
|
351
423
|
_graphGateWriteSessions(sessions);
|
|
424
|
+
return out;
|
|
425
|
+
} finally {
|
|
426
|
+
release();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function _graphGateMarkQueried(sessionId) {
|
|
431
|
+
if (!sessionId) return;
|
|
432
|
+
try {
|
|
433
|
+
_graphGateUpdateSession(sessionId, function (s) { s.queried = true; });
|
|
352
434
|
} catch (e) { /* non-fatal */ }
|
|
353
435
|
}
|
|
354
436
|
|
|
@@ -356,19 +438,23 @@ function _graphGateMarkQueried(sessionId) {
|
|
|
356
438
|
function _graphGateShouldBlock(sessionId) {
|
|
357
439
|
if (String(process.env.MONOMIND_GRAPH_GATE || '').toLowerCase() === 'off') return false;
|
|
358
440
|
if (!sessionId || !_isGraphFresh()) return false;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
441
|
+
// Test-and-set blockedOnce atomically: two concurrent greps in the same
|
|
442
|
+
// session must not both observe blockedOnce=false and both block.
|
|
443
|
+
try {
|
|
444
|
+
return _graphGateUpdateSession(sessionId, function (s, ctx) {
|
|
445
|
+
if (s.queried) { ctx.noWrite(); return false; }
|
|
446
|
+
if (!s.blockedOnce) {
|
|
447
|
+
s.blockedOnce = true;
|
|
448
|
+
return 'block';
|
|
449
|
+
}
|
|
450
|
+
// Already blocked once but monograph still not called — warn without
|
|
451
|
+
// blocking so subagents without MCP access don't deadlock.
|
|
452
|
+
ctx.noWrite();
|
|
453
|
+
return 'warn';
|
|
454
|
+
});
|
|
455
|
+
} catch (e) {
|
|
456
|
+
return false; // state unreadable/unwritable — never block on a state error
|
|
368
457
|
}
|
|
369
|
-
// Already blocked once but monograph still not called — warn without blocking
|
|
370
|
-
// so subagents without MCP access don't deadlock.
|
|
371
|
-
return 'warn';
|
|
372
458
|
}
|
|
373
459
|
|
|
374
460
|
function _getNodeCount() {
|
|
@@ -4,6 +4,7 @@ import { join, dirname } from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { WebSocketServer } from 'ws';
|
|
7
|
+
import { getMonomindDataRoot } from '../../mcp-tools/types.js';
|
|
7
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
// 4243: the main monomind dashboard owns 4242 — keep this server off that port
|
|
9
10
|
const DEFAULT_PORT = 4243;
|
|
@@ -49,7 +50,11 @@ async function readMetricsDir(root) {
|
|
|
49
50
|
async function collectDashboardState(root) {
|
|
50
51
|
const [workerMetrics, swarmState, lastRoute, autoMemory] = await Promise.all([
|
|
51
52
|
readMetricsDir(root),
|
|
52
|
-
|
|
53
|
+
// Canonical root first (getMonomindDataRoot(): `<repo>/.git/monomind` in a
|
|
54
|
+
// git repo), legacy `<root>/.monomind` second. Reading only the legacy path
|
|
55
|
+
// showed stale/absent swarm state in every real project.
|
|
56
|
+
readJsonSafe(join(getMonomindDataRoot(root), 'swarm', 'swarm-state.json'))
|
|
57
|
+
.then((v) => v ?? readJsonSafe(join(root, '.monomind', 'swarm', 'swarm-state.json'))),
|
|
53
58
|
readJsonSafe(join(root, '.monomind', 'last-route.json')),
|
|
54
59
|
readJsonSafe(join(root, '.monomind', 'data', 'auto-memory-store.json')),
|
|
55
60
|
]);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { scanDirectory, saveFingerprint, loadFingerprint } from './scanner.js';
|
|
3
3
|
export { CapabilityManager } from './manager.js';
|
|
4
|
-
export { FileWatcher } from './watcher.js';
|
|
5
4
|
export { codeCapability } from './cap-code.js';
|
|
6
5
|
export { documentsCapability } from './cap-documents.js';
|
|
7
6
|
export { mediaCapability } from './cap-media.js';
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { scanDirectory, saveFingerprint, loadFingerprint } from './scanner.js';
|
|
3
3
|
export { CapabilityManager } from './manager.js';
|
|
4
|
-
|
|
4
|
+
// `FileWatcher` (./watcher.ts) was deleted 2026-07: it was built, exported and
|
|
5
|
+
// unit-tested but never instantiated anywhere in production. Nothing in the
|
|
6
|
+
// capabilities pipeline watches files — `search scan` re-fingerprints on demand
|
|
7
|
+
// (see commands/search-universal.ts) and monograph has its own separate watcher
|
|
8
|
+
// (`monograph_watch`). Nothing outside this package could reach it either:
|
|
9
|
+
// capabilities/ is not in package.json's `exports` map and src/index.ts never
|
|
10
|
+
// re-exported it. If incremental re-scanning is wanted later, wire it to the
|
|
11
|
+
// scanner at the same time rather than landing an unused class again.
|
|
5
12
|
export { codeCapability } from './cap-code.js';
|
|
6
13
|
export { documentsCapability } from './cap-documents.js';
|
|
7
14
|
export { mediaCapability } from './cap-media.js';
|
|
@@ -181,6 +181,10 @@ export const listCommand = {
|
|
|
181
181
|
],
|
|
182
182
|
action: async (ctx) => {
|
|
183
183
|
try {
|
|
184
|
+
// agent_list emits `agentId` (agent-tools.ts), not `id` — reading `id`
|
|
185
|
+
// here rendered a blank ID column for every agent. `id` is kept as a
|
|
186
|
+
// fallback because agent_pool/agent_health project the same records
|
|
187
|
+
// under that key.
|
|
184
188
|
const result = await callMCPTool('agent_list', {
|
|
185
189
|
status: ctx.flags.all ? 'all' : ctx.flags.status || undefined,
|
|
186
190
|
agentType: ctx.flags.type || undefined,
|
|
@@ -198,7 +202,7 @@ export const listCommand = {
|
|
|
198
202
|
return { success: true, data: result };
|
|
199
203
|
}
|
|
200
204
|
const displayAgents = result.agents.map(agent => ({
|
|
201
|
-
id: agent.id,
|
|
205
|
+
id: agent.agentId ?? agent.id ?? '',
|
|
202
206
|
type: agent.agentType,
|
|
203
207
|
status: agent.status,
|
|
204
208
|
created: new Date(agent.createdAt).toLocaleTimeString(),
|
|
@@ -627,7 +627,14 @@ export async function checkGuidanceGates() {
|
|
|
627
627
|
}
|
|
628
628
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
629
629
|
const preToolUse = settings?.hooks?.PreToolUse ?? [];
|
|
630
|
-
|
|
630
|
+
// Match on capability, not on an exact matcher string. The matcher is a
|
|
631
|
+
// regex alternation that legitimately changes as tools are added — it
|
|
632
|
+
// gained `|NotebookEdit` when that tool turned out to bypass the secret
|
|
633
|
+
// gate — and an `=== 'Write|Edit|MultiEdit'` test reported the gate as
|
|
634
|
+
// INACTIVE the moment it did, telling users their secrets gate was off
|
|
635
|
+
// while it was demonstrably blocking secrets, and sending them to
|
|
636
|
+
// `guidance setup`, which would then add a duplicate entry.
|
|
637
|
+
const hasPreWrite = preToolUse.some(e => /\bWrite\b/.test(e.matcher ?? '') && e.hooks.some(h => h.command?.includes('pre-write')));
|
|
631
638
|
const hasPreBash = preToolUse.some(e => e.matcher === 'Bash' && e.hooks.some(h => h.command?.includes('pre-bash')));
|
|
632
639
|
if (!hasPreWrite && !hasPreBash)
|
|
633
640
|
return { name: 'Guidance Gates', status: 'warn', message: 'gates-handler.cjs present but no gates registered', fix: 'monomind guidance setup' };
|
|
@@ -52,12 +52,18 @@ const setupCommand = {
|
|
|
52
52
|
const preToolUse = hooks.PreToolUse || [];
|
|
53
53
|
const PRE_BASH_MATCHER = 'Bash';
|
|
54
54
|
const PRE_BASH_COMMAND = 'node "$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs" pre-bash';
|
|
55
|
-
|
|
55
|
+
// Keep in step with .claude/settings.json and init/settings-generator.ts.
|
|
56
|
+
// NotebookEdit is listed explicitly: its content field is `new_source`, so
|
|
57
|
+
// it slipped past the secret gate until the handler learned to read it.
|
|
58
|
+
const PRE_WRITE_MATCHER = 'Write|Edit|MultiEdit|NotebookEdit';
|
|
56
59
|
const PRE_WRITE_COMMAND = 'node "$CLAUDE_PROJECT_DIR/.claude/helpers/hook-handler.cjs" pre-write';
|
|
57
60
|
// Find any existing Bash/Write entry (may have different hooks from other tools)
|
|
58
61
|
const existingBashEntry = preToolUse.find(e => e.matcher === PRE_BASH_MATCHER);
|
|
59
62
|
const alreadyHasPreBash = existingBashEntry?.hooks.some(h => h.command.includes('pre-bash')) ?? false;
|
|
60
|
-
|
|
63
|
+
// Find by capability, not exact string: a settings.json written before
|
|
64
|
+
// NotebookEdit was added still says 'Write|Edit|MultiEdit', and an exact
|
|
65
|
+
// match would miss it and append a second, overlapping entry.
|
|
66
|
+
const existingWriteEntry = preToolUse.find(e => /\bWrite\b/.test(e.matcher ?? ''));
|
|
61
67
|
const alreadyHasPreWrite = existingWriteEntry?.hooks.some(h => h.command.includes('pre-write')) ?? false;
|
|
62
68
|
const changes = [];
|
|
63
69
|
// Register pre-bash (destructive-ops gate)
|
|
@@ -90,7 +90,18 @@ const initAction = async (ctx) => {
|
|
|
90
90
|
// Start monograph watch for ongoing file-change rebuilds, unless --no-watch was passed.
|
|
91
91
|
// Guard: skip if a watcher PID file already exists and the process is still alive,
|
|
92
92
|
// preventing duplicate watchers from accumulating on repeated `init --force` runs.
|
|
93
|
-
|
|
93
|
+
// `--no-watch` is the parser's negation of the declared boolean `watch`
|
|
94
|
+
// option. It used to be declared as its own boolean named 'no-watch', which
|
|
95
|
+
// the parser never populated: parseFlag strips the `--no-` prefix and looks
|
|
96
|
+
// up `watch`, which IS a declared boolean (status/agent-ops declare it and
|
|
97
|
+
// getBooleanFlags() is global across all registered commands). So
|
|
98
|
+
// `--no-watch` set the unrelated `watch` flag to false and left `no-watch`
|
|
99
|
+
// at its `false` default — the flag was a silent no-op and the watcher
|
|
100
|
+
// started anyway. The legacy `no-watch`/`noWatch` keys are still honoured
|
|
101
|
+
// for programmatic callers that set ctx.flags directly.
|
|
102
|
+
const noWatch = ctx.flags.watch === false ||
|
|
103
|
+
ctx.flags['no-watch'] === true ||
|
|
104
|
+
ctx.flags.noWatch === true;
|
|
94
105
|
if (!noWatch) {
|
|
95
106
|
try {
|
|
96
107
|
const { spawn } = await import('child_process');
|
|
@@ -354,10 +365,13 @@ export const initCommand = {
|
|
|
354
365
|
default: true,
|
|
355
366
|
},
|
|
356
367
|
{
|
|
357
|
-
|
|
358
|
-
|
|
368
|
+
// Declared as the positive `watch` so the parser's `--no-X` negation
|
|
369
|
+
// actually reaches it. Declaring it as `no-watch` made `--no-watch` a
|
|
370
|
+
// no-op — see the noWatch resolution in initAction.
|
|
371
|
+
name: 'watch',
|
|
372
|
+
description: 'Start the monograph knowledge graph watcher after init (default: true; --no-watch skips it)',
|
|
359
373
|
type: 'boolean',
|
|
360
|
-
default:
|
|
374
|
+
default: true,
|
|
361
375
|
},
|
|
362
376
|
{
|
|
363
377
|
name: 'with-embeddings',
|
|
@@ -338,20 +338,26 @@ export const searchCommand = {
|
|
|
338
338
|
// behind a "(semantic)" header.
|
|
339
339
|
const REASON_TEXT = {
|
|
340
340
|
'no-embedding-model': 'embedding model unavailable',
|
|
341
|
+
'empty-query': 'query was empty, so no vector could be built',
|
|
341
342
|
'embedding-failed': 'embedding generation failed',
|
|
342
343
|
'no-semantic-matches': 'vector search returned no matches',
|
|
343
344
|
};
|
|
345
|
+
const why = fallbackReason ? REASON_TEXT[fallbackReason] ?? fallbackReason : undefined;
|
|
344
346
|
if (actualMethod === 'semantic') {
|
|
345
347
|
output.writeln(output.dim(' Method: semantic (vector similarity)'));
|
|
346
348
|
}
|
|
347
349
|
else if (actualMethod === 'hybrid') {
|
|
348
350
|
output.writeln(output.dim(' Method: hybrid (per-entry cosine, keyword overlap where no vector exists)'));
|
|
349
351
|
}
|
|
352
|
+
else if (actualMethod === 'hash-vector' || actualMethod === 'hash-hybrid') {
|
|
353
|
+
// A vector search did run, but over hash-fallback embeddings — the
|
|
354
|
+
// scores are cosines of a lexical hash, not of a semantic model.
|
|
355
|
+
output.printWarning(`Method: ${actualMethod}${why ? ` — ${why}` : ''}. Scores are cosines over deterministic hash embeddings, not semantic similarity.`);
|
|
356
|
+
}
|
|
350
357
|
else if (actualMethod === 'unknown') {
|
|
351
358
|
output.writeln(output.dim(' Method: unknown'));
|
|
352
359
|
}
|
|
353
360
|
else {
|
|
354
|
-
const why = fallbackReason ? REASON_TEXT[fallbackReason] ?? fallbackReason : undefined;
|
|
355
361
|
output.printWarning(`Method: ${actualMethod}${why ? ` — ${why}` : ''}. Scores are token-overlap fractions, not vector similarity.`);
|
|
356
362
|
}
|
|
357
363
|
output.writeln(output.dim(` Search time: ${searchResult.searchTime}ms`));
|
|
@@ -295,6 +295,23 @@ export const answerAction = async (ctx, name) => {
|
|
|
295
295
|
if (freshQ && freshQ.answer !== null) {
|
|
296
296
|
return { success: false, message: `question "${questionId}" was answered while this command was running` };
|
|
297
297
|
}
|
|
298
|
+
// Queue BEFORE marking answered (same rule as daemon.answerQuestion): if the
|
|
299
|
+
// append fails, the question must stay pending and answerable. Marking first meant
|
|
300
|
+
// a failed queueMessage recorded the answer as delivered while nothing was queued,
|
|
301
|
+
// and the `already answered` guard then rejected every retry.
|
|
302
|
+
const { queueMessage } = await import('../orgrt/inbox.js');
|
|
303
|
+
try {
|
|
304
|
+
queueMessage(ctx.cwd, name, {
|
|
305
|
+
fromQualified: 'human', toRole: q.role,
|
|
306
|
+
subject: `answer:${questionId}`,
|
|
307
|
+
body: `question: ${q.question}\n\nanswer: ${answer}`,
|
|
308
|
+
ts: Date.now(),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
log(output.error(`Could not queue the answer for ${name}:${q.role} (${err instanceof Error ? err.message : String(err)}) — answer NOT recorded, retry it.`));
|
|
313
|
+
return { success: false, message: 'queueing failed — answer not recorded' };
|
|
314
|
+
}
|
|
298
315
|
const merged = fresh.some(x => x.questionId === questionId)
|
|
299
316
|
? fresh.map(x => x.questionId === questionId ? { ...x, answer, answeredAt: Date.now() } : x)
|
|
300
317
|
: [...fresh, { ...q, answer, answeredAt: Date.now() }];
|
|
@@ -303,13 +320,6 @@ export const answerAction = async (ctx, name) => {
|
|
|
303
320
|
writeFileSync(tmp, JSON.stringify({ questions: merged }, null, 2));
|
|
304
321
|
const { renameSync } = await import('node:fs');
|
|
305
322
|
renameSync(tmp, dest);
|
|
306
|
-
const { queueMessage } = await import('../orgrt/inbox.js');
|
|
307
|
-
queueMessage(ctx.cwd, name, {
|
|
308
|
-
fromQualified: 'human', toRole: q.role,
|
|
309
|
-
subject: `answer:${questionId}`,
|
|
310
|
-
body: `question: ${q.question}\n\nanswer: ${answer}`,
|
|
311
|
-
ts: Date.now(),
|
|
312
|
-
});
|
|
313
323
|
log(output.success(`Answer recorded — ${name}:${q.role} receives it when the org next runs.`));
|
|
314
324
|
return { success: true };
|
|
315
325
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command, CommandResult } from '../types.js';
|
|
2
|
+
import { OrgDaemon } from '../orgrt/daemon.js';
|
|
2
3
|
export declare function validateOrgName(name: string | undefined): {
|
|
3
4
|
ok: true;
|
|
4
5
|
name: string;
|
|
@@ -9,6 +10,16 @@ export declare function validateOrgName(name: string | undefined): {
|
|
|
9
10
|
export declare function listOrgConfigFiles(orgsDir: string): string[];
|
|
10
11
|
/** Remove a lingering stopfile so a fresh `org run` doesn't self-terminate. */
|
|
11
12
|
export declare const clearStopfile: (cwd: string, name: string) => void;
|
|
13
|
+
/** One pass of the `org serve` stopfile poll.
|
|
14
|
+
*
|
|
15
|
+
* `monomind org stop <name>` writes `.monomind/orgs/<name>/stop`. `org run` has always
|
|
16
|
+
* polled that file; `org serve` did not — so against a serve daemon `org stop` was a
|
|
17
|
+
* silent no-op that still printed "daemon exits within 2s" and exited 0 while the org
|
|
18
|
+
* kept running. Stops every running org whose stopfile is present, then clears the
|
|
19
|
+
* stopfile so the next scheduled iteration isn't killed on sight.
|
|
20
|
+
*
|
|
21
|
+
* Returns the names it stopped (awaited), so callers/tests don't have to guess. */
|
|
22
|
+
export declare const pollStopfiles: (cwd: string, daemon: OrgDaemon) => Promise<string[]>;
|
|
12
23
|
export declare const orgCommand: Command;
|
|
13
24
|
export default orgCommand;
|
|
14
25
|
//# sourceMappingURL=org.d.ts.map
|