@monoes/monomindcli 1.10.29 → 1.10.31

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.
Files changed (111) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +39 -4
  2. package/.claude/helpers/handlers/adr-draft-handler.cjs +64 -0
  3. package/.claude/helpers/handlers/agent-start-handler.cjs +99 -0
  4. package/.claude/helpers/handlers/edit-handler.cjs +145 -0
  5. package/.claude/helpers/handlers/graph-status-handler.cjs +38 -0
  6. package/.claude/helpers/handlers/route-handler.cjs +393 -0
  7. package/.claude/helpers/handlers/session-handler.cjs +167 -0
  8. package/.claude/helpers/handlers/session-restore-handler.cjs +348 -0
  9. package/.claude/helpers/handlers/task-handler.cjs +329 -0
  10. package/.claude/helpers/hook-handler.cjs +120 -2431
  11. package/.claude/helpers/intelligence.cjs +21 -2
  12. package/.claude/helpers/learning-service.mjs +166 -8
  13. package/.claude/helpers/memory-palace.cjs +72 -12
  14. package/.claude/helpers/router.cjs +79 -5
  15. package/.claude/helpers/statusline.cjs +193 -399
  16. package/.claude/helpers/utils/micro-agents.cjs +338 -0
  17. package/.claude/helpers/utils/monograph.cjs +349 -0
  18. package/.claude/helpers/utils/telemetry.cjs +144 -0
  19. package/.claude/skills/agent-browser-testing/SKILL.md +3 -2
  20. package/.claude/skills/monomind/browse-agentcore.md +116 -0
  21. package/.claude/skills/monomind/browse-electron.md +189 -0
  22. package/.claude/skills/monomind/browse-qa.md +229 -0
  23. package/.claude/skills/monomind/browse-references/authentication.md +162 -0
  24. package/.claude/skills/monomind/browse-references/trust-boundaries.md +41 -0
  25. package/.claude/skills/monomind/browse-references/video-recording.md +84 -0
  26. package/.claude/skills/monomind/browse-slack.md +189 -0
  27. package/.claude/skills/monomind/browse-vercel.md +240 -0
  28. package/.claude/skills/monomind/browse.md +724 -0
  29. package/dist/src/browser/actions.d.ts +28 -0
  30. package/dist/src/browser/actions.d.ts.map +1 -0
  31. package/dist/src/browser/actions.js +292 -0
  32. package/dist/src/browser/actions.js.map +1 -0
  33. package/dist/src/browser/batch.d.ts +13 -0
  34. package/dist/src/browser/batch.d.ts.map +1 -0
  35. package/dist/src/browser/batch.js +11 -0
  36. package/dist/src/browser/batch.js.map +1 -0
  37. package/dist/src/browser/browser.d.ts +14 -0
  38. package/dist/src/browser/browser.d.ts.map +1 -0
  39. package/dist/src/browser/browser.js +198 -0
  40. package/dist/src/browser/browser.js.map +1 -0
  41. package/dist/src/browser/cdp.d.ts +17 -0
  42. package/dist/src/browser/cdp.d.ts.map +1 -0
  43. package/dist/src/browser/cdp.js +106 -0
  44. package/dist/src/browser/cdp.js.map +1 -0
  45. package/dist/src/browser/console-log.d.ts +22 -0
  46. package/dist/src/browser/console-log.d.ts.map +1 -0
  47. package/dist/src/browser/console-log.js +55 -0
  48. package/dist/src/browser/console-log.js.map +1 -0
  49. package/dist/src/browser/dialog.d.ts +11 -0
  50. package/dist/src/browser/dialog.d.ts.map +1 -0
  51. package/dist/src/browser/dialog.js +36 -0
  52. package/dist/src/browser/dialog.js.map +1 -0
  53. package/dist/src/browser/emulation.d.ts +15 -0
  54. package/dist/src/browser/emulation.d.ts.map +1 -0
  55. package/dist/src/browser/emulation.js +62 -0
  56. package/dist/src/browser/emulation.js.map +1 -0
  57. package/dist/src/browser/find.d.ts +21 -0
  58. package/dist/src/browser/find.d.ts.map +1 -0
  59. package/dist/src/browser/find.js +118 -0
  60. package/dist/src/browser/find.js.map +1 -0
  61. package/dist/src/browser/index.d.ts +18 -0
  62. package/dist/src/browser/index.d.ts.map +1 -0
  63. package/dist/src/browser/index.js +18 -0
  64. package/dist/src/browser/index.js.map +1 -0
  65. package/dist/src/browser/network.d.ts +11 -0
  66. package/dist/src/browser/network.d.ts.map +1 -0
  67. package/dist/src/browser/network.js +81 -0
  68. package/dist/src/browser/network.js.map +1 -0
  69. package/dist/src/browser/pdf.d.ts +15 -0
  70. package/dist/src/browser/pdf.d.ts.map +1 -0
  71. package/dist/src/browser/pdf.js +27 -0
  72. package/dist/src/browser/pdf.js.map +1 -0
  73. package/dist/src/browser/screenshot.d.ts +15 -0
  74. package/dist/src/browser/screenshot.d.ts.map +1 -0
  75. package/dist/src/browser/screenshot.js +36 -0
  76. package/dist/src/browser/screenshot.js.map +1 -0
  77. package/dist/src/browser/session.d.ts +8 -0
  78. package/dist/src/browser/session.d.ts.map +1 -0
  79. package/dist/src/browser/session.js +50 -0
  80. package/dist/src/browser/session.js.map +1 -0
  81. package/dist/src/browser/snapshot.d.ts +12 -0
  82. package/dist/src/browser/snapshot.d.ts.map +1 -0
  83. package/dist/src/browser/snapshot.js +147 -0
  84. package/dist/src/browser/snapshot.js.map +1 -0
  85. package/dist/src/browser/storage.d.ts +11 -0
  86. package/dist/src/browser/storage.d.ts.map +1 -0
  87. package/dist/src/browser/storage.js +43 -0
  88. package/dist/src/browser/storage.js.map +1 -0
  89. package/dist/src/browser/tabs.d.ts +8 -0
  90. package/dist/src/browser/tabs.d.ts.map +1 -0
  91. package/dist/src/browser/tabs.js +25 -0
  92. package/dist/src/browser/tabs.js.map +1 -0
  93. package/dist/src/browser/types.d.ts +109 -0
  94. package/dist/src/browser/types.d.ts.map +1 -0
  95. package/dist/src/browser/types.js +16 -0
  96. package/dist/src/browser/types.js.map +1 -0
  97. package/dist/src/browser/wait.d.ts +4 -0
  98. package/dist/src/browser/wait.d.ts.map +1 -0
  99. package/dist/src/browser/wait.js +122 -0
  100. package/dist/src/browser/wait.js.map +1 -0
  101. package/dist/src/commands/browse.d.ts +8 -0
  102. package/dist/src/commands/browse.d.ts.map +1 -0
  103. package/dist/src/commands/browse.js +1494 -0
  104. package/dist/src/commands/browse.js.map +1 -0
  105. package/dist/src/commands/index.d.ts.map +1 -1
  106. package/dist/src/commands/index.js +2 -0
  107. package/dist/src/commands/index.js.map +1 -1
  108. package/dist/src/ui/dashboard-v2.html +1857 -0
  109. package/dist/src/ui/server.mjs +71 -1
  110. package/dist/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +2 -1
