@monoes/monomindcli 1.10.29 → 1.10.30

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 (80) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +39 -4
  2. package/.claude/helpers/handlers/edit-handler.cjs +145 -0
  3. package/.claude/helpers/handlers/route-handler.cjs +393 -0
  4. package/.claude/helpers/handlers/session-handler.cjs +167 -0
  5. package/.claude/helpers/handlers/session-restore-handler.cjs +343 -0
  6. package/.claude/helpers/handlers/task-handler.cjs +329 -0
  7. package/.claude/helpers/hook-handler.cjs +114 -2273
  8. package/.claude/helpers/intelligence.cjs +21 -2
  9. package/.claude/helpers/learning-service.mjs +166 -8
  10. package/.claude/helpers/memory-palace.cjs +72 -12
  11. package/.claude/helpers/router.cjs +79 -5
  12. package/.claude/helpers/statusline.cjs +193 -399
  13. package/.claude/helpers/utils/micro-agents.cjs +338 -0
  14. package/.claude/helpers/utils/monograph.cjs +349 -0
  15. package/.claude/helpers/utils/telemetry.cjs +144 -0
  16. package/.claude/skills/agent-browser-testing/SKILL.md +3 -2
  17. package/.claude/skills/monomind/browse-agentcore.md +116 -0
  18. package/.claude/skills/monomind/browse-electron.md +189 -0
  19. package/.claude/skills/monomind/browse-qa.md +229 -0
  20. package/.claude/skills/monomind/browse-references/authentication.md +162 -0
  21. package/.claude/skills/monomind/browse-references/trust-boundaries.md +41 -0
  22. package/.claude/skills/monomind/browse-references/video-recording.md +84 -0
  23. package/.claude/skills/monomind/browse-slack.md +189 -0
  24. package/.claude/skills/monomind/browse-vercel.md +240 -0
  25. package/.claude/skills/monomind/browse.md +724 -0
  26. package/dist/src/browser/actions.d.ts +13 -0
  27. package/dist/src/browser/actions.d.ts.map +1 -0
  28. package/dist/src/browser/actions.js +201 -0
  29. package/dist/src/browser/actions.js.map +1 -0
  30. package/dist/src/browser/browser.d.ts +14 -0
  31. package/dist/src/browser/browser.d.ts.map +1 -0
  32. package/dist/src/browser/browser.js +198 -0
  33. package/dist/src/browser/browser.js.map +1 -0
  34. package/dist/src/browser/cdp.d.ts +17 -0
  35. package/dist/src/browser/cdp.d.ts.map +1 -0
  36. package/dist/src/browser/cdp.js +106 -0
  37. package/dist/src/browser/cdp.js.map +1 -0
  38. package/dist/src/browser/index.d.ts +11 -0
  39. package/dist/src/browser/index.d.ts.map +1 -0
  40. package/dist/src/browser/index.js +11 -0
  41. package/dist/src/browser/index.js.map +1 -0
  42. package/dist/src/browser/network.d.ts +11 -0
  43. package/dist/src/browser/network.d.ts.map +1 -0
  44. package/dist/src/browser/network.js +81 -0
  45. package/dist/src/browser/network.js.map +1 -0
  46. package/dist/src/browser/screenshot.d.ts +15 -0
  47. package/dist/src/browser/screenshot.d.ts.map +1 -0
  48. package/dist/src/browser/screenshot.js +36 -0
  49. package/dist/src/browser/screenshot.js.map +1 -0
  50. package/dist/src/browser/session.d.ts +8 -0
  51. package/dist/src/browser/session.d.ts.map +1 -0
  52. package/dist/src/browser/session.js +50 -0
  53. package/dist/src/browser/session.js.map +1 -0
  54. package/dist/src/browser/snapshot.d.ts +12 -0
  55. package/dist/src/browser/snapshot.d.ts.map +1 -0
  56. package/dist/src/browser/snapshot.js +147 -0
  57. package/dist/src/browser/snapshot.js.map +1 -0
  58. package/dist/src/browser/tabs.d.ts +8 -0
  59. package/dist/src/browser/tabs.d.ts.map +1 -0
  60. package/dist/src/browser/tabs.js +25 -0
  61. package/dist/src/browser/tabs.js.map +1 -0
  62. package/dist/src/browser/types.d.ts +109 -0
  63. package/dist/src/browser/types.d.ts.map +1 -0
  64. package/dist/src/browser/types.js +16 -0
  65. package/dist/src/browser/types.js.map +1 -0
  66. package/dist/src/browser/wait.d.ts +4 -0
  67. package/dist/src/browser/wait.d.ts.map +1 -0
  68. package/dist/src/browser/wait.js +122 -0
  69. package/dist/src/browser/wait.js.map +1 -0
  70. package/dist/src/commands/browse.d.ts +8 -0
  71. package/dist/src/commands/browse.d.ts.map +1 -0
  72. package/dist/src/commands/browse.js +573 -0
  73. package/dist/src/commands/browse.js.map +1 -0
  74. package/dist/src/commands/index.d.ts.map +1 -1
  75. package/dist/src/commands/index.js +2 -0
  76. package/dist/src/commands/index.js.map +1 -1
  77. package/dist/src/ui/dashboard-v2.html +1692 -0
  78. package/dist/src/ui/server.mjs +15 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +2 -1
@@ -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
+ };