@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.
- package/.claude/helpers/auto-memory-hook.mjs +39 -4
- package/.claude/helpers/handlers/adr-draft-handler.cjs +64 -0
- package/.claude/helpers/handlers/agent-start-handler.cjs +99 -0
- package/.claude/helpers/handlers/edit-handler.cjs +145 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +38 -0
- package/.claude/helpers/handlers/route-handler.cjs +393 -0
- package/.claude/helpers/handlers/session-handler.cjs +167 -0
- package/.claude/helpers/handlers/session-restore-handler.cjs +348 -0
- package/.claude/helpers/handlers/task-handler.cjs +329 -0
- package/.claude/helpers/hook-handler.cjs +120 -2431
- package/.claude/helpers/intelligence.cjs +21 -2
- package/.claude/helpers/learning-service.mjs +166 -8
- package/.claude/helpers/memory-palace.cjs +72 -12
- package/.claude/helpers/router.cjs +79 -5
- package/.claude/helpers/statusline.cjs +193 -399
- package/.claude/helpers/utils/micro-agents.cjs +338 -0
- package/.claude/helpers/utils/monograph.cjs +349 -0
- package/.claude/helpers/utils/telemetry.cjs +144 -0
- package/.claude/skills/agent-browser-testing/SKILL.md +3 -2
- package/.claude/skills/monomind/browse-agentcore.md +116 -0
- package/.claude/skills/monomind/browse-electron.md +189 -0
- package/.claude/skills/monomind/browse-qa.md +229 -0
- package/.claude/skills/monomind/browse-references/authentication.md +162 -0
- package/.claude/skills/monomind/browse-references/trust-boundaries.md +41 -0
- package/.claude/skills/monomind/browse-references/video-recording.md +84 -0
- package/.claude/skills/monomind/browse-slack.md +189 -0
- package/.claude/skills/monomind/browse-vercel.md +240 -0
- package/.claude/skills/monomind/browse.md +724 -0
- package/dist/src/browser/actions.d.ts +28 -0
- package/dist/src/browser/actions.d.ts.map +1 -0
- package/dist/src/browser/actions.js +292 -0
- package/dist/src/browser/actions.js.map +1 -0
- package/dist/src/browser/batch.d.ts +13 -0
- package/dist/src/browser/batch.d.ts.map +1 -0
- package/dist/src/browser/batch.js +11 -0
- package/dist/src/browser/batch.js.map +1 -0
- package/dist/src/browser/browser.d.ts +14 -0
- package/dist/src/browser/browser.d.ts.map +1 -0
- package/dist/src/browser/browser.js +198 -0
- package/dist/src/browser/browser.js.map +1 -0
- package/dist/src/browser/cdp.d.ts +17 -0
- package/dist/src/browser/cdp.d.ts.map +1 -0
- package/dist/src/browser/cdp.js +106 -0
- package/dist/src/browser/cdp.js.map +1 -0
- package/dist/src/browser/console-log.d.ts +22 -0
- package/dist/src/browser/console-log.d.ts.map +1 -0
- package/dist/src/browser/console-log.js +55 -0
- package/dist/src/browser/console-log.js.map +1 -0
- package/dist/src/browser/dialog.d.ts +11 -0
- package/dist/src/browser/dialog.d.ts.map +1 -0
- package/dist/src/browser/dialog.js +36 -0
- package/dist/src/browser/dialog.js.map +1 -0
- package/dist/src/browser/emulation.d.ts +15 -0
- package/dist/src/browser/emulation.d.ts.map +1 -0
- package/dist/src/browser/emulation.js +62 -0
- package/dist/src/browser/emulation.js.map +1 -0
- package/dist/src/browser/find.d.ts +21 -0
- package/dist/src/browser/find.d.ts.map +1 -0
- package/dist/src/browser/find.js +118 -0
- package/dist/src/browser/find.js.map +1 -0
- package/dist/src/browser/index.d.ts +18 -0
- package/dist/src/browser/index.d.ts.map +1 -0
- package/dist/src/browser/index.js +18 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/network.d.ts +11 -0
- package/dist/src/browser/network.d.ts.map +1 -0
- package/dist/src/browser/network.js +81 -0
- package/dist/src/browser/network.js.map +1 -0
- package/dist/src/browser/pdf.d.ts +15 -0
- package/dist/src/browser/pdf.d.ts.map +1 -0
- package/dist/src/browser/pdf.js +27 -0
- package/dist/src/browser/pdf.js.map +1 -0
- package/dist/src/browser/screenshot.d.ts +15 -0
- package/dist/src/browser/screenshot.d.ts.map +1 -0
- package/dist/src/browser/screenshot.js +36 -0
- package/dist/src/browser/screenshot.js.map +1 -0
- package/dist/src/browser/session.d.ts +8 -0
- package/dist/src/browser/session.d.ts.map +1 -0
- package/dist/src/browser/session.js +50 -0
- package/dist/src/browser/session.js.map +1 -0
- package/dist/src/browser/snapshot.d.ts +12 -0
- package/dist/src/browser/snapshot.d.ts.map +1 -0
- package/dist/src/browser/snapshot.js +147 -0
- package/dist/src/browser/snapshot.js.map +1 -0
- package/dist/src/browser/storage.d.ts +11 -0
- package/dist/src/browser/storage.d.ts.map +1 -0
- package/dist/src/browser/storage.js +43 -0
- package/dist/src/browser/storage.js.map +1 -0
- package/dist/src/browser/tabs.d.ts +8 -0
- package/dist/src/browser/tabs.d.ts.map +1 -0
- package/dist/src/browser/tabs.js +25 -0
- package/dist/src/browser/tabs.js.map +1 -0
- package/dist/src/browser/types.d.ts +109 -0
- package/dist/src/browser/types.d.ts.map +1 -0
- package/dist/src/browser/types.js +16 -0
- package/dist/src/browser/types.js.map +1 -0
- package/dist/src/browser/wait.d.ts +4 -0
- package/dist/src/browser/wait.d.ts.map +1 -0
- package/dist/src/browser/wait.js +122 -0
- package/dist/src/browser/wait.js.map +1 -0
- package/dist/src/commands/browse.d.ts +8 -0
- package/dist/src/commands/browse.d.ts.map +1 -0
- package/dist/src/commands/browse.js +1494 -0
- package/dist/src/commands/browse.js.map +1 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/ui/dashboard-v2.html +1857 -0
- package/dist/src/ui/server.mjs +71 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Extracted from hook-handler.cjs — handles 'session-restore' 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
|
+
const { injectGodNodesContext } = require('../utils/monograph.cjs');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
handleRestore: async function(hCtx) {
|
|
11
|
+
var hookInput = hCtx.hookInput;
|
|
12
|
+
var session = hCtx.session;
|
|
13
|
+
var intelligence = hCtx.intelligence;
|
|
14
|
+
var CWD = hCtx.CWD;
|
|
15
|
+
var helpersDir = hCtx.helpersDir;
|
|
16
|
+
var runWithTimeout = hCtx.runWithTimeout;
|
|
17
|
+
var _autoIndexKnowledge = hCtx._autoIndexKnowledge;
|
|
18
|
+
var _buildKnowledgeSearchFn = hCtx._buildKnowledgeSearchFn;
|
|
19
|
+
var getMonographSuggestions = hCtx.getMonographSuggestions;
|
|
20
|
+
|
|
21
|
+
// Session restore / start
|
|
22
|
+
try {
|
|
23
|
+
if (session) {
|
|
24
|
+
var existing = session.restore && session.restore();
|
|
25
|
+
if (!existing) {
|
|
26
|
+
session.start && session.start();
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
console.log('[OK] Session restored: session-' + Date.now());
|
|
30
|
+
}
|
|
31
|
+
} catch (e) { console.log('[WARN] Session restore failed: ' + e.message); }
|
|
32
|
+
|
|
33
|
+
// Stale helper detection — warn when project helpers drift from the bundled npm copy.
|
|
34
|
+
// Skip when running inside the monomind dev repo itself: local helpers ARE the
|
|
35
|
+
// source of truth there, so any diff vs. the npm global install is expected.
|
|
36
|
+
try {
|
|
37
|
+
var _isDevRepo = fs.existsSync(path.join(CWD, 'packages', '@monomind', 'cli', 'package.json'));
|
|
38
|
+
if (!_isDevRepo) {
|
|
39
|
+
var crypto = require('crypto');
|
|
40
|
+
function _findBundledHelpers() {
|
|
41
|
+
var helperPaths = [
|
|
42
|
+
path.join(helpersDir),
|
|
43
|
+
path.join(CWD, 'node_modules', 'monomind', '.claude', 'helpers'),
|
|
44
|
+
path.join(CWD, 'node_modules', '@monoes', 'monomindcli', '.claude', 'helpers'),
|
|
45
|
+
];
|
|
46
|
+
try {
|
|
47
|
+
var globalRoot = require('child_process')
|
|
48
|
+
.execSync('npm root -g 2>/dev/null', { encoding: 'utf-8', timeout: 2000 })
|
|
49
|
+
.trim();
|
|
50
|
+
if (globalRoot) {
|
|
51
|
+
helperPaths.push(path.join(globalRoot, 'monomind', '.claude', 'helpers'));
|
|
52
|
+
helperPaths.push(path.join(globalRoot, '@monoes', 'monomindcli', '.claude', 'helpers'));
|
|
53
|
+
}
|
|
54
|
+
} catch (_) {}
|
|
55
|
+
for (var i = 0; i < helperPaths.length; i++) {
|
|
56
|
+
if (fs.existsSync(path.join(helperPaths[i], 'hook-handler.cjs')) &&
|
|
57
|
+
helperPaths[i] !== path.join(CWD, '.claude', 'helpers')) {
|
|
58
|
+
return helperPaths[i];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var bundledDir = _findBundledHelpers();
|
|
65
|
+
if (bundledDir) {
|
|
66
|
+
var helpersToCheck = ['hook-handler.cjs', 'statusline.cjs'];
|
|
67
|
+
var stale = [];
|
|
68
|
+
for (var hi = 0; hi < helpersToCheck.length; hi++) {
|
|
69
|
+
var hName = helpersToCheck[hi];
|
|
70
|
+
var localF = path.join(CWD, '.claude', 'helpers', hName);
|
|
71
|
+
var bundledF = path.join(bundledDir, hName);
|
|
72
|
+
if (!fs.existsSync(localF) || !fs.existsSync(bundledF)) continue;
|
|
73
|
+
try {
|
|
74
|
+
var hashL = crypto.createHash('sha256').update(fs.readFileSync(localF)).digest('hex');
|
|
75
|
+
var hashB = crypto.createHash('sha256').update(fs.readFileSync(bundledF)).digest('hex');
|
|
76
|
+
if (hashL !== hashB) stale.push(hName);
|
|
77
|
+
} catch (_) {}
|
|
78
|
+
}
|
|
79
|
+
if (stale.length > 0) {
|
|
80
|
+
console.log('[STALE_HELPERS] Project helpers differ from bundled version: ' + stale.join(', '));
|
|
81
|
+
console.log(' Run `npx monomind@latest init upgrade` to refresh and pick up the latest features.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) { /* non-fatal */ }
|
|
86
|
+
|
|
87
|
+
// Initialize intelligence — respects monomind.neural.enabled kill switch.
|
|
88
|
+
var neuralEnabled = true;
|
|
89
|
+
try {
|
|
90
|
+
var settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
91
|
+
if (fs.existsSync(settingsPath)) {
|
|
92
|
+
var settingsData = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
93
|
+
if (settingsData.monomind && settingsData.monomind.neural && settingsData.monomind.neural.enabled === false) {
|
|
94
|
+
neuralEnabled = false;
|
|
95
|
+
console.log('[NEURAL] Disabled via monomind.neural.enabled=false');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (e) { /* non-fatal */ }
|
|
99
|
+
if (neuralEnabled && intelligence && intelligence.init) {
|
|
100
|
+
var initResult = await runWithTimeout(function() { return intelligence.init(); }, 'intelligence.init()');
|
|
101
|
+
if (initResult && initResult.nodes > 0) {
|
|
102
|
+
console.log('[INTELLIGENCE] Loaded ' + initResult.nodes + ' patterns, ' + initResult.edges + ' edges');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Bridge to @monomind/hooks compiled workers (GAP-001).
|
|
107
|
+
try {
|
|
108
|
+
var hooksModule = await import('@monomind/hooks');
|
|
109
|
+
if (hooksModule && hooksModule.initDefaultWorkers) {
|
|
110
|
+
await runWithTimeout(function() { return hooksModule.initDefaultWorkers(); }, '@monomind/hooks.initDefaultWorkers()');
|
|
111
|
+
hCtx._hooksModule = hooksModule;
|
|
112
|
+
console.log('[INFO] @monomind/hooks workers initialized');
|
|
113
|
+
}
|
|
114
|
+
} catch (e) { /* @monomind/hooks not compiled yet — skip */ }
|
|
115
|
+
|
|
116
|
+
// Context Persistence Auto-Restore
|
|
117
|
+
try {
|
|
118
|
+
var cpHook = await import('file://' + path.join(helpersDir, 'context-persistence-hook.mjs'));
|
|
119
|
+
var restoreFn = (cpHook && cpHook.restore) || (cpHook && cpHook.default && cpHook.default.restore);
|
|
120
|
+
if (restoreFn) {
|
|
121
|
+
var restored = await runWithTimeout(function() { return restoreFn(); }, 'context-persistence.restore()');
|
|
122
|
+
if (restored && restored.turns > 0) {
|
|
123
|
+
console.log('[CONTEXT_RESTORED] ' + restored.turns + ' turns from previous session');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (e) { /* non-fatal */ }
|
|
127
|
+
|
|
128
|
+
// AgentKnowledgeBase — preload shared knowledge context on session restore.
|
|
129
|
+
try {
|
|
130
|
+
var knowledgeDir = path.join(CWD, '.monomind', 'knowledge');
|
|
131
|
+
var indexed = _autoIndexKnowledge(knowledgeDir);
|
|
132
|
+
if (indexed > 0) {
|
|
133
|
+
console.log('[KNOWLEDGE_INDEXED] ' + indexed + ' chunks written from project sources');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var kSearchFn = _buildKnowledgeSearchFn(knowledgeDir);
|
|
137
|
+
var sessionCtx = (hookInput && (hookInput.sessionId || hookInput.session_id))
|
|
138
|
+
? 'session context: ' + (hookInput.sessionId || hookInput.session_id)
|
|
139
|
+
: 'project context general';
|
|
140
|
+
|
|
141
|
+
var memoryMod = null;
|
|
142
|
+
try { memoryMod = await import('@monomind/memory'); } catch (e) {}
|
|
143
|
+
|
|
144
|
+
if (memoryMod && memoryMod.KnowledgeStore && memoryMod.KnowledgeRetriever) {
|
|
145
|
+
var kStore = new memoryMod.KnowledgeStore(knowledgeDir);
|
|
146
|
+
var kRetriever = new memoryMod.KnowledgeRetriever(kSearchFn, kStore);
|
|
147
|
+
var kResult = await kRetriever.retrieveForTask('shared', sessionCtx, 5);
|
|
148
|
+
if (kResult.excerpts.length > 0) {
|
|
149
|
+
console.log('[KNOWLEDGE_PRELOADED] ' + kResult.excerpts.length + ' excerpts (KnowledgeRetriever)');
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
var directResults = await kSearchFn(sessionCtx, { namespace: 'knowledge:shared', limit: 5, minScore: 0.3 });
|
|
153
|
+
if (directResults.length > 0) {
|
|
154
|
+
console.log('[KNOWLEDGE_PRELOADED] ' + directResults.length + ' excerpts (direct keyword search)');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (e) { /* non-fatal */ }
|
|
158
|
+
|
|
159
|
+
// Monograph Context Injection — delegates to shared helper in utils/monograph.cjs.
|
|
160
|
+
injectGodNodesContext(CWD);
|
|
161
|
+
|
|
162
|
+
// SharedInstructions — auto-load .agents/shared_instructions.md (hard limit: 1500 chars).
|
|
163
|
+
var SI_CHAR_LIMIT = 1500;
|
|
164
|
+
var applySharedInstrLimit = function(content, source) {
|
|
165
|
+
if (content.length > SI_CHAR_LIMIT) {
|
|
166
|
+
console.warn('[SHARED_INSTRUCTIONS_OVERLIMIT] ' + content.length + ' chars exceeds limit of ' + SI_CHAR_LIMIT +
|
|
167
|
+
' — truncating. Edit ' + source + ' to stay under limit.');
|
|
168
|
+
return content.slice(0, SI_CHAR_LIMIT) + '\n… [truncated — file exceeds ' + SI_CHAR_LIMIT + ' char limit]';
|
|
169
|
+
}
|
|
170
|
+
return content;
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
var siMod = await import('file://' + path.join(CWD, 'packages/@monomind/cli/dist/src/agents/shared-instructions-loader.js'));
|
|
174
|
+
var loader = siMod.sharedInstructionsLoader || (siMod.SharedInstructionsLoader ? new siMod.SharedInstructionsLoader() : null);
|
|
175
|
+
if (loader) {
|
|
176
|
+
var sharedInstr = loader.getSharedInstructions(CWD);
|
|
177
|
+
if (sharedInstr) {
|
|
178
|
+
var sharedInstrSafe = applySharedInstrLimit(sharedInstr, '.agents/shared_instructions.md');
|
|
179
|
+
console.log('[SHARED_INSTRUCTIONS] Loaded ' + sharedInstrSafe.length + ' chars from .agents/shared_instructions.md');
|
|
180
|
+
console.log(sharedInstrSafe);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
try {
|
|
185
|
+
var siPath = path.join(CWD, '.agents', 'shared_instructions.md');
|
|
186
|
+
if (fs.existsSync(siPath)) {
|
|
187
|
+
var siContent = fs.readFileSync(siPath, 'utf-8');
|
|
188
|
+
var siContentSafe = applySharedInstrLimit(siContent, siPath);
|
|
189
|
+
console.log('[SHARED_INSTRUCTIONS] Loaded ' + siContentSafe.length + ' chars from .agents/shared_instructions.md');
|
|
190
|
+
console.log(siContentSafe);
|
|
191
|
+
}
|
|
192
|
+
} catch (e2) { /* non-fatal */ }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Memory Palace — inject L0 (identity) + L1 (essential story) into session context.
|
|
196
|
+
try {
|
|
197
|
+
var palace = require(path.join(helpersDir, 'memory-palace.cjs'));
|
|
198
|
+
var palaceContext = palace.wakeUp(CWD);
|
|
199
|
+
if (palaceContext) {
|
|
200
|
+
console.log(palaceContext);
|
|
201
|
+
}
|
|
202
|
+
} catch (e) { /* non-fatal — palace not available */ }
|
|
203
|
+
|
|
204
|
+
// Periodic Update Check (once per day).
|
|
205
|
+
try {
|
|
206
|
+
var updateCheckFile = path.join(CWD, '.monomind', 'last-update-check.json');
|
|
207
|
+
var shouldCheck = true;
|
|
208
|
+
if (fs.existsSync(updateCheckFile)) {
|
|
209
|
+
var lastCheck = JSON.parse(fs.readFileSync(updateCheckFile, 'utf-8'));
|
|
210
|
+
var hoursSince = (Date.now() - new Date(lastCheck.timestamp).getTime()) / (1000 * 60 * 60);
|
|
211
|
+
if (hoursSince < 24) shouldCheck = false;
|
|
212
|
+
}
|
|
213
|
+
if (shouldCheck) {
|
|
214
|
+
fs.mkdirSync(path.join(CWD, '.monomind'), { recursive: true });
|
|
215
|
+
fs.writeFileSync(updateCheckFile, JSON.stringify({ timestamp: new Date().toISOString() }), 'utf-8');
|
|
216
|
+
try {
|
|
217
|
+
var localPkg = path.join(CWD, 'packages/@monomind/cli/package.json');
|
|
218
|
+
if (fs.existsSync(localPkg)) {
|
|
219
|
+
var localVer = JSON.parse(fs.readFileSync(localPkg, 'utf-8')).version;
|
|
220
|
+
if (localVer) {
|
|
221
|
+
var spawnFn = require('child_process').spawn;
|
|
222
|
+
var child = spawnFn('npm', ['view', '@monomind/cli', 'version'], {
|
|
223
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
224
|
+
shell: false,
|
|
225
|
+
});
|
|
226
|
+
child.on('error', function() {});
|
|
227
|
+
var out = '';
|
|
228
|
+
child.stdout.on('data', function(d) { out += d; });
|
|
229
|
+
child.on('close', function() {
|
|
230
|
+
var current = out.trim();
|
|
231
|
+
var pendingUpdatePath = path.join(CWD, '.monomind', 'pending-update.json');
|
|
232
|
+
if (current && current !== localVer) {
|
|
233
|
+
try {
|
|
234
|
+
fs.writeFileSync(
|
|
235
|
+
pendingUpdatePath,
|
|
236
|
+
JSON.stringify({ from: localVer, to: current, checkedAt: new Date().toISOString() }),
|
|
237
|
+
'utf-8'
|
|
238
|
+
);
|
|
239
|
+
} catch (e2) {}
|
|
240
|
+
} else if (current) {
|
|
241
|
+
try { fs.unlinkSync(pendingUpdatePath); } catch (e2) {}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
child.unref();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (e) { /* npm not available */ }
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
var pendingUpdate = path.join(CWD, '.monomind', 'pending-update.json');
|
|
251
|
+
if (fs.existsSync(pendingUpdate)) {
|
|
252
|
+
var upd = JSON.parse(fs.readFileSync(pendingUpdate, 'utf-8'));
|
|
253
|
+
if (upd && upd.from && upd.to && upd.from !== upd.to) {
|
|
254
|
+
console.log('[UPDATE_AVAILABLE] @monomind/cli ' + upd.from + ' → ' + upd.to + ' (run: npx monomind update)');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (e) {}
|
|
258
|
+
} catch (e) { /* non-fatal */ }
|
|
259
|
+
|
|
260
|
+
// Daemon Auto-Start Check.
|
|
261
|
+
try {
|
|
262
|
+
var daemonPid = path.join(CWD, '.monomind', 'daemon.pid');
|
|
263
|
+
var daemonRunning = false;
|
|
264
|
+
if (fs.existsSync(daemonPid)) {
|
|
265
|
+
try {
|
|
266
|
+
var pid = parseInt(fs.readFileSync(daemonPid, 'utf-8').trim(), 10);
|
|
267
|
+
process.kill(pid, 0);
|
|
268
|
+
daemonRunning = true;
|
|
269
|
+
} catch (e) { /* pid stale */ }
|
|
270
|
+
}
|
|
271
|
+
if (!daemonRunning) {
|
|
272
|
+
var daemonCfg = {};
|
|
273
|
+
try {
|
|
274
|
+
var cfgPath = path.join(CWD, 'monomind.config.json');
|
|
275
|
+
if (fs.existsSync(cfgPath)) daemonCfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8')).daemon || {};
|
|
276
|
+
} catch (e) {}
|
|
277
|
+
if (daemonCfg.autoStart) {
|
|
278
|
+
var spawn = require('child_process').spawn;
|
|
279
|
+
var daemonChild = spawn('npx', ['monomind', 'daemon', 'start'], {
|
|
280
|
+
cwd: CWD, detached: true, stdio: 'ignore'
|
|
281
|
+
});
|
|
282
|
+
daemonChild.on('error', function() {});
|
|
283
|
+
daemonChild.unref();
|
|
284
|
+
console.log('[DAEMON_AUTOSTART] Background daemon started (pid ' + daemonChild.pid + ')');
|
|
285
|
+
} else {
|
|
286
|
+
console.log('[DAEMON_STOPPED] Background daemon is not running. To auto-start, set daemon.autoStart=true in monomind.config.json or run: npx monomind daemon start');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (e) { /* non-fatal */ }
|
|
290
|
+
|
|
291
|
+
// Token Usage — inject daily/monthly cost summary.
|
|
292
|
+
try {
|
|
293
|
+
var tokenTracker = require(path.join(helpersDir, 'token-tracker.cjs'));
|
|
294
|
+
var tokenSummary = tokenTracker.quickSummary();
|
|
295
|
+
if (tokenSummary) {
|
|
296
|
+
console.log(tokenSummary);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
var tokenData = tokenTracker.quickSummaryData();
|
|
300
|
+
if (tokenData) {
|
|
301
|
+
var metricsDir = path.join(CWD, '.monomind', 'metrics');
|
|
302
|
+
if (!fs.existsSync(metricsDir)) fs.mkdirSync(metricsDir, { recursive: true });
|
|
303
|
+
tokenData.cachedAt = new Date().toISOString();
|
|
304
|
+
fs.writeFileSync(path.join(metricsDir, 'token-summary.json'), JSON.stringify(tokenData), 'utf-8');
|
|
305
|
+
}
|
|
306
|
+
} catch (_) { /* ignore cache write failure */ }
|
|
307
|
+
} catch (e) { /* non-fatal — token tracker not available */ }
|
|
308
|
+
|
|
309
|
+
// Registry Surfacing (SR-001) — show agent count.
|
|
310
|
+
try {
|
|
311
|
+
var regPath = path.join(CWD, '.monomind', 'registry.json');
|
|
312
|
+
if (fs.existsSync(regPath)) {
|
|
313
|
+
var reg = JSON.parse(fs.readFileSync(regPath, 'utf-8'));
|
|
314
|
+
var agentCount = (reg.agents || []).length;
|
|
315
|
+
if (agentCount > 0) {
|
|
316
|
+
console.log('[REGISTRY] ' + agentCount + ' agents available in registry');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (e) { /* non-fatal */ }
|
|
320
|
+
|
|
321
|
+
// Monomind Control UI Status.
|
|
322
|
+
try {
|
|
323
|
+
var http = require('http');
|
|
324
|
+
var controlPort = 4242;
|
|
325
|
+
var req = http.get('http://localhost:' + controlPort + '/', function(res) {
|
|
326
|
+
if (res.statusCode === 200) {
|
|
327
|
+
console.log('[CONTROL_UI] UP — http://localhost:' + controlPort);
|
|
328
|
+
}
|
|
329
|
+
res.resume();
|
|
330
|
+
});
|
|
331
|
+
req.on('error', function() {
|
|
332
|
+
console.log('[CONTROL_UI] offline — run: npx monomind mcp start');
|
|
333
|
+
});
|
|
334
|
+
req.setTimeout(800, function() { req.destroy(); });
|
|
335
|
+
} catch (e) { /* non-fatal */ }
|
|
336
|
+
|
|
337
|
+
// Worker Queue Resume (SR-003).
|
|
338
|
+
try {
|
|
339
|
+
var dispatchDir = path.join(CWD, '.monomind', 'worker-dispatch');
|
|
340
|
+
if (fs.existsSync(dispatchDir)) {
|
|
341
|
+
var pendingFiles = fs.readdirSync(dispatchDir).filter(function(f) { return f.startsWith('pending-'); });
|
|
342
|
+
if (pendingFiles.length > 0) {
|
|
343
|
+
console.log('[WORKER_RESUME] ' + pendingFiles.length + ' worker dispatch(es) pending from prior session');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} catch (e) { /* non-fatal */ }
|
|
347
|
+
},
|
|
348
|
+
};
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Extracted from hook-handler.cjs — receives hCtx from dispatcher.
|
|
3
|
+
// Handles 'pre-task' and 'post-task' hook events.
|
|
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
|
+
handlePreTask: async function(hCtx) {
|
|
11
|
+
var hookInput = hCtx.hookInput;
|
|
12
|
+
var prompt = hCtx.prompt;
|
|
13
|
+
var router = hCtx.router;
|
|
14
|
+
var session = hCtx.session;
|
|
15
|
+
var CWD = hCtx.CWD;
|
|
16
|
+
|
|
17
|
+
if (session && session.metric) {
|
|
18
|
+
try { session.metric('tasks'); } catch (e) { /* no active session */ }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Task 27: PerRunModelTier — inline complexity scoring ───────────────
|
|
22
|
+
var taskStr = typeof prompt === 'string' ? prompt : '';
|
|
23
|
+
if (taskStr) {
|
|
24
|
+
var score = 50;
|
|
25
|
+
var lower = taskStr.toLowerCase();
|
|
26
|
+
var words = taskStr.trim().split(/\s+/).length;
|
|
27
|
+
if (words < 20) score -= 20;
|
|
28
|
+
if (words > 100) score += 20;
|
|
29
|
+
if (words > 200) score += 10;
|
|
30
|
+
var highKw = ['architecture','distributed','security audit','cve','consensus','fault-tolerant','migrate','refactor across','orchestrat','design system','database schema','performance optim','threat model','encryption','zero-knowledge'];
|
|
31
|
+
var lowKwRe = /\b(format|list|rename|sort|typo|lint|log|comment|print|echo|delete unused|remove import)\b/i;
|
|
32
|
+
if (highKw.some(function(k) { return lower.includes(k); })) score += 10;
|
|
33
|
+
if (lowKwRe.test(lower)) score -= 10;
|
|
34
|
+
if (/(?:step\s*\d|first[\s,].*then[\s,]|phase\s*\d)/i.test(taskStr)) score += 10;
|
|
35
|
+
if (/```[\s\S]*?```/.test(taskStr) || /\b[\w.-]+\/[\w./-]+\b/.test(taskStr)) score += 5;
|
|
36
|
+
score = Math.max(0, Math.min(100, score));
|
|
37
|
+
var tier = score < 30 ? 'haiku' : score > 70 ? 'opus' : 'sonnet';
|
|
38
|
+
console.log('[TASK_MODEL_RECOMMENDATION] Use model="' + tier + '" (complexity=' + score + ')');
|
|
39
|
+
}
|
|
40
|
+
// Task 06: AutoRetry — signal retry policy only if coordinator path is active
|
|
41
|
+
if (hookInput.swarmCoordinator || hookInput.coordinator || hookInput.useRetry) {
|
|
42
|
+
console.log('[AUTO_RETRY_ENABLED] maxAttempts=3 strategy=exponential-backoff backoffMs=1000');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (router && prompt) {
|
|
46
|
+
var routeFn = router.routeTaskSemantic || router.routeTask;
|
|
47
|
+
var result = await Promise.resolve(routeFn(prompt));
|
|
48
|
+
console.log('[INFO] Task routed to: ' + result.agent + ' (confidence: ' + result.confidence + ')');
|
|
49
|
+
} else {
|
|
50
|
+
console.log('[OK] Task started');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Task 24: PromptVersioning — resolve prompt variant before agent spawn
|
|
54
|
+
try {
|
|
55
|
+
var memMod = await import('@monomind/memory');
|
|
56
|
+
if (memMod && memMod.PromptVersionStore) {
|
|
57
|
+
var pvStore = new memMod.PromptVersionStore(path.join(CWD, '.monomind', 'prompt-versions'));
|
|
58
|
+
var pvMod = await import('file://' + path.join(CWD, 'packages/@monomind/cli/dist/src/agents/prompt-experiment.js'));
|
|
59
|
+
if (pvMod && pvMod.PromptExperimentRouter) {
|
|
60
|
+
var pvRouter = new pvMod.PromptExperimentRouter(pvStore);
|
|
61
|
+
var agentSlug24 = hookInput.agentSlug || hookInput.agentType || hookInput.agent_type || 'unknown';
|
|
62
|
+
if (agentSlug24 !== 'unknown') {
|
|
63
|
+
var resolved = pvRouter.resolvePromptForSpawn(agentSlug24);
|
|
64
|
+
if (resolved.version) {
|
|
65
|
+
console.log('[PROMPT_VERSION] ' + agentSlug24 + ' v' + resolved.version + (resolved.isCandidate ? ' (experiment candidate)' : ''));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (e) { /* not available or no experiment */ }
|
|
71
|
+
|
|
72
|
+
// Monograph impact — detect changed files and surface their dependents
|
|
73
|
+
try {
|
|
74
|
+
var mgDbPath1 = path.join(CWD, '.monomind', 'monograph.db');
|
|
75
|
+
if (fs.existsSync(mgDbPath1)) {
|
|
76
|
+
var execSync1 = require('child_process').execSync;
|
|
77
|
+
var changedFiles1 = '';
|
|
78
|
+
try { changedFiles1 = execSync1('git diff --name-only HEAD 2>/dev/null || git diff --name-only 2>/dev/null', { cwd: CWD, timeout: 3000, shell: true }).toString().trim(); } catch(e) {}
|
|
79
|
+
if (changedFiles1) {
|
|
80
|
+
var mgMod1 = null;
|
|
81
|
+
mgMod1 = hCtx._requireMonograph();
|
|
82
|
+
if (mgMod1 && mgMod1.openDb) {
|
|
83
|
+
var db1 = mgMod1.openDb(mgDbPath1);
|
|
84
|
+
try {
|
|
85
|
+
var fileList1 = changedFiles1.split('\n').filter(Boolean).slice(0, 8);
|
|
86
|
+
var impacted1 = [];
|
|
87
|
+
for (var fi = 0; fi < fileList1.length; fi++) {
|
|
88
|
+
var fBase = path.basename(fileList1[fi]);
|
|
89
|
+
var fNode = db1.prepare("SELECT id, name, label FROM nodes WHERE file_path LIKE ? LIMIT 1").get('%' + fBase);
|
|
90
|
+
if (fNode) {
|
|
91
|
+
var fImporters = db1.prepare(
|
|
92
|
+
'SELECT n2.name FROM edges e JOIN nodes n2 ON n2.id = e.source_id WHERE e.target_id = ? LIMIT 5'
|
|
93
|
+
).all(fNode.id);
|
|
94
|
+
var entry = fNode.name + ' (' + fNode.label + ')';
|
|
95
|
+
if (fImporters.length) entry += ' ← ' + fImporters.map(function(i){ return i.name; }).join(', ');
|
|
96
|
+
impacted1.push(entry);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (impacted1.length > 0) {
|
|
100
|
+
console.log('[MONOGRAPH_IMPACT] Changed files and their dependents: ' + impacted1.join(' | '));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Effective blast radius — second pass using first impacted node
|
|
104
|
+
try {
|
|
105
|
+
if (fileList1.length > 0 && mgMod1.effectiveBlastRadius) {
|
|
106
|
+
var firstFile1 = path.basename(fileList1[0]);
|
|
107
|
+
var firstNode1 = db1.prepare("SELECT id, name, label FROM nodes WHERE file_path LIKE ? LIMIT 1").get('%' + firstFile1);
|
|
108
|
+
if (firstNode1) {
|
|
109
|
+
var blastResults1 = mgMod1.effectiveBlastRadius(db1, firstNode1.id, { maxDepth: 4 });
|
|
110
|
+
if (blastResults1 && blastResults1.length > 0) {
|
|
111
|
+
var bwdCount1 = blastResults1.filter(function(r){ return r.direction === 'backward' || r.direction === 'both'; }).length;
|
|
112
|
+
var fwdCount1 = blastResults1.filter(function(r){ return r.direction === 'forward' || r.direction === 'both'; }).length;
|
|
113
|
+
var topBlast1 = blastResults1.slice(0, 8).map(function(r){
|
|
114
|
+
return '[' + r.direction + ':' + r.hops + '] ' + r.nodeName + ' (' + r.nodeLabel + ')';
|
|
115
|
+
});
|
|
116
|
+
console.log('[MONOGRAPH_BLAST_RADIUS] Node: ' + firstNode1.name + ' | forward=' + fwdCount1 + ' backward=' + bwdCount1 + ' | ' + topBlast1.join(', '));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch(blastErr) { /* non-fatal */ }
|
|
121
|
+
} finally { if (mgMod1.closeDb) mgMod1.closeDb(db1); }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch(e) { /* non-fatal */ }
|
|
126
|
+
|
|
127
|
+
// Bridge to @monomind/hooks registry — fires Tasks 26 (PromptAssembler) and any other PreTask hooks
|
|
128
|
+
var _hooksModule = hCtx._hooksModule;
|
|
129
|
+
if (_hooksModule && _hooksModule.executeHooks && _hooksModule.HookEvent) {
|
|
130
|
+
try {
|
|
131
|
+
await _hooksModule.executeHooks(_hooksModule.HookEvent.PreTask, {
|
|
132
|
+
task: typeof prompt === 'string' ? { description: prompt, id: hookInput.taskId || '' } : null,
|
|
133
|
+
sessionId: hookInput.sessionId || hookInput.session_id || 'default',
|
|
134
|
+
}, { continueOnError: true, timeout: 2000 });
|
|
135
|
+
} catch (e) { /* non-fatal */ }
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
handlePostTask: async function(hCtx) {
|
|
140
|
+
var hookInput = hCtx.hookInput;
|
|
141
|
+
var prompt = hCtx.prompt;
|
|
142
|
+
var intelligence = hCtx.intelligence;
|
|
143
|
+
var CWD = hCtx.CWD;
|
|
144
|
+
|
|
145
|
+
var taskSuccess = hookInput.success !== false && hookInput.status !== 'failed';
|
|
146
|
+
if (intelligence && intelligence.feedback) {
|
|
147
|
+
try {
|
|
148
|
+
intelligence.feedback(true);
|
|
149
|
+
} catch (e) { /* non-fatal */ }
|
|
150
|
+
}
|
|
151
|
+
// Each TeammateIdle/TaskCompleted = one agent done → remove oldest registration (FIFO)
|
|
152
|
+
const regDir = path.join(CWD, '.monomind', 'agents', 'registrations');
|
|
153
|
+
try {
|
|
154
|
+
if (fs.existsSync(regDir)) {
|
|
155
|
+
const files = fs.readdirSync(regDir).filter(f => f.endsWith('.json'));
|
|
156
|
+
if (files.length > 0) {
|
|
157
|
+
// Sort by mtime ascending (oldest first) and remove the oldest one
|
|
158
|
+
const sorted = files
|
|
159
|
+
.map(f => ({ f, mtime: (() => { try { return fs.statSync(path.join(regDir, f)).mtimeMs; } catch { return 0; } })() }))
|
|
160
|
+
.sort((a, b) => a.mtime - b.mtime);
|
|
161
|
+
try { fs.unlinkSync(path.join(regDir, sorted[0].f)); } catch { /* ignore */ }
|
|
162
|
+
}
|
|
163
|
+
// Also purge any stragglers older than 30 min
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
for (const f of fs.readdirSync(regDir).filter(f => f.endsWith('.json'))) {
|
|
166
|
+
try { if (now - fs.statSync(path.join(regDir, f)).mtimeMs > 30 * 60 * 1000) fs.unlinkSync(path.join(regDir, f)); } catch { /* ignore */ }
|
|
167
|
+
}
|
|
168
|
+
const remaining = fs.readdirSync(regDir).filter(f => f.endsWith('.json')).length;
|
|
169
|
+
const _actPath = path.join(CWD, '.monomind', 'metrics', 'swarm-activity.json');
|
|
170
|
+
let _prevLastActive = 0;
|
|
171
|
+
try { _prevLastActive = (JSON.parse(fs.readFileSync(_actPath, 'utf-8'))?.swarm?.lastActive) || 0; } catch { /* ignore */ }
|
|
172
|
+
fs.writeFileSync(_actPath, JSON.stringify({
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
swarm: {
|
|
175
|
+
active: remaining > 0,
|
|
176
|
+
agent_count: remaining,
|
|
177
|
+
coordination_active: remaining > 0,
|
|
178
|
+
lastActive: Math.max(remaining, _prevLastActive), // preserve peak across completion
|
|
179
|
+
},
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
} catch (e) { /* non-fatal */ }
|
|
183
|
+
|
|
184
|
+
// Bridge to @monomind/hooks registry — fires Tasks 39 (SpecializationScorer) and any other PostTask hooks
|
|
185
|
+
var _hooksModule = hCtx._hooksModule;
|
|
186
|
+
if (_hooksModule && _hooksModule.executeHooks && _hooksModule.HookEvent) {
|
|
187
|
+
try {
|
|
188
|
+
await _hooksModule.executeHooks(_hooksModule.HookEvent.PostTask, {
|
|
189
|
+
task: {
|
|
190
|
+
id: hookInput.taskId || hookInput.task_id || '',
|
|
191
|
+
status: taskSuccess ? 'completed' : 'failed',
|
|
192
|
+
agentSlug: hookInput.agentSlug || hookInput.agent_slug || 'unknown',
|
|
193
|
+
type: hookInput.taskType || hookInput.task_type || 'general',
|
|
194
|
+
},
|
|
195
|
+
success: taskSuccess,
|
|
196
|
+
latencyMs: hookInput.latencyMs || hookInput.latency_ms || 0,
|
|
197
|
+
qualityScore: hookInput.qualityScore || hookInput.quality_score,
|
|
198
|
+
}, { continueOnError: true, timeout: 2000 });
|
|
199
|
+
} catch (e) { /* non-fatal */ }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Task 35: TerminationConditions — detect halted swarms via halt-signal
|
|
203
|
+
try {
|
|
204
|
+
var haltMod = await import('file://' + path.join(CWD, 'packages/@monomind/cli/dist/src/agents/halt-signal.js'));
|
|
205
|
+
if (haltMod && haltMod.isHalted) {
|
|
206
|
+
var swarmId35 = hookInput.swarmId || hookInput.swarm_id || 'default';
|
|
207
|
+
if (haltMod.isHalted(swarmId35)) {
|
|
208
|
+
console.warn('[HALT_DETECTED] Swarm ' + swarmId35 + ' has an active halt signal — agents should stop');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// Try direct file check
|
|
213
|
+
try {
|
|
214
|
+
var haltFile = path.join(CWD, 'data', 'halt-signals.jsonl');
|
|
215
|
+
if (fs.existsSync(haltFile)) {
|
|
216
|
+
var haltLines = fs.readFileSync(haltFile, 'utf-8').trim().split('\n').filter(Boolean);
|
|
217
|
+
if (haltLines.length > 0) {
|
|
218
|
+
console.warn('[HALT_DETECTED] ' + haltLines.length + ' halt signal(s) present');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch (e2) { /* non-fatal */ }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Task 37: DeadLetterQueue — enqueue failed tasks when retries exhausted
|
|
225
|
+
try {
|
|
226
|
+
if (!taskSuccess) {
|
|
227
|
+
var dlqMod = await import('file://' + path.join(CWD, 'packages/@monomind/cli/dist/src/dlq/dlq-writer.js'));
|
|
228
|
+
if (dlqMod && dlqMod.DLQWriter) {
|
|
229
|
+
var dlqDir = path.join(CWD, '.monomind', 'dlq');
|
|
230
|
+
var dlqWriter = new dlqMod.DLQWriter(dlqDir);
|
|
231
|
+
dlqWriter.enqueue({
|
|
232
|
+
toolName: 'post-task',
|
|
233
|
+
originalPayload: { taskId: hookInput.taskId || '', agentSlug: hookInput.agentSlug || 'unknown' },
|
|
234
|
+
deliveryAttempts: [{ attempt: 1, timestamp: new Date().toISOString(), error: hookInput.error || 'task failed' }],
|
|
235
|
+
agentId: hookInput.agentSlug || hookInput.agent_slug,
|
|
236
|
+
swarmId: hookInput.swarmId || hookInput.swarm_id,
|
|
237
|
+
});
|
|
238
|
+
console.log('[DLQ_ENQUEUED] Failed task ' + (hookInput.taskId || 'unknown') + ' sent to dead-letter queue');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch (e) { /* non-fatal */ }
|
|
242
|
+
|
|
243
|
+
// Memory Palace task drawer writes removed — use auto-memory files for task context
|
|
244
|
+
|
|
245
|
+
// ── Worker Auto-Dispatch ──────────────────────────────────────────────
|
|
246
|
+
// Auto-dispatch background workers based on task outcome
|
|
247
|
+
try {
|
|
248
|
+
var taskDesc = (typeof prompt === 'string' ? prompt : hookInput.description || '').toLowerCase();
|
|
249
|
+
var workersToDispatch = [];
|
|
250
|
+
|
|
251
|
+
// Always consolidate memory after any task
|
|
252
|
+
workersToDispatch.push('consolidate');
|
|
253
|
+
|
|
254
|
+
// Security-related task → dispatch audit worker
|
|
255
|
+
if (/\b(security|auth|vuln|cve|threat|token|permission|crypto)\b/.test(taskDesc)) {
|
|
256
|
+
workersToDispatch.push('audit');
|
|
257
|
+
}
|
|
258
|
+
// Performance-related → dispatch benchmark worker
|
|
259
|
+
if (/\b(performance|optimiz|benchmark|latency|throughput)\b/.test(taskDesc)) {
|
|
260
|
+
workersToDispatch.push('benchmark');
|
|
261
|
+
}
|
|
262
|
+
// Code changes → dispatch testgaps worker
|
|
263
|
+
if (/\b(implement|feature|refactor|fix|build|add|create|modify)\b/.test(taskDesc)) {
|
|
264
|
+
workersToDispatch.push('testgaps');
|
|
265
|
+
}
|
|
266
|
+
// Any significant task → dispatch map worker for codebase indexing
|
|
267
|
+
if (taskDesc.length > 50) {
|
|
268
|
+
workersToDispatch.push('map');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Dispatch via @monomind/hooks if available, otherwise write dispatch file
|
|
272
|
+
if (workersToDispatch.length > 0) {
|
|
273
|
+
var dispatchDir = path.join(CWD, '.monomind', 'worker-dispatch');
|
|
274
|
+
fs.mkdirSync(dispatchDir, { recursive: true });
|
|
275
|
+
var dispatchPayload = {
|
|
276
|
+
workers: workersToDispatch,
|
|
277
|
+
trigger: 'post-task',
|
|
278
|
+
taskDesc: taskDesc.substring(0, 100),
|
|
279
|
+
success: taskSuccess,
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
};
|
|
282
|
+
fs.writeFileSync(
|
|
283
|
+
path.join(dispatchDir, 'pending-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7) + '.json'),
|
|
284
|
+
JSON.stringify(dispatchPayload), 'utf-8'
|
|
285
|
+
);
|
|
286
|
+
console.log('[WORKER_DISPATCH] Queued: ' + workersToDispatch.join(', '));
|
|
287
|
+
}
|
|
288
|
+
} catch (e) { /* non-fatal */ }
|
|
289
|
+
|
|
290
|
+
// ── ADR Auto-Generation ────────────────────────────────────────────────
|
|
291
|
+
// When adr.autoGenerate is true and task involved architect-level work,
|
|
292
|
+
// create an ADR stub in the configured directory
|
|
293
|
+
try {
|
|
294
|
+
var settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
295
|
+
var adrCfg = {};
|
|
296
|
+
if (fs.existsSync(settingsPath)) {
|
|
297
|
+
var s = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
298
|
+
adrCfg = (s.monomind && s.monomind.adr) || {};
|
|
299
|
+
}
|
|
300
|
+
if (adrCfg.autoGenerate) {
|
|
301
|
+
var taskAgent = hookInput.agentSlug || hookInput.agent_slug || '';
|
|
302
|
+
var taskDescAdr = (typeof prompt === 'string' ? prompt : hookInput.description || '').toLowerCase();
|
|
303
|
+
var isArchitectLevel = ['architect', 'system-architect', 'software-architect'].includes(taskAgent)
|
|
304
|
+
|| /\b(architecture|design decision|adr|trade-?off|migration strategy)\b/.test(taskDescAdr);
|
|
305
|
+
if (isArchitectLevel && taskDescAdr.length > 30) {
|
|
306
|
+
var adrDir = path.join(CWD, adrCfg.directory || 'docs/adrs');
|
|
307
|
+
fs.mkdirSync(adrDir, { recursive: true });
|
|
308
|
+
var adrNum = (fs.readdirSync(adrDir).filter(function(f) { return f.endsWith('.md'); }).length + 1)
|
|
309
|
+
.toString().padStart(4, '0');
|
|
310
|
+
var adrTitle = taskDescAdr.substring(0, 60).replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
311
|
+
var adrFile = path.join(adrDir, 'ADR-' + adrNum + '-' + adrTitle + '.md');
|
|
312
|
+
if (!fs.existsSync(adrFile)) {
|
|
313
|
+
var adrContent = '# ADR-' + adrNum + ': ' + (typeof prompt === 'string' ? prompt.substring(0, 80) : adrTitle) + '\n\n'
|
|
314
|
+
+ '**Date:** ' + new Date().toISOString().slice(0, 10) + '\n'
|
|
315
|
+
+ '**Status:** Accepted\n'
|
|
316
|
+
+ '**Agent:** ' + (taskAgent || 'unknown') + '\n\n'
|
|
317
|
+
+ '## Context\n\nAuto-generated from task completion.\n\n'
|
|
318
|
+
+ '## Decision\n\n_Fill in the decision made._\n\n'
|
|
319
|
+
+ '## Consequences\n\n_Fill in the consequences._\n';
|
|
320
|
+
fs.writeFileSync(adrFile, adrContent, 'utf-8');
|
|
321
|
+
console.log('[ADR_GENERATED] ' + path.basename(adrFile));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (e) { /* non-fatal */ }
|
|
326
|
+
|
|
327
|
+
console.log('[OK] Task completed');
|
|
328
|
+
}
|
|
329
|
+
};
|