@@ -11,7 +11,7 @@
11
11
  * node auto-memory-hook.mjs status # Show bridge status
12
12
  */
13
13
 
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
14
+ import { existsSync, mkdirSync, openSync, closeSync, unlinkSync, readFileSync, writeFileSync, renameSync } from 'fs';
15
15
  import { join, dirname } from 'path';
16
16
  import { fileURLToPath } from 'url';
17
17
 
@@ -34,6 +34,37 @@ const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
34
34
  // Ensure data dir
35
35
  if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
36
36
 
37
+ // ============================================================================
38
+ // File-lock helper for concurrent hook coordination
39
+ // Uses O_EXCL exclusive-create as a spin lock.
40
+ // A 10ms synchronous wait (via Atomics.wait) is used between retries to avoid
41
+ // pegging the CPU. Acceptable for low-contention hook processes.
42
+ // ============================================================================
43
+
44
+ function withFileLock(lockPath, fn, timeoutMs = 5000) {
45
+ const start = Date.now();
46
+ const sharedBuf = new Int32Array(new SharedArrayBuffer(4));
47
+ while (true) {
48
+ try {
49
+ // O_EXCL: exclusive create — fails atomically if file already exists
50
+ const fd = openSync(lockPath, 'wx');
51
+ closeSync(fd);
52
+ break; // acquired lock
53
+ } catch {
54
+ if (Date.now() - start > timeoutMs) {
55
+ // Timeout: proceed without lock rather than deadlocking hook processes
56
+ try { fn(); } catch { /* best effort */ }
57
+ return;
58
+ }
59
+ // Synchronous 10ms sleep via Atomics.wait (avoids busy-spin CPU peg)
60
+ Atomics.wait(sharedBuf, 0, 0, 10);
61
+ }
62
+ }
63
+ try { fn(); } finally {
64
+ try { unlinkSync(lockPath); } catch { /* ignore */ }
65
+ }
66
+ }
67
+
37
68
  // ============================================================================
