erne-universal 0.10.24 → 0.10.25
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/lib/generate.js +98 -66
- package/package.json +1 -1
- package/scripts/hooks/pre-commit-lint.js +3 -2
- package/scripts/hooks/run-with-flags.js +12 -11
- package/scripts/hooks/session-start.js +14 -10
package/lib/generate.js
CHANGED
|
@@ -12,20 +12,20 @@ const VARIANT_MAP = {
|
|
|
12
12
|
fields: ['state', 'serverState'],
|
|
13
13
|
variants: {
|
|
14
14
|
'zustand+tanstack-query': 'state-management/zustand-tanstack.md',
|
|
15
|
-
'zustand+none':
|
|
16
|
-
'zustand+swr':
|
|
17
|
-
'redux-saga+none':
|
|
15
|
+
'zustand+none': 'state-management/zustand-only.md',
|
|
16
|
+
'zustand+swr': 'state-management/zustand-tanstack.md',
|
|
17
|
+
'redux-saga+none': 'state-management/redux-saga.md',
|
|
18
18
|
'redux-saga+tanstack-query': 'state-management/redux-saga.md',
|
|
19
|
-
'redux-toolkit+rtk-query':'state-management/redux-toolkit.md',
|
|
19
|
+
'redux-toolkit+rtk-query': 'state-management/redux-toolkit.md',
|
|
20
20
|
'redux-toolkit+tanstack-query': 'state-management/redux-toolkit.md',
|
|
21
|
-
'redux-toolkit+none':
|
|
21
|
+
'redux-toolkit+none': 'state-management/redux-toolkit.md',
|
|
22
22
|
},
|
|
23
23
|
default: 'state-management/zustand-tanstack.md',
|
|
24
24
|
},
|
|
25
25
|
'rules/common/navigation.md': {
|
|
26
26
|
fields: ['navigation'],
|
|
27
27
|
variants: {
|
|
28
|
-
'expo-router':
|
|
28
|
+
'expo-router': 'navigation/expo-router.md',
|
|
29
29
|
'react-navigation': 'navigation/react-navigation.md',
|
|
30
30
|
},
|
|
31
31
|
default: 'navigation/expo-router.md',
|
|
@@ -33,21 +33,21 @@ const VARIANT_MAP = {
|
|
|
33
33
|
'rules/common/performance.md': {
|
|
34
34
|
fields: ['lists', 'images'],
|
|
35
35
|
variants: {
|
|
36
|
-
'flashlist+expo-image':
|
|
37
|
-
'flashlist+rn-image':
|
|
38
|
-
'flashlist+fast-image':
|
|
39
|
-
'flatlist+expo-image':
|
|
40
|
-
'flatlist+rn-image':
|
|
41
|
-
'flatlist+fast-image':
|
|
36
|
+
'flashlist+expo-image': 'performance/modern.md',
|
|
37
|
+
'flashlist+rn-image': 'performance/modern.md',
|
|
38
|
+
'flashlist+fast-image': 'performance/modern.md',
|
|
39
|
+
'flatlist+expo-image': 'performance/modern.md',
|
|
40
|
+
'flatlist+rn-image': 'performance/legacy.md',
|
|
41
|
+
'flatlist+fast-image': 'performance/legacy.md',
|
|
42
42
|
},
|
|
43
43
|
default: 'performance/modern.md',
|
|
44
44
|
},
|
|
45
45
|
'rules/common/coding-style.md': {
|
|
46
46
|
fields: ['componentStyle'],
|
|
47
47
|
variants: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
functional: 'coding-style/functional.md',
|
|
49
|
+
class: 'coding-style/mixed.md',
|
|
50
|
+
mixed: 'coding-style/mixed.md',
|
|
51
51
|
},
|
|
52
52
|
default: 'coding-style/functional.md',
|
|
53
53
|
},
|
|
@@ -55,72 +55,72 @@ const VARIANT_MAP = {
|
|
|
55
55
|
fields: ['storage'],
|
|
56
56
|
variants: {
|
|
57
57
|
'expo-secure-store': 'security/expo-secure.md',
|
|
58
|
-
'rn-keychain':
|
|
59
|
-
'async-storage':
|
|
58
|
+
'rn-keychain': 'security/rn-keychain.md',
|
|
59
|
+
'async-storage': 'security/async-storage.md',
|
|
60
60
|
},
|
|
61
61
|
default: 'security/async-storage.md',
|
|
62
62
|
},
|
|
63
63
|
'rules/common/styling.md': {
|
|
64
64
|
fields: ['styling'],
|
|
65
65
|
variants: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
stylesheet: 'styling/stylesheet.md',
|
|
67
|
+
nativewind: 'styling/nativewind.md',
|
|
68
|
+
tamagui: 'styling/stylesheet.md',
|
|
69
|
+
unistyles: 'styling/stylesheet.md',
|
|
70
70
|
},
|
|
71
71
|
default: 'styling/stylesheet.md',
|
|
72
72
|
},
|
|
73
73
|
'agents/ui-designer.md': {
|
|
74
74
|
fields: ['styling'],
|
|
75
75
|
variants: {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
stylesheet: 'ui-designer/stylesheet.md',
|
|
77
|
+
nativewind: 'ui-designer/nativewind.md',
|
|
78
|
+
tamagui: 'ui-designer/stylesheet.md',
|
|
79
|
+
unistyles: 'ui-designer/stylesheet.md',
|
|
80
80
|
},
|
|
81
81
|
default: 'ui-designer/stylesheet.md',
|
|
82
82
|
},
|
|
83
83
|
'agents/architect.md': {
|
|
84
84
|
fields: ['state', 'hasMonorepo'],
|
|
85
85
|
variants: {
|
|
86
|
-
'zustand+false':
|
|
87
|
-
'zustand+true':
|
|
86
|
+
'zustand+false': 'architect/zustand.md',
|
|
87
|
+
'zustand+true': 'architect/monorepo.md',
|
|
88
88
|
'redux-toolkit+false': 'architect/redux.md',
|
|
89
|
-
'redux-toolkit+true':
|
|
90
|
-
'redux-saga+false':
|
|
91
|
-
'redux-saga+true':
|
|
92
|
-
'mobx+false':
|
|
93
|
-
'mobx+true':
|
|
89
|
+
'redux-toolkit+true': 'architect/monorepo.md',
|
|
90
|
+
'redux-saga+false': 'architect/redux.md',
|
|
91
|
+
'redux-saga+true': 'architect/monorepo.md',
|
|
92
|
+
'mobx+false': 'architect/zustand.md',
|
|
93
|
+
'mobx+true': 'architect/monorepo.md',
|
|
94
94
|
},
|
|
95
95
|
default: 'architect/zustand.md',
|
|
96
96
|
},
|
|
97
97
|
'agents/senior-developer.md': {
|
|
98
98
|
fields: ['framework', 'state'],
|
|
99
99
|
variants: {
|
|
100
|
-
'expo-managed+zustand':
|
|
100
|
+
'expo-managed+zustand': 'senior-developer/modern-expo.md',
|
|
101
101
|
'expo-managed+redux-toolkit': 'senior-developer/legacy-bare.md',
|
|
102
|
-
'expo-managed+redux-saga':
|
|
103
|
-
'expo-bare+zustand':
|
|
104
|
-
'expo-bare+redux-toolkit':
|
|
105
|
-
'expo-bare+redux-saga':
|
|
106
|
-
'bare-rn+redux-saga':
|
|
107
|
-
'bare-rn+redux-toolkit':
|
|
108
|
-
'bare-rn+zustand':
|
|
102
|
+
'expo-managed+redux-saga': 'senior-developer/legacy-bare.md',
|
|
103
|
+
'expo-bare+zustand': 'senior-developer/modern-expo.md',
|
|
104
|
+
'expo-bare+redux-toolkit': 'senior-developer/legacy-bare.md',
|
|
105
|
+
'expo-bare+redux-saga': 'senior-developer/legacy-bare.md',
|
|
106
|
+
'bare-rn+redux-saga': 'senior-developer/legacy-bare.md',
|
|
107
|
+
'bare-rn+redux-toolkit': 'senior-developer/legacy-bare.md',
|
|
108
|
+
'bare-rn+zustand': 'senior-developer/modern-expo.md',
|
|
109
109
|
},
|
|
110
110
|
default: 'senior-developer/modern-expo.md',
|
|
111
111
|
},
|
|
112
112
|
'agents/feature-builder.md': {
|
|
113
113
|
fields: ['framework', 'state'],
|
|
114
114
|
variants: {
|
|
115
|
-
'expo-managed+zustand':
|
|
115
|
+
'expo-managed+zustand': 'feature-builder/modern-expo.md',
|
|
116
116
|
'expo-managed+redux-toolkit': 'feature-builder/legacy-bare.md',
|
|
117
|
-
'expo-managed+redux-saga':
|
|
118
|
-
'expo-bare+zustand':
|
|
119
|
-
'expo-bare+redux-toolkit':
|
|
120
|
-
'expo-bare+redux-saga':
|
|
121
|
-
'bare-rn+redux-saga':
|
|
122
|
-
'bare-rn+redux-toolkit':
|
|
123
|
-
'bare-rn+zustand':
|
|
117
|
+
'expo-managed+redux-saga': 'feature-builder/legacy-bare.md',
|
|
118
|
+
'expo-bare+zustand': 'feature-builder/modern-expo.md',
|
|
119
|
+
'expo-bare+redux-toolkit': 'feature-builder/legacy-bare.md',
|
|
120
|
+
'expo-bare+redux-saga': 'feature-builder/legacy-bare.md',
|
|
121
|
+
'bare-rn+redux-saga': 'feature-builder/legacy-bare.md',
|
|
122
|
+
'bare-rn+redux-toolkit': 'feature-builder/legacy-bare.md',
|
|
123
|
+
'bare-rn+zustand': 'feature-builder/modern-expo.md',
|
|
124
124
|
},
|
|
125
125
|
default: 'feature-builder/modern-expo.md',
|
|
126
126
|
},
|
|
@@ -132,12 +132,14 @@ function selectVariant(targetPath, detection) {
|
|
|
132
132
|
const mapping = VARIANT_MAP[targetPath];
|
|
133
133
|
if (!mapping) return null;
|
|
134
134
|
|
|
135
|
-
const key = mapping.fields
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
const key = mapping.fields
|
|
136
|
+
.map((field) => {
|
|
137
|
+
if (field === 'componentStyle') return detection.componentStyle;
|
|
138
|
+
if (field === 'hasMonorepo') return String(detection.hasMonorepo);
|
|
139
|
+
if (field === 'framework') return detection.framework;
|
|
140
|
+
return detection.stack?.[field];
|
|
141
|
+
})
|
|
142
|
+
.join('+');
|
|
141
143
|
|
|
142
144
|
return mapping.variants[key] || mapping.default;
|
|
143
145
|
}
|
|
@@ -146,8 +148,10 @@ function selectVariant(targetPath, detection) {
|
|
|
146
148
|
|
|
147
149
|
function determineRuleLayers(detection, cwd) {
|
|
148
150
|
const layers = ['common'];
|
|
149
|
-
if (detection.framework === 'expo-managed' || detection.framework === 'expo-bare')
|
|
150
|
-
|
|
151
|
+
if (detection.framework === 'expo-managed' || detection.framework === 'expo-bare')
|
|
152
|
+
layers.push('expo');
|
|
153
|
+
if (detection.framework === 'bare-rn' || detection.framework === 'expo-bare')
|
|
154
|
+
layers.push('bare-rn');
|
|
151
155
|
|
|
152
156
|
// Add native rule layers for bare projects with native directories
|
|
153
157
|
if (cwd && (detection.framework === 'bare-rn' || detection.framework === 'expo-bare')) {
|
|
@@ -162,9 +166,22 @@ function determineRuleLayers(detection, cwd) {
|
|
|
162
166
|
// Converts ERNE's flat hook array into Claude Code's settings.local.json format.
|
|
163
167
|
// Claude Code reads hooks from the "hooks" key in settings files, NOT from hooks.json.
|
|
164
168
|
|
|
169
|
+
const HOOK_TIMEOUTS = {
|
|
170
|
+
'session-start.js': 10,
|
|
171
|
+
'post-edit-typecheck.js': 60,
|
|
172
|
+
'pre-edit-test-gate.js': 60,
|
|
173
|
+
'audit-refresh.js': 60,
|
|
174
|
+
'post-edit-format.js': 30,
|
|
175
|
+
'bundle-size-check.js': 30,
|
|
176
|
+
'pre-commit-lint.js': 30,
|
|
177
|
+
'security-scan.js': 30,
|
|
178
|
+
'evaluate-session.js': 30,
|
|
179
|
+
};
|
|
180
|
+
const DEFAULT_HOOK_TIMEOUT = 15;
|
|
181
|
+
|
|
165
182
|
function convertToClaudeCodeHooks(erneHooks, profileName) {
|
|
166
183
|
// erneHooks is { hooks: [{ event, pattern?, script, command, profiles }] }
|
|
167
|
-
const activeHooks = erneHooks.hooks.filter(h => {
|
|
184
|
+
const activeHooks = erneHooks.hooks.filter((h) => {
|
|
168
185
|
const hookProfiles = h.profiles || ['minimal', 'standard', 'strict'];
|
|
169
186
|
return hookProfiles.includes(profileName);
|
|
170
187
|
});
|
|
@@ -190,10 +207,10 @@ function convertToClaudeCodeHooks(erneHooks, profileName) {
|
|
|
190
207
|
for (const [pattern, patternHooks] of Object.entries(byPattern)) {
|
|
191
208
|
const entry = {};
|
|
192
209
|
if (pattern) entry.matcher = pattern;
|
|
193
|
-
entry.hooks = patternHooks.map(h => ({
|
|
210
|
+
entry.hooks = patternHooks.map((h) => ({
|
|
194
211
|
type: 'command',
|
|
195
212
|
command: 'node .claude/hooks/scripts/run-with-flags.js ' + h.script,
|
|
196
|
-
timeout:
|
|
213
|
+
timeout: HOOK_TIMEOUTS[h.script] || DEFAULT_HOOK_TIMEOUT,
|
|
197
214
|
}));
|
|
198
215
|
result[event].push(entry);
|
|
199
216
|
}
|
|
@@ -210,7 +227,7 @@ function mergeHookProfile(masterHooks, profileHooks, profileName) {
|
|
|
210
227
|
// - event-keyed format: { PreToolUse: [...], PostToolUse: [...], _meta: {...} }
|
|
211
228
|
if (Array.isArray(masterHooks.hooks)) {
|
|
212
229
|
// Flat array format — filter by profile, then group by event
|
|
213
|
-
const filtered = masterHooks.hooks.filter(hook => {
|
|
230
|
+
const filtered = masterHooks.hooks.filter((hook) => {
|
|
214
231
|
const hookProfiles = hook.profiles || ['minimal', 'standard', 'strict'];
|
|
215
232
|
return hookProfiles.includes(profileName);
|
|
216
233
|
});
|
|
@@ -232,7 +249,7 @@ function mergeHookProfile(masterHooks, profileHooks, profileName) {
|
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
if (Array.isArray(hooks)) {
|
|
235
|
-
result[event] = hooks.filter(hook => {
|
|
252
|
+
result[event] = hooks.filter((hook) => {
|
|
236
253
|
const hookProfiles = hook.profiles || ['minimal', 'standard', 'strict'];
|
|
237
254
|
return hookProfiles.includes(profileName);
|
|
238
255
|
});
|
|
@@ -293,7 +310,7 @@ function generateConfig(erneRoot, targetDir, detection, profile, mcpSelections)
|
|
|
293
310
|
// Copy commands as Claude Code skills: commands/foo.md → skills/foo/SKILL.md
|
|
294
311
|
const commandsSrc = path.join(erneRoot, 'commands');
|
|
295
312
|
if (fs.existsSync(commandsSrc)) {
|
|
296
|
-
const commandFiles = fs.readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
|
|
313
|
+
const commandFiles = fs.readdirSync(commandsSrc).filter((f) => f.endsWith('.md'));
|
|
297
314
|
for (const file of commandFiles) {
|
|
298
315
|
const name = path.basename(file, '.md');
|
|
299
316
|
const skillDir = path.join(targetDir, 'skills', name);
|
|
@@ -352,7 +369,12 @@ function generateConfig(erneRoot, targetDir, detection, profile, mcpSelections)
|
|
|
352
369
|
const agentFiles = fs.readdirSync(agentsSrc);
|
|
353
370
|
for (const file of agentFiles) {
|
|
354
371
|
// Skip native-bridge-builder unless bare-rn
|
|
355
|
-
if (
|
|
372
|
+
if (
|
|
373
|
+
file === 'native-bridge-builder.md' &&
|
|
374
|
+
detection.framework !== 'bare-rn' &&
|
|
375
|
+
detection.framework !== 'expo-bare'
|
|
376
|
+
)
|
|
377
|
+
continue;
|
|
356
378
|
// Skip expo-config-resolver if bare-rn
|
|
357
379
|
if (file === 'expo-config-resolver.md' && detection.framework === 'bare-rn') continue;
|
|
358
380
|
|
|
@@ -410,7 +432,11 @@ function generateConfig(erneRoot, targetDir, detection, profile, mcpSelections)
|
|
|
410
432
|
const settingsLocalPath = path.join(targetDir, 'settings.local.json');
|
|
411
433
|
let settingsLocal = {};
|
|
412
434
|
if (fs.existsSync(settingsLocalPath)) {
|
|
413
|
-
try {
|
|
435
|
+
try {
|
|
436
|
+
settingsLocal = JSON.parse(fs.readFileSync(settingsLocalPath, 'utf8'));
|
|
437
|
+
} catch {
|
|
438
|
+
/* fresh */
|
|
439
|
+
}
|
|
414
440
|
}
|
|
415
441
|
settingsLocal.hooks = claudeCodeHooks;
|
|
416
442
|
fs.writeFileSync(settingsLocalPath, JSON.stringify(settingsLocal, null, 2) + '\n');
|
|
@@ -440,7 +466,9 @@ function generateConfig(erneRoot, targetDir, detection, profile, mcpSelections)
|
|
|
440
466
|
if (mcpConfig.env) serverEntry.env = mcpConfig.env;
|
|
441
467
|
if (mcpConfig.url) serverEntry.url = mcpConfig.url;
|
|
442
468
|
mcpServers[sel] = serverEntry;
|
|
443
|
-
} catch {
|
|
469
|
+
} catch {
|
|
470
|
+
/* skip invalid json */
|
|
471
|
+
}
|
|
444
472
|
}
|
|
445
473
|
}
|
|
446
474
|
}
|
|
@@ -450,7 +478,11 @@ function generateConfig(erneRoot, targetDir, detection, profile, mcpSelections)
|
|
|
450
478
|
const mcpJsonPath = path.join(projectRoot, '.mcp.json');
|
|
451
479
|
let existingMcp = {};
|
|
452
480
|
if (fs.existsSync(mcpJsonPath)) {
|
|
453
|
-
try {
|
|
481
|
+
try {
|
|
482
|
+
existingMcp = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
|
|
483
|
+
} catch {
|
|
484
|
+
/* fresh */
|
|
485
|
+
}
|
|
454
486
|
}
|
|
455
487
|
if (!existingMcp.mcpServers) existingMcp.mcpServers = {};
|
|
456
488
|
// Merge — don't overwrite existing servers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { execFileSync } = require('child_process');
|
|
3
|
-
const { readStdin, pass,
|
|
3
|
+
const { readStdin, pass, warn } = require('./lib/hook-utils');
|
|
4
4
|
|
|
5
5
|
const input = readStdin();
|
|
6
6
|
const command = (input.tool_input && input.tool_input.command) || '';
|
|
@@ -18,7 +18,8 @@ try {
|
|
|
18
18
|
} catch (err) {
|
|
19
19
|
const output = err.stdout || err.stderr || '';
|
|
20
20
|
if (output.includes('error') || output.includes('warning')) {
|
|
21
|
-
|
|
21
|
+
console.error(`ERNE: Lint errors found. Fix before committing:\n${output.slice(0, 500)}`);
|
|
22
|
+
return pass();
|
|
22
23
|
}
|
|
23
24
|
if (err.status === 127 || output.includes('not found')) {
|
|
24
25
|
return pass('ERNE: ESLint not available, skipping lint check');
|
|
@@ -31,9 +31,7 @@ function resolveProfile() {
|
|
|
31
31
|
for (const mdPath of claudeMdPaths) {
|
|
32
32
|
try {
|
|
33
33
|
const content = fs.readFileSync(mdPath, 'utf8');
|
|
34
|
-
const match = content.match(
|
|
35
|
-
/<!--\s*Hook Profile:\s*(minimal|standard|strict)\s*-->/i
|
|
36
|
-
);
|
|
34
|
+
const match = content.match(/<!--\s*Hook Profile:\s*(minimal|standard|strict)\s*-->/i);
|
|
37
35
|
if (match) return match[1].toLowerCase();
|
|
38
36
|
} catch {}
|
|
39
37
|
}
|
|
@@ -63,7 +61,9 @@ function loadHooksConfig() {
|
|
|
63
61
|
for (const candidate of candidates) {
|
|
64
62
|
try {
|
|
65
63
|
return JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
66
|
-
} catch {
|
|
64
|
+
} catch {
|
|
65
|
+
/* try next */
|
|
66
|
+
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return { hooks: [] };
|
|
@@ -73,7 +73,7 @@ const profile = resolveProfile();
|
|
|
73
73
|
const config = loadHooksConfig();
|
|
74
74
|
|
|
75
75
|
// Find hook entry in config
|
|
76
|
-
const hookEntry = config.hooks.find(h => h.script === HOOK_SCRIPT);
|
|
76
|
+
const hookEntry = config.hooks.find((h) => h.script === HOOK_SCRIPT);
|
|
77
77
|
if (!hookEntry) {
|
|
78
78
|
process.exit(0);
|
|
79
79
|
}
|
|
@@ -99,7 +99,7 @@ const result = spawnSync('node', [scriptPath], {
|
|
|
99
99
|
input: stdinData,
|
|
100
100
|
encoding: 'utf8',
|
|
101
101
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
102
|
-
timeout:
|
|
102
|
+
timeout: 120000,
|
|
103
103
|
env,
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ if (result.stdout) process.stdout.write(result.stdout);
|
|
|
109
109
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
110
110
|
|
|
111
111
|
// Report hook execution metrics to dashboard (fire-and-forget)
|
|
112
|
-
if (process.env.ERNE_DASHBOARD_PORT
|
|
112
|
+
if (process.env.ERNE_DASHBOARD_PORT) {
|
|
113
113
|
try {
|
|
114
114
|
const http = require('http');
|
|
115
115
|
const { resolveDashboardPort } = require('./lib/port-registry');
|
|
@@ -122,14 +122,15 @@ if (process.env.ERNE_DASHBOARD_PORT || process.env.ERNE_HOOK_CHAIN) {
|
|
|
122
122
|
duration_ms: durationMs,
|
|
123
123
|
exit_code: result.status ?? 0,
|
|
124
124
|
skipped: false,
|
|
125
|
-
}
|
|
125
|
+
},
|
|
126
126
|
});
|
|
127
127
|
const req = http.request({
|
|
128
|
-
hostname: '127.0.0.1',
|
|
128
|
+
hostname: '127.0.0.1',
|
|
129
|
+
port,
|
|
129
130
|
path: '/api/events',
|
|
130
131
|
method: 'POST',
|
|
131
132
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
132
|
-
timeout: 300
|
|
133
|
+
timeout: 300,
|
|
133
134
|
});
|
|
134
135
|
req.on('error', () => {});
|
|
135
136
|
req.write(payload);
|
|
@@ -138,7 +139,7 @@ if (process.env.ERNE_DASHBOARD_PORT || process.env.ERNE_HOOK_CHAIN) {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
if (result.signal === 'SIGTERM') {
|
|
141
|
-
console.error('ERNE: hook timed out after
|
|
142
|
+
console.error('ERNE: hook timed out after 120s');
|
|
142
143
|
process.exit(2);
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -26,9 +26,7 @@ function findFilesWithExt(dir, ext) {
|
|
|
26
26
|
withFileTypes: true,
|
|
27
27
|
recursive: true,
|
|
28
28
|
});
|
|
29
|
-
return entries.some(
|
|
30
|
-
e => e.isFile() && e.name.endsWith(ext)
|
|
31
|
-
);
|
|
29
|
+
return entries.some((e) => e.isFile() && e.name.endsWith(ext));
|
|
32
30
|
} catch {
|
|
33
31
|
return false;
|
|
34
32
|
}
|
|
@@ -85,15 +83,19 @@ try {
|
|
|
85
83
|
hasSettings = true;
|
|
86
84
|
profile = settings.profile || 'standard';
|
|
87
85
|
version = settings.erneVersion || '';
|
|
88
|
-
} catch {
|
|
86
|
+
} catch {
|
|
87
|
+
/* no settings */
|
|
88
|
+
}
|
|
89
89
|
|
|
90
90
|
// Count agents
|
|
91
91
|
try {
|
|
92
92
|
const agentDir = path.join(projectDir, '.claude', 'agents');
|
|
93
93
|
if (fs.existsSync(agentDir)) {
|
|
94
|
-
agentCount = fs.readdirSync(agentDir).filter(f => f.endsWith('.md')).length;
|
|
94
|
+
agentCount = fs.readdirSync(agentDir).filter((f) => f.endsWith('.md')).length;
|
|
95
95
|
}
|
|
96
|
-
} catch {
|
|
96
|
+
} catch {
|
|
97
|
+
/* skip */
|
|
98
|
+
}
|
|
97
99
|
|
|
98
100
|
// ─── Dashboard detection (no auto-start — use `npx erne-universal dashboard`) ─
|
|
99
101
|
|
|
@@ -106,7 +108,9 @@ if (hasSettings) {
|
|
|
106
108
|
if (existingPort) {
|
|
107
109
|
dashboardUrl = `http://localhost:${existingPort}`;
|
|
108
110
|
}
|
|
109
|
-
} catch {
|
|
111
|
+
} catch {
|
|
112
|
+
/* port-registry not available — skip */
|
|
113
|
+
}
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
// ─── Print banner ────────────────────────────────────────────────────────────
|
|
@@ -118,7 +122,7 @@ if (hasSettings) {
|
|
|
118
122
|
parts.push(profile);
|
|
119
123
|
if (agentCount > 0) parts.push(`${agentCount} agents`);
|
|
120
124
|
if (dashboardUrl) parts.push(`Dashboard: ${dashboardUrl}`);
|
|
121
|
-
console.
|
|
125
|
+
console.error(`ERNE ${parts.join(' | ')}`);
|
|
122
126
|
} else {
|
|
123
127
|
// Fallback: layer-based output for projects without full ERNE init
|
|
124
128
|
const parts = [];
|
|
@@ -126,8 +130,8 @@ if (hasSettings) {
|
|
|
126
130
|
parts.push(`${profile} profile`);
|
|
127
131
|
if (agentCount > 0) parts.push(`${agentCount} agents`);
|
|
128
132
|
parts.push(`layers: ${layers.join(', ')}`);
|
|
129
|
-
console.
|
|
130
|
-
console.
|
|
133
|
+
console.error(`ERNE ${parts.join(' | ')}`);
|
|
134
|
+
console.error(`Use /erne- commands (e.g. /erne-plan, /erne-perf, /erne-doctor)`);
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
if (!hasSignals) {
|