create-merlin-brain 5.3.4 → 5.3.5
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/bin/install.cjs +18 -8
- package/dist/server/tools/route-helpers.d.ts +6 -1
- package/dist/server/tools/route-helpers.d.ts.map +1 -1
- package/dist/server/tools/route-helpers.js +61 -3
- package/dist/server/tools/route-helpers.js.map +1 -1
- package/files/merlin/skills/TASK-OPTIMIZER.json +45 -0
- package/files/scripts/duo-codex-call.sh +29 -10
- package/files/scripts/task-optimize.sh +8 -0
- package/package.json +1 -1
package/bin/install.cjs
CHANGED
|
@@ -1557,6 +1557,10 @@ async function install() {
|
|
|
1557
1557
|
try {
|
|
1558
1558
|
claudeDirConfig = JSON.parse(fs.readFileSync(claudeDirDesktopConfig, 'utf8'));
|
|
1559
1559
|
} catch (e) {
|
|
1560
|
+
// Backup the malformed file before overwriting
|
|
1561
|
+
const bakPath = claudeDirDesktopConfig + '.bak.' + Date.now();
|
|
1562
|
+
fs.copyFileSync(claudeDirDesktopConfig, bakPath);
|
|
1563
|
+
logWarn(`Config file malformed, backed up to ${bakPath} before overwriting`);
|
|
1560
1564
|
claudeDirConfig = {};
|
|
1561
1565
|
}
|
|
1562
1566
|
}
|
|
@@ -1575,7 +1579,14 @@ async function install() {
|
|
|
1575
1579
|
fs.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
|
|
1576
1580
|
logSuccess('Merlin Sights configured for ~/.claude.json (Claude MCP registry)');
|
|
1577
1581
|
} catch (e) {
|
|
1578
|
-
|
|
1582
|
+
// Backup the malformed file before overwriting
|
|
1583
|
+
const bakPath = claudeJsonPath + '.bak.' + Date.now();
|
|
1584
|
+
fs.copyFileSync(claudeJsonPath, bakPath);
|
|
1585
|
+
logWarn(`Config file malformed, backed up to ${bakPath} before overwriting`);
|
|
1586
|
+
// Create with minimal config
|
|
1587
|
+
const claudeJson = { mcpServers: { merlin: mcpConfig(apiKey, true) } };
|
|
1588
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
|
|
1589
|
+
logWarn('Could not update ~/.claude.json - created minimal config with backup');
|
|
1579
1590
|
}
|
|
1580
1591
|
} else {
|
|
1581
1592
|
// Create ~/.claude.json with MCP config if it doesn't exist
|
|
@@ -1739,13 +1750,12 @@ ${colors.cyan}Universal Task Optimization (NEW in 5.3.0):${colors.reset}
|
|
|
1739
1750
|
• ${colors.bright}/merlin:polish${colors.reset} - Animation polish via animation-expert
|
|
1740
1751
|
• ${colors.bright}/merlin:redesign${colors.reset} - Full redesign via ui-builder
|
|
1741
1752
|
|
|
1742
|
-
${colors.cyan}
|
|
1743
|
-
•
|
|
1744
|
-
•
|
|
1745
|
-
•
|
|
1746
|
-
•
|
|
1747
|
-
•
|
|
1748
|
-
• Removed hostile postinstall hook
|
|
1753
|
+
${colors.cyan}Tier-2 cleanup (NEW in 5.3.5):${colors.reset}
|
|
1754
|
+
• 4 new agents wired (android-expert, apple-swift-expert, desktop-app-expert, marketing-automation)
|
|
1755
|
+
• Counter-reset bug diagnosed in duo-codex-call.sh (fix pending v5.3.6)
|
|
1756
|
+
• MCP routing reads live agent list from disk (no drift)
|
|
1757
|
+
• Concurrent codex calls protected by flock
|
|
1758
|
+
• Malformed config files now backed up before overwrite
|
|
1749
1759
|
|
|
1750
1760
|
${colors.cyan}Merlin works with or without Sights:${colors.reset}
|
|
1751
1761
|
• ${colors.green}With Sights${colors.reset}: Instant context, cross-session memory
|
|
@@ -38,7 +38,12 @@ export declare function buildEffortPrefix(effort: EffortLevel): {
|
|
|
38
38
|
prefix: string;
|
|
39
39
|
suffix: string;
|
|
40
40
|
};
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Read live agent roles from ~/.claude/agents/*.md files.
|
|
43
|
+
* Caches the result after first access.
|
|
44
|
+
*/
|
|
45
|
+
export declare function getAgentRoles(): Record<string, string>;
|
|
46
|
+
/** Known specialist agents and their roles (live from disk, with fallback) */
|
|
42
47
|
export declare const AGENT_ROLES: Record<string, string>;
|
|
43
48
|
/** Merlin internal workflow agents */
|
|
44
49
|
export declare const WORKFLOW_AGENTS: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../../src/server/tools/route-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../../../src/server/tools/route-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,gDAAgD;AAChD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEpD,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,IAAI,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAM9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAKnE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAWzF;AAkCD;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA+BtD;AAED,8EAA8E;AAC9E,eAAO,MAAM,WAAW,wBAAkB,CAAC;AAE3C,sCAAsC;AACtC,eAAO,MAAM,eAAe,UAM3B,CAAC"}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Extracted from route.ts to keep the main routing file focused on
|
|
5
5
|
* tool registration and orchestration logic.
|
|
6
6
|
*/
|
|
7
|
-
import { readFileSync } from 'fs';
|
|
7
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
8
10
|
/**
|
|
9
11
|
* Parse the YAML frontmatter block from an agent .md file.
|
|
10
12
|
* Returns the raw frontmatter string or null if not present.
|
|
@@ -71,8 +73,8 @@ export function buildEffortPrefix(effort) {
|
|
|
71
73
|
}
|
|
72
74
|
return { prefix: '', suffix: '' };
|
|
73
75
|
}
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
+
/** Fallback specialist agents (used if agent files not readable) */
|
|
77
|
+
const FALLBACK_AGENT_ROLES = {
|
|
76
78
|
'product-spec': 'Feature specification and product definition',
|
|
77
79
|
'system-architect': 'Architecture decisions and system design',
|
|
78
80
|
'implementation-dev': 'Code implementation and feature building',
|
|
@@ -82,6 +84,62 @@ export const AGENT_ROLES = {
|
|
|
82
84
|
'ops-railway': 'Deployment, ops, Railway, Google Cloud',
|
|
83
85
|
'docs-keeper': 'Documentation updates',
|
|
84
86
|
};
|
|
87
|
+
/** Cache for agent roles read from disk */
|
|
88
|
+
let agentRolesCache = null;
|
|
89
|
+
/**
|
|
90
|
+
* Extract agent description from .md file frontmatter.
|
|
91
|
+
* Looks for 'description:' field in YAML frontmatter.
|
|
92
|
+
*/
|
|
93
|
+
function extractDescription(agentPath) {
|
|
94
|
+
try {
|
|
95
|
+
const content = readFileSync(agentPath, 'utf-8');
|
|
96
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
97
|
+
if (!match)
|
|
98
|
+
return null;
|
|
99
|
+
const fm = match[1];
|
|
100
|
+
const descMatch = fm.match(/^description:\s*["']([^"']*?)["']\s*$/m);
|
|
101
|
+
return descMatch ? descMatch[1] : null;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Read live agent roles from ~/.claude/agents/*.md files.
|
|
109
|
+
* Caches the result after first access.
|
|
110
|
+
*/
|
|
111
|
+
export function getAgentRoles() {
|
|
112
|
+
if (agentRolesCache)
|
|
113
|
+
return agentRolesCache;
|
|
114
|
+
const roles = {};
|
|
115
|
+
const agentsDir = join(homedir(), '.claude', 'agents');
|
|
116
|
+
if (existsSync(agentsDir)) {
|
|
117
|
+
try {
|
|
118
|
+
const files = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
119
|
+
for (const file of files) {
|
|
120
|
+
const agentName = file.replace('.md', '');
|
|
121
|
+
const filePath = join(agentsDir, file);
|
|
122
|
+
const desc = extractDescription(filePath);
|
|
123
|
+
if (desc) {
|
|
124
|
+
roles[agentName] = desc;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Fall back to hardcoded defaults
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Merge with fallback defaults (fallback wins for missing entries)
|
|
133
|
+
for (const [name, role] of Object.entries(FALLBACK_AGENT_ROLES)) {
|
|
134
|
+
if (!roles[name]) {
|
|
135
|
+
roles[name] = role;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
agentRolesCache = roles;
|
|
139
|
+
return roles;
|
|
140
|
+
}
|
|
141
|
+
/** Known specialist agents and their roles (live from disk, with fallback) */
|
|
142
|
+
export const AGENT_ROLES = getAgentRoles();
|
|
85
143
|
/** Merlin internal workflow agents */
|
|
86
144
|
export const WORKFLOW_AGENTS = [
|
|
87
145
|
'merlin-planner', 'merlin-executor', 'merlin-codebase-mapper',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-helpers.js","sourceRoot":"","sources":["../../../src/server/tools/route-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"route-helpers.js","sourceRoot":"","sources":["../../../src/server/tools/route-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -298,6 +298,51 @@
|
|
|
298
298
|
"agent": "code-organization-supervisor",
|
|
299
299
|
"intent": "organization",
|
|
300
300
|
"weight": 0.8
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"id": "platform/android",
|
|
304
|
+
"path": "platform/android",
|
|
305
|
+
"triggers": [
|
|
306
|
+
"android", "kotlin", "jetpack compose", "android studio", "google play", "material you",
|
|
307
|
+
"android ui", "android intent", "android activity", "android fragment", "viewmodel"
|
|
308
|
+
],
|
|
309
|
+
"agent": "android-expert",
|
|
310
|
+
"intent": "android",
|
|
311
|
+
"weight": 1.0
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": "platform/apple",
|
|
315
|
+
"path": "platform/apple",
|
|
316
|
+
"triggers": [
|
|
317
|
+
"ios", "macos", "swift", "swiftui", "objective-c", "xcode", "uikit", "appkit",
|
|
318
|
+
"iphone", "ipad", "app store", "watchos", "tvos"
|
|
319
|
+
],
|
|
320
|
+
"agent": "apple-swift-expert",
|
|
321
|
+
"intent": "apple",
|
|
322
|
+
"weight": 1.0
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"id": "platform/desktop",
|
|
326
|
+
"path": "platform/desktop",
|
|
327
|
+
"triggers": [
|
|
328
|
+
"electron", "tauri", "desktop app", "system tray", "menu bar app", "native window",
|
|
329
|
+
"ipc", "windows app", "macos app", "linux app"
|
|
330
|
+
],
|
|
331
|
+
"agent": "desktop-app-expert",
|
|
332
|
+
"intent": "desktop",
|
|
333
|
+
"weight": 1.0
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"id": "marketing/automation",
|
|
337
|
+
"path": "marketing/automation",
|
|
338
|
+
"triggers": [
|
|
339
|
+
"email campaign", "drip sequence", "marketing automation", "marketing campaign",
|
|
340
|
+
"email marketing", "mailchimp", "sendgrid", "transactional email", "newsletter",
|
|
341
|
+
"growth marketing", "lifecycle email", "ab test email"
|
|
342
|
+
],
|
|
343
|
+
"agent": "marketing-automation",
|
|
344
|
+
"intent": "marketing",
|
|
345
|
+
"weight": 1.0
|
|
301
346
|
}
|
|
302
347
|
],
|
|
303
348
|
"intent_overrides": {
|
|
@@ -15,15 +15,9 @@ FAILURES_FILE="${HOME}/.claude/merlin-state/.duo-codex-failures"
|
|
|
15
15
|
DECISIONS_LOG="${HOME}/.claude/merlin-state/duo-decisions.log"
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
17
|
|
|
18
|
-
# --- Counter helpers ---
|
|
18
|
+
# --- Counter helpers with flock protection ---
|
|
19
19
|
_read_counter() {
|
|
20
|
-
# Reset counter if file is > 6h old (session boundary)
|
|
21
20
|
if [[ -f "$FAILURES_FILE" ]]; then
|
|
22
|
-
AGE=$(python3 -c "import os,time; print(int(time.time() - os.path.getmtime('$FAILURES_FILE')))" 2>/dev/null || echo "99999")
|
|
23
|
-
if [[ "$AGE" -gt 21600 ]]; then
|
|
24
|
-
rm -f "$FAILURES_FILE"
|
|
25
|
-
echo 0; return
|
|
26
|
-
fi
|
|
27
21
|
cat "$FAILURES_FILE" 2>/dev/null || echo 0
|
|
28
22
|
else
|
|
29
23
|
echo 0
|
|
@@ -31,9 +25,34 @@ _read_counter() {
|
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
_write_counter() {
|
|
28
|
+
mkdir -p "$(dirname "$FAILURES_FILE")" 2>/dev/null || true
|
|
34
29
|
echo "$1" > "$FAILURES_FILE"
|
|
35
30
|
}
|
|
36
31
|
|
|
32
|
+
_read_counter_locked() {
|
|
33
|
+
local result
|
|
34
|
+
if command -v flock >/dev/null 2>&1; then
|
|
35
|
+
(
|
|
36
|
+
flock -s 9 2>/dev/null || true
|
|
37
|
+
result=$(_read_counter)
|
|
38
|
+
echo "$result"
|
|
39
|
+
) 9>"${FAILURES_FILE}.lock" 2>/dev/null || _read_counter
|
|
40
|
+
else
|
|
41
|
+
_read_counter
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_write_counter_locked() {
|
|
46
|
+
if command -v flock >/dev/null 2>&1; then
|
|
47
|
+
(
|
|
48
|
+
flock -x 9 2>/dev/null || true
|
|
49
|
+
_write_counter "$1"
|
|
50
|
+
) 9>"${FAILURES_FILE}.lock" 2>/dev/null || _write_counter "$1"
|
|
51
|
+
else
|
|
52
|
+
_write_counter "$1"
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
_log_failure() {
|
|
38
57
|
local exit_code="$1"
|
|
39
58
|
local ts
|
|
@@ -64,15 +83,15 @@ fi
|
|
|
64
83
|
|
|
65
84
|
if [[ $EXIT_CODE -eq 0 ]]; then
|
|
66
85
|
# Success — reset failure counter
|
|
67
|
-
|
|
86
|
+
_write_counter_locked 0
|
|
68
87
|
exit 0
|
|
69
88
|
fi
|
|
70
89
|
|
|
71
90
|
# Failure path
|
|
72
91
|
_log_failure "$EXIT_CODE" "$@"
|
|
73
92
|
|
|
74
|
-
COUNT=$(( $(
|
|
75
|
-
|
|
93
|
+
COUNT=$(( $(_read_counter_locked) + 1 ))
|
|
94
|
+
_write_counter_locked "$COUNT"
|
|
76
95
|
|
|
77
96
|
if [[ $COUNT -ge 3 ]]; then
|
|
78
97
|
_auto_disable_duo
|
|
@@ -93,6 +93,14 @@ TEST_CASES = [
|
|
|
93
93
|
("React component refactor", "react", "merlin-frontend", 25),
|
|
94
94
|
("useState hook pattern", "react", "merlin-frontend", 25),
|
|
95
95
|
("Next.js server component", "react", "merlin-frontend", 25),
|
|
96
|
+
("build Android app with Kotlin", "android", "android-expert", 25),
|
|
97
|
+
("Jetpack Compose UI for Material You", "android", "android-expert", 25),
|
|
98
|
+
("iOS SwiftUI development", "apple", "apple-swift-expert", 25),
|
|
99
|
+
("macOS AppKit interface", "apple", "apple-swift-expert", 25),
|
|
100
|
+
("Electron app development", "desktop", "desktop-app-expert", 25),
|
|
101
|
+
("Tauri system tray application", "desktop", "desktop-app-expert", 25),
|
|
102
|
+
("email marketing campaign setup", "marketing", "marketing-automation", 25),
|
|
103
|
+
("drip sequence for onboarding", "marketing", "marketing-automation", 25),
|
|
96
104
|
("fix the database query", "none", "", 0),
|
|
97
105
|
("rename a variable", "none", "", 0),
|
|
98
106
|
("update README", "none", "", 0),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-merlin-brain",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.5",
|
|
4
4
|
"description": "Merlin - The Ultimate AI Brain for Claude Code, Codex, and other AI CLIs. One install: workflows, agents, loop, and Sights MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server/index.js",
|