38
69
  // Simple JSON File Backend (implements IMemoryBackend interface)
39
70
  // ============================================================================
@@ -120,9 +151,13 @@ class JsonFileBackend {
120
151
  }
121
152
 
122
153
  _persist() {
123
- try {
124
- writeFileSync(this.filePath, JSON.stringify([...this.entries.values()], null, 2), 'utf-8');
125
- } catch { /* best effort */ }
154
+ const lockPath = this.filePath + '.lock';
155
+ withFileLock(lockPath, () => {
156
+ const data = JSON.stringify([...this.entries.values()], null, 2);
157
+ const tmpPath = this.filePath + '.tmp';
158
+ writeFileSync(tmpPath, data, 'utf-8');
159
+ renameSync(tmpPath, this.filePath);
160
+ });
126
161
  }
127
162
  }
128
163
 
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+ // Extracted from hook-handler.cjs — handles 'adr-draft' command.
3
+ // Drafts an ADR from accumulated decision markers in .monomind/decisions.jsonl.
4
+ // Receives hCtx from dispatcher. See route-handler.cjs for hCtx field docs.
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ module.exports = {
10
+ handle: function(hCtx) {
11
+ var CWD = hCtx.CWD;
12
+
13
+ var jsonl = path.join(CWD, '.monomind', 'decisions.jsonl');
14
+ if (!fs.existsSync(jsonl)) {
15
+ console.log('[ADR] No decisions recorded yet. Type prompts containing markers like "let\'s go with X", "we chose Y", "decision: Z" to populate the log.');
16
+ return;
17
+ }
18
+ var lines = fs.readFileSync(jsonl, 'utf-8').trim().split('\n').filter(Boolean);
19
+ if (lines.length === 0) {
20
+ console.log('[ADR] decisions.jsonl is empty.');
21
+ return;
22
+ }
23
+ // Group decisions captured in the last 7 days.
24
+ var cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
25
+ var recent = lines
26
+ .map(function(l) { try { return JSON.parse(l); } catch (_) { return null; } })
27
+ .filter(function(d) { return d && d.ts >= cutoff; });
28
+ if (recent.length === 0) {
29
+ console.log('[ADR] No decisions in the last 7 days. Older entries: ' + lines.length + '.');
30
+ return;
31
+ }
32
+
33
+ var adrsDir = path.join(CWD, 'docs', 'adrs');
34
+ try { fs.mkdirSync(adrsDir, { recursive: true }); } catch (_) {}
35
+ var existing = [];
36
+ try { existing = fs.readdirSync(adrsDir).filter(function(f) { return /^ADR-\d{4}/.test(f); }); } catch (_) {}
37
+ var nextNum = existing.length + 1;
38
+ var num = String(nextNum).padStart(4, '0');
39
+ var stamp = new Date().toISOString().slice(0, 10);
40
+ var fname = 'ADR-' + num + '-' + stamp + '-session-decisions.md';
41
+ var outPath = path.join(adrsDir, fname);
42
+
43
+ var body = '# ADR-' + num + ': Session decisions (' + stamp + ')\n\n' +
44
+ '**Status:** Proposed\n**Date:** ' + stamp + '\n\n' +
45
+ '## Context\n\n' +
46
+ 'During recent sessions, the following decision markers were captured ' +
47
+ 'from user prompts. Each excerpt is the surrounding sentence at the time.\n\n' +
48
+ '## Decisions\n\n';
49
+ for (var i = 0; i < recent.length; i++) {
50
+ var d = recent[i];
51
+ var date = new Date(d.ts).toISOString().slice(0, 16).replace('T', ' ');
52
+ body += '### ' + (i + 1) + '. ' + date + '\n\n';
53
+ for (var j = 0; j < d.excerpts.length; j++) {
54
+ body += '> ' + d.excerpts[j].trim() + '\n\n';
55
+ }
56
+ if (d.prompt) body += '_Prompt:_ ' + d.prompt.slice(0, 200) + (d.prompt.length > 200 ? '…' : '') + '\n\n';
57
+ }
58
+ body += '## Consequences\n\n_(fill in after review)_\n\n' +
59
+ '## Status\n\nProposed — awaiting human review and refinement.\n';
60
+ fs.writeFileSync(outPath, body);
61
+ console.log('[ADR_DRAFT] Wrote ' + recent.length + ' decision(s) to ' + outPath);
62
+ console.log(' Edit the file to fill in Context and Consequences, then change Status to Accepted/Rejected.');
63
+ },
64
+ };
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+ // Extracted from hook-handler.cjs — handles 'agent-start' (SubagentStart) hook event.
3
+ // Receives hCtx from dispatcher. See route-handler.cjs for hCtx field docs.
4
+
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+
8
+ module.exports = {
9
+ handle: function(hCtx) {
10
+ var hookInput = hCtx.hookInput;
11
+ var CWD = hCtx.CWD;
12
+ var _openMonographDb = hCtx._openMonographDb;
13
+ var getMonographSuggestions = hCtx.getMonographSuggestions;
14
+
15
+ // Register this agent so the statusline can count active agents.
16
+ const regDir = path.join(CWD, '.monomind', 'agents', 'registrations');
17
+ try {
18
+ fs.mkdirSync(regDir, { recursive: true });
19
+ const id = Date.now() + '-' + Math.random().toString(36).slice(2, 6);
20
+ const regFile = path.join(regDir, 'agent-' + id + '.json');
21
+ fs.writeFileSync(regFile, JSON.stringify({
22
+ agentId: id,
23
+ startedAt: new Date().toISOString(),
24
+ pid: process.pid,
25
+ }));
26
+ // Refresh swarm-activity.json within the 5-min staleness window.
27
+ const activityDir = path.join(CWD, '.monomind', 'metrics');
28
+ fs.mkdirSync(activityDir, { recursive: true });
29
+ const activityPath = path.join(activityDir, 'swarm-activity.json');
30
+ const active = fs.readdirSync(regDir).filter(f => f.endsWith('.json')).length;
31
+ // Preserve lastActive (peak) so statusline shows non-zero after completion.
32
+ let prevLastActive = 0;
33
+ try { prevLastActive = (JSON.parse(fs.readFileSync(activityPath, 'utf-8'))?.swarm?.lastActive) || 0; } catch { /* ignore */ }
34
+ fs.writeFileSync(activityPath, JSON.stringify({
35
+ timestamp: new Date().toISOString(),
36
+ swarm: {
37
+ active: active > 0,
38
+ agent_count: active,
39
+ coordination_active: active > 0,
40
+ lastActive: Math.max(active, prevLastActive),
41
+ },
42
+ }));
43
+
44
+ // Write last-dispatch.json so the route handler can suppress redundant
45
+ // suggestions on the next turn when the same agent type is recommended.
46
+ const agentType = hookInput.subagent_type || hookInput.agentType || hookInput.agent_type || hookInput.agentSlug || 'unknown';
47
+ const agentDesc = hookInput.description || hookInput.prompt_description || '';
48
+ fs.writeFileSync(
49
+ path.join(CWD, '.monomind', 'last-dispatch.json'),
50
+ JSON.stringify({
51
+ agentType: agentType,
52
+ description: agentDesc.substring(0, 120),
53
+ dispatchedAt: new Date().toISOString(),
54
+ }),
55
+ 'utf-8'
56
+ );
57
+ } catch (e) { /* non-fatal — never block a subagent from starting */ }
58
+
59
+ // Subagent context inheritance — inject graph god nodes + parent's last
60
+ // pre-resolved suggestions so the spawned agent inherits spatial map.
61
+ try {
62
+ var subDb = _openMonographDb();
63
+ if (subDb) {
64
+ try {
65
+ var godRows = subDb.prepare(
66
+ "SELECT n.name, n.label, n.file_path AS file, " +
67
+ "(SELECT COUNT(*) FROM edges WHERE source_id=n.id OR target_id=n.id) AS deg " +
68
+ "FROM nodes n " +
69
+ "WHERE n.label NOT IN ('Concept') AND n.file_path IS NOT NULL AND n.file_path != '' " +
70
+ "ORDER BY deg DESC LIMIT 5"
71
+ ).all();
72
+ if (godRows.length > 0) {
73
+ console.log('[MONOGRAPH_SUBAGENT_CTX] Graph map inherited from parent:');
74
+ for (var gi = 0; gi < godRows.length; gi++) {
75
+ var gr = godRows[gi];
76
+ console.log(' · ' + gr.name + ' [' + gr.label + '] — ' + (gr.file || '') + ' (deg ' + gr.deg + ')');
77
+ }
78
+ try {
79
+ var subAgentDesc = hookInput.description || hookInput.prompt_description || '';
80
+ if (subAgentDesc && subAgentDesc.length > 8) {
81
+ var subHints = getMonographSuggestions(subAgentDesc, 3);
82
+ if (subHints.length > 0) {
83
+ console.log(' Top files for this subagent task:');
84
+ for (var si2 = 0; si2 < subHints.length; si2++) {
85
+ var sh = subHints[si2];
86
+ console.log(' · ' + sh.name + ' [' + sh.label + '] — ' + (sh.file || ''));
87
+ }
88
+ }
89
+ }
90
+ } catch (_) {}
91
+ console.log(' Use mcp__monomind__monograph_suggest / monograph_query in this subagent before grepping.');
92
+ }
93
+ } catch (e) { /* non-fatal */ }
94
+ }
95
+ } catch (e) { /* non-fatal */ }
96
+
97
+ console.log('[OK] Agent registered');
98
+ },
99
+ };
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+ // Extracted from hook-handler.cjs — receives hCtx from dispatcher.
3
+ // Handles the 'post-edit' hook event.
4
+ // See route-handler.cjs for full hCtx field documentation.
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ module.exports = {
10
+ handle: async function(hCtx) {
11
+ var hookInput = hCtx.hookInput;
12
+ var toolInput = hCtx.toolInput;
13
+ var args = hCtx.args;
14
+ var session = hCtx.session;
15
+ var intelligence = hCtx.intelligence;
16
+ var CWD = hCtx.CWD;
17
+
18
+ if (session && session.metric) {
19
+ try { session.metric('edits'); } catch (e) { /* no active session */ }
20
+ }
21
+ if (intelligence && intelligence.recordEdit) {
22
+ try {
23
+ var file = hookInput.file_path || toolInput.file_path
24
+ || process.env.TOOL_INPUT_file_path || args[0] || '';
25
+ intelligence.recordEdit(file);
26
+ } catch (e) { /* non-fatal */ }
27
+ }
28
+ // Track recently-edited files for compact injection and pre-resolve boosting.
29
+ try {
30
+ var editedForRecent = hookInput.file_path || toolInput.file_path
31
+ || process.env.TOOL_INPUT_file_path || args[0] || '';
32
+ if (editedForRecent) hCtx._recordRecentEdit(editedForRecent);
33
+ } catch (e) { /* non-fatal */ }
34
+ // Increment write counter and rebuild monograph when threshold hit.
35
+ hCtx._maybeRebuildMonograph();
36
+
37
+ // Test feedback (detection-only): when editing a source file, list tests
38
+ // that import it so the LLM/user knows what to verify next.
39
+ try {
40
+ var editedFile = hookInput.file_path || toolInput.file_path
41
+ || process.env.TOOL_INPUT_file_path || args[0] || '';
42
+ if (editedFile && !editedFile.match(/\.(test|spec)\./) && !editedFile.includes('__tests__')) {
43
+ var affectedTests = hCtx._findAffectedTests(editedFile);
44
+ if (affectedTests.length > 0) {
45
+ console.log('[AFFECTED_TESTS] ' + affectedTests.length + ' test(s) cover this file:');
46
+ for (var ti = 0; ti < Math.min(5, affectedTests.length); ti++) {
47
+ console.log(' · ' + affectedTests[ti]);
48
+ }
49
+ }
50
+ }
51
+ } catch (e) {}
52
+ // ── Security-Sensitive File Auto-Alert ────────────────────────────────────
53
+ // When editing auth, security, crypto, or env-related files, flag it
54
+ try {
55
+ var editFile = (hookInput.file_path || toolInput.file_path
56
+ || process.env.TOOL_INPUT_file_path || args[0] || '').toLowerCase();
57
+ var securityPatterns = /\b(auth|security|crypto|secret|credential|token|password|\.env|permission|acl|rbac|jwt|oauth|session|cookie)\b/;
58
+ if (securityPatterns.test(editFile) || editFile.includes('/security/') || editFile.includes('/auth/')) {
59
+ console.log('[SECURITY_EDIT] Security-sensitive file modified: ' + path.basename(editFile));
60
+ console.log('[SECURITY_EDIT] INSTRUCTION: Consider running a security review. Invoke Skill("code-review:code-review") with security focus, or run: npx monomind security scan --path "' + editFile + '"');
61
+ }
62
+ } catch (e) { /* non-fatal */ }
63
+
64
+ // ── Smart Test/Build Suggestions (PE-001) ───────────────────────────
65
+ try {
66
+ var editFile2 = (hookInput.file_path || toolInput.file_path
67
+ || process.env.TOOL_INPUT_file_path || args[0] || '');
68
+ var editBase = path.basename(editFile2).toLowerCase();
69
+ if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(editBase)) {
70
+ console.log('[AUTO_SUGGEST] Test file modified — run: npm test -- --testPathPattern="' + path.basename(editFile2) + '"');
71
+ } else if (editBase === 'package.json') {
72
+ console.log('[AUTO_SUGGEST] package.json changed — consider running: npm install');
73
+ } else if (editBase === 'tsconfig.json' || editBase === 'tsconfig.base.json') {
74
+ console.log('[AUTO_SUGGEST] TypeScript config changed — consider running: npm run build');
75
+ }
76
+ } catch (e) { /* non-fatal */ }
77
+
78
+ // ── Monograph Incremental Rebuild ─────────────────────────────────────
79
+ // After every code file edit, trigger a background monograph rebuild so
80
+ // the knowledge graph stays current. Debounced via a lock file (5s cooldown).
81
+ try {
82
+ var editedFile2 = (hookInput.file_path || toolInput.file_path
83
+ || process.env.TOOL_INPUT_file_path || args[0] || '');
84
+ var codeExts = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|cs|cpp|c|rb|swift|php)$/i;
85
+ if (editedFile2 && codeExts.test(editedFile2)) {
86
+ var lockFile = path.join(CWD, '.monomind', 'graph', '.rebuild-lock');
87
+ var now = Date.now();
88
+ var lastBuild = 0;
89
+ try { lastBuild = parseInt(fs.readFileSync(lockFile, 'utf-8').trim(), 10) || 0; } catch (e) {}
90
+ var COOLDOWN_MS = 5000; // 5-second debounce
91
+ if (now - lastBuild > COOLDOWN_MS) {
92
+ fs.writeFileSync(lockFile, String(now), 'utf-8');
93
+ var { spawn: spawnRebuild } = require('child_process');
94
+ var rebuildScript = "import { buildAsync } from '@monoes/monograph'; await buildAsync(" + JSON.stringify(CWD) + ");";
95
+ var graphDir = path.join(CWD, '.monomind', 'graph');
96
+ var logPath = path.join(graphDir, 'build.log');
97
+ var logFd;
98
+ try { logFd = fs.openSync(logPath, 'a'); } catch(e) { logFd = 'ignore'; }
99
+ var child = spawnRebuild(process.execPath, ['--input-type=module', '--eval', rebuildScript], {
100
+ detached: true, stdio: ['ignore', logFd, logFd], cwd: CWD,
101
+ });
102
+ child.unref();
103
+ console.log('[MONOGRAPH] Incremental rebuild triggered for ' + path.basename(editedFile2));
104
+
105
+ // Option C: fire ua-enrich.mjs in background after monograph rebuild
106
+ var uaEnrichScript = path.join(CWD, 'scripts', 'ua-enrich.mjs');
107
+ if (fs.existsSync(uaEnrichScript)) {
108
+ var uaChild = spawnRebuild(process.execPath, [uaEnrichScript, '--dir', CWD, '--file', editedFile2, '--db', path.join(CWD, '.monomind', 'monograph.db')], {
109
+ detached: true, stdio: 'ignore', cwd: CWD,
110
+ });
111
+ uaChild.unref();
112
+ }
113
+ }
114
+ // Show importers of the edited file so Claude sees blast radius
115
+ try {
116
+ var mgDbPath4 = path.join(CWD, '.monomind', 'monograph.db');
117
+ if (fs.existsSync(mgDbPath4)) {
118
+ var mgMod4 = null;
119
+ var _requireMonograph4 = hCtx._requireMonograph;
120
+ mgMod4 = _requireMonograph4 ? _requireMonograph4() : null;
121
+ if (mgMod4 && mgMod4.openDb) {
122
+ var db4 = mgMod4.openDb(mgDbPath4);
123
+ try {
124
+ var editedBase4 = path.basename(editedFile2).replace(/\.[^.]+$/, '');
125
+ var editNode4 = db4.prepare("SELECT id, name, label FROM nodes WHERE file_path LIKE ? OR name = ? LIMIT 1")
126
+ .get('%' + path.sep + path.basename(editedFile2), editedBase4);
127
+ if (editNode4) {
128
+ var editImporters4 = db4.prepare(
129
+ 'SELECT n2.name FROM edges e JOIN nodes n2 ON n2.id = e.source_id WHERE e.target_id = ? LIMIT 8'
130
+ ).all(editNode4.id);
131
+ if (editImporters4.length > 0) {
132
+ console.log('[MONOGRAPH_IMPACT] ' + editNode4.name + ' (' + editNode4.label + ') is depended on by: ' +
133
+ editImporters4.map(function(i) { return i.name; }).join(', '));
134
+ }
135
+ }
136
+ } finally { if (mgMod4.closeDb) mgMod4.closeDb(db4); }
137
+ }
138
+ }
139
+ } catch(e) { /* non-fatal */ }
140
+ }
141
+ } catch (e) { /* non-fatal */ }
142
+
143
+ console.log('[OK] Edit recorded');
144
+ }
145
+ };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+ // Extracted from hook-handler.cjs — handles 'graph-status' command.
3
+ // Shows monograph node/edge counts and graph-vs-grep usage ratio.
4
+ // Receives hCtx from dispatcher. See route-handler.cjs for hCtx field docs.
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+
9
+ module.exports = {
10
+ handle: function(hCtx) {
11
+ var CWD = hCtx.CWD;
12
+ var _openMonographDb = hCtx._openMonographDb;
13
+
14
+ var db = _openMonographDb();
15
+ if (!db) {
16
+ console.log('No monograph.db found. Run /monomind:understand to build.');
17
+ return;
18
+ }
19
+ try {
20
+ var n = db.prepare("SELECT COUNT(*) AS c FROM nodes").get().c;
21
+ var e = db.prepare("SELECT COUNT(*) AS c FROM edges").get().c;
22
+ var usage = (function() {
23
+ try { return JSON.parse(fs.readFileSync(path.join(CWD, '.monomind', 'metrics', 'graph-usage.json'), 'utf-8')); }
24
+ catch (_) { return {}; }
25
+ })();
26
+ var wins = (usage.monograph_call || 0) + (usage.preresolve_hit || 0)
27
+ + (usage.graph_assist_search || 0) + (usage.graph_assist_neighbors || 0);
28
+ var search = (usage.grep_call || 0) + (usage.glob_call || 0)
29
+ + (usage.bash_grep_call || 0) + (usage.bash_find_call || 0);
30
+ var pct = (wins + search) > 0 ? Math.round((wins / (wins + search)) * 100) : 0;
31
+ var saved = usage.dollars_saved || 0;
32
+ console.log('Monograph: ' + n.toLocaleString() + ' nodes · ' + e.toLocaleString() + ' edges');
33
+ console.log('Usage: ' + pct + '% graph · ' + (100 - pct) + '% grep · ' +
34
+ 'wins=' + wins + ' search=' + search +
35
+ (saved > 0 ? ' · saved $' + saved.toFixed(2) : ''));
36
+ } catch (err) { console.log('Error: ' + err.message); }
37
+ },
38
+ };