erne-universal 0.10.18 → 0.10.20

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.md CHANGED
@@ -1,185 +1,35 @@
1
- # ERNE — Everything React Native & Expo
2
-
3
- You are working in an ERNE-powered React Native/Expo project.
1
+ <!-- ERNE-GENERATED -->
2
+ <!-- erne-profile: standard -->
3
+ # erne-universal ERNE Configuration
4
4
 
5
5
  ## Project Stack
6
-
7
- > **Note:** When `erne init` runs in a project, this section is replaced with a stack summary generated from deep detection of the project's actual dependencies (state management, navigation, styling, lists, images, forms, storage, testing, build system).
8
-
9
- - **Framework**: React Native with Expo (managed or bare)
10
- - **Language**: TypeScript (strict mode)
11
- - **Navigation**: Detected at init (Expo Router, React Navigation, etc.)
12
- - **State**: Detected at init (Zustand, Redux Toolkit, MobX, etc.)
13
- - **Testing**: Jest + React Native Testing Library + Detox
14
- - **Styling**: Detected at init (StyleSheet.create, NativeWind, etc.)
6
+ - **Framework**: React Native
7
+ - **Language**: JavaScript
8
+ - **Navigation**: None
9
+ - **State**: None
10
+ - **Styling**: StyleSheet.create
11
+ - **Lists**: FlatList (built-in)
12
+ - **Images**: Image (built-in)
13
+ - **Testing**: None configured
14
+ - **Build**: Manual
15
15
 
16
16
  ## Key Rules
17
-
18
- ### Code Style
19
17
  - Functional components only with `const` + arrow functions
20
- - PascalCase for components, camelCase for hooks/utils, SCREAMING_SNAKE for constants
21
18
  - Named exports only (no default exports)
22
- - Group imports: react react-native expo external internal → types
23
- - Max component length: 250 lines extract if larger
24
-
25
- ### Performance
26
- - Memoize with `React.memo`, `useMemo`, `useCallback` where measurable
27
- - Use `FlashList` over `FlatList` for large lists (100+ items)
28
- - Avoid anonymous functions in JSX render paths
29
- - Optimize images: WebP format, resizeMode, caching
30
- - Keep JS bundle under 1.5MB
31
-
32
- ### Testing
33
- - Every new component/hook needs a test file
34
- - Test user behavior, not implementation details
35
- - Mock native modules at `__mocks__/` level
36
- - E2E critical paths with Detox
37
-
38
- ### Security
39
- - Never hardcode secrets — use environment variables
40
- - Validate all deep link parameters
41
- - Pin SSL certificates for sensitive API calls
42
- - Use secure storage for tokens (expo-secure-store, react-native-keychain, etc.)
43
-
44
- ### Git
45
- - Conventional Commits: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`
46
- - Branch naming: `feat/`, `fix/`, `refactor/` prefix
47
- - Atomic commits — one logical change per commit
48
-
49
- ## Workflow Orchestration
50
-
51
- ### 1. Plan Mode Default
52
- - Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
53
- - If something goes sideways, STOP and re-plan immediately — don't keep pushing
54
- - Use plan mode for verification steps, not just building
55
- - Write detailed specs upfront to reduce ambiguity
56
-
57
- ### 2. Subagent Strategy
58
- - Use subagents liberally to keep main context window clean
59
- - Offload research, exploration, and parallel analysis to subagents
60
- - For complex problems, throw more compute at it via subagents
61
- - One task per subagent for focused execution
62
-
63
- ### 3. Self-Improvement Loop
64
- - After ANY correction from the user: save a `feedback` memory via the memory system
65
- - Write rules for yourself that prevent the same mistake
66
- - Memory is auto-loaded each session — no manual review needed
67
-
68
- ### 4. Verification Before Done
69
- - Never mark a task complete without proving it works
70
- - Diff behavior between main and your changes when relevant
71
- - Ask yourself: "Would a staff engineer approve this?"
72
- - Run tests, check logs, demonstrate correctness
73
-
74
- ### 5. Demand Elegance (Balanced)
75
- - For non-trivial changes: pause and ask "is there a more elegant way?"
76
- - If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
77
- - Skip this for simple, obvious fixes — don't over-engineer
78
- - Challenge your own work before presenting it
79
-
80
- ### 6. Autonomous Bug Fixing
81
- - When given a bug report: just fix it. Don't ask for hand-holding
82
- - Point at logs, errors, failing tests — then resolve them
83
- - Zero context switching required from the user
84
- - Go fix failing CI tests without being told how
85
-
86
- ## Smart Agent Routing
87
-
88
- When a user's message matches these signals, automatically use the corresponding agent via a subagent. The user should NOT need to type explicit `/commands` — detect intent from context. Explicit `/command` invocations always override auto-routing.
89
-
90
- **How routing works:** Read the user's message. If it matches trigger signals below, dispatch the matching agent as a subagent. If multiple agents could match, prefer the more specific one. If unclear, ask the user.
91
-
92
- **Image detection:** When the user shares an image/screenshot alongside a description of a visual problem, ALWAYS route to visual-debugger.
93
-
94
- | Agent | Trigger Signals |
95
- |-------|----------------|
96
- | `visual-debugger` | Image in chat with UI issue, screenshot, "doesn't look right", "UI is broken", "layout not working", "fix this" + image, spacing, alignment, overflow, "wrong colors", "doesn't match Figma", pixel, responsive, "design issue", "visual bug", "cut off", overlapping, "not centered", "wrong font", "dark mode broken", "safe area", notch, "status bar", "keyboard covers" |
97
- | `performance-profiler` | "slow", lag, FPS, "memory leak", jank, freeze, hanging, "bundle size", TTI, "startup time", "re-render", heavy, "animation stutter", "frame drop", "battery drain", "CPU usage", "JS thread blocked", "bridge overhead", "large list", "image loading slow", "splash screen long", ANR |
98
- | `code-reviewer` | review, PR, refactor, "code quality", "clean up", "best practice", "technical debt", "code smell", maintainability, "check my code", "is this okay", "anti-pattern", readability, DRY, SOLID, "type safety" |
99
- | `architect` | "how to build", architecture, structure, plan, "system design", "new feature", "want to add", "how to approach", decompose, "design pattern", "folder structure", "state management", "data flow", "API design", "navigation structure", monorepo, "separation of concerns" |
100
- | `feature-builder` | implement, build, create, "add feature", "create component", "write this", "make this", scaffold, "add screen", "wire up", integrate, "connect to API", "hook up", "add functionality", CRUD |
101
- | `tdd-guide` | test, coverage, jest, detox, TDD, "write test", "unit test", e2e, "failing test", "test broken", "snapshot test", mock, fixture, "testing library", "integration test", "test flaky" |
102
- | `expo-config-resolver` | build error, red screen, crash, "won't start", error, "build failed", metro, babel, "config issue", "EAS build", "can't build", "module not found", "pod install", gradle, "Xcode error", "Android build", "iOS build", "linking error", prebuild, "app.json", "app.config", "plugin not working", "white screen", "blank screen" |
103
- | `native-bridge-builder` | "native module", bridge, "turbo module", Swift, Kotlin, Java, "Objective-C", "native code", "platform specific", "expo module", JSI, Fabric, "New Architecture", codegen, "C++", "native view", Nitro, "expo-modules-core" |
104
- | `upgrade-assistant` | upgrade, update, version, migration, "breaking change", deprecated, "Expo SDK", "React Native version", bump, migrate, changelog, compatibility, "peer dependency", outdated, "npx expo install --fix" |
105
- | `ui-designer` | component, button, modal, form, screen, page, tab, "navigation UI", "build UI", "design system", styled, theme, icon, animation, transition, gesture, "bottom sheet", drawer, header, card, skeleton, loading, toast |
106
- | `pipeline-orchestrator` | deploy, publish, EAS, "app store", "play store", "CI/CD", release, "OTA update", submit, distribution, TestFlight, "internal testing", "code signing", provisioning, certificate, keystore, fastlane, "GitHub Actions" |
107
- | `senior-developer` | "what do you think", opinion, advice, approach, tradeoff, "not sure how", mentor, "help me decide", "best way to", "should I", "pros and cons", "compare options", "which is better", "common pitfall", "production ready", scalable |
108
- | `documentation-generator` | "generate docs", "document this", "write documentation", "project overview", "architecture docs", "what does this project do", "onboard me", "explain codebase", "project structure", "how does this work" |
109
-
110
- ## Task Management
111
-
112
- 1. **Plan First**: Use plan mode or TodoWrite for non-trivial tasks
113
- 2. **Verify Plan**: Check in before starting implementation
114
- 3. **Track Progress**: Use built-in task system (TodoWrite/TaskCreate) to mark progress
115
- 4. **Explain Changes**: High-level summary at each step
116
- 5. **Capture Lessons**: Save corrections as `feedback` memories
117
-
118
- ## Core Principles
119
-
120
- - **Simplicity First**: Make every change as simple as possible. Impact minimal code.
121
- - **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
122
- - **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
19
+ - Use secure storage for tokens avoid AsyncStorage for sensitive data
20
+ - Conventional Commits: feat:, fix:, refactor:, test:, docs:, chore:
123
21
 
124
22
  ## Available Commands
23
+ /erne-plan, /erne-code-review, /erne-tdd, /erne-build-fix, /erne-perf, /erne-upgrade, /erne-native-module, /erne-debug, /erne-debug-visual, /erne-deploy,
24
+ /erne-component, /erne-navigate, /erne-animate, /erne-orchestrate, /erne-quality-gate, /erne-code, /erne-feature, /erne-worker, /erne-audit, /erne-learn, /erne-retrospective, /erne-setup-device
125
25
 
126
- Use `/erne-plan`, `/erne-code-review`, `/erne-tdd`, `/erne-build-fix`, `/erne-perf`, `/erne-upgrade`, `/erne-native-module`, `/erne-navigate`, `/erne-animate`, `/erne-deploy`, `/erne-component`, `/erne-debug`, `/erne-debug-visual`, `/erne-audit`, `/erne-quality-gate`, `/erne-code`, `/erne-feature`, `/erne-learn`, `/erne-retrospective`, `/erne-setup-device` for guided workflows.
127
-
128
- ## Worker Mode (Autonomous Ticket Execution)
129
-
130
- ERNE can run as an autonomous worker that polls a ticket provider, picks up ready tasks, and executes the full pipeline without human intervention.
131
-
132
- ### Quick Start
133
-
134
- ```bash
135
- erne worker --config worker.json
136
- ```
137
-
138
- ### How It Works
139
-
140
- 1. Polls a configured provider (ClickUp, GitHub Issues, Linear, Jira, or local JSON) for tickets marked as ready
141
- 2. Validates each ticket has sufficient detail (title, description, acceptance criteria)
142
- 3. Scores confidence (0-100) — skips tickets below the configured threshold
143
- 4. Generates an implementation plan from ticket context + project audit data
144
- 5. Executes in an isolated git worktree to avoid disrupting the main branch
145
- 6. Runs the test suite and verifies no regressions
146
- 7. Performs automated self-review against ERNE coding standards
147
- 8. Compares audit health score before and after changes
148
- 9. Creates a pull request with summary, test results, and ticket link
149
-
150
- ### Quality Gates
151
-
152
- Each ticket must pass these gates before a PR is created:
153
- - Confidence score above threshold (default: 70)
154
- - All existing tests pass
155
- - No audit score regression
156
- - Self-review finds no critical issues
157
-
158
- ### Configuration
159
-
160
- See `worker.example.json` for a full template. Key options:
161
- - `provider.type` — clickup, github, linear, jira, local
162
- - `provider.poll_interval_seconds` — How often to check for new tickets (default: 60)
163
- - `erne.min_confidence` — Minimum confidence score to attempt a ticket (default: 70)
164
- - `erne.hook_profile` — ERNE hook profile to use (minimal, standard, strict)
165
- - `repo.path` — Absolute path to the local repository
166
- - `repo.base_branch` — Branch to create worktrees from (default: main)
167
-
168
- ### CLI Options
169
-
170
- | Flag | Description |
171
- |------|-------------|
172
- | `--config <path>` | Path to worker config JSON (required) |
173
- | `--dry-run` | Fetch and display tickets without executing |
174
- | `--once` | Process one ticket then exit |
26
+ ## Dashboard
27
+ ERNE includes a visual dashboard for monitoring agents, project health, worker status, and documentation.
28
+ Launch: `npx erne-universal dashboard`
29
+ The dashboard shows 6 tabs: HQ (pixel-art agent office), Worker (autonomous tickets), Health (doctor score), Docs (generated documentation), Project (stack & MCP), Insights (analytics).
175
30
 
176
- ## Available Skills
31
+ ## Rules
32
+ @import .claude/rules/common/
177
33
 
178
- Skills in `skills/` activate automatically:
179
- - `coding-standards` — Audit and enforce standards
180
- - `tdd-workflow` — Red-green-refactor cycle
181
- - `performance-optimization` — Diagnose and fix perf issues
182
- - `security-review` — Mobile security audit
183
- - `native-module-scaffold` — Create Turbo/Expo modules
184
- - `upgrade-workflow` — Version migration guide
185
- - `continuous-learning-v2` — Pattern extraction and learning
34
+ ## Skills
35
+ @import .claude/skills/
@@ -1,54 +1,58 @@
1
1
  ---
2
2
  name: erne-init
3
- description: ERNE — Initialize the AI agent harness in this project. Sets up 13 agents, hooks, rules, MCP servers, and dashboard. Use when user says "set up erne", "initialize erne", "install erne", or first time setup.
3
+ description: Initialize ERNE — the AI agent harness for React Native & Expo projects. Sets up 13 specialized agents, hook-based code quality enforcement, MCP server integrations, and a visual dashboard. Triggers on "set up erne", "initialize erne", "install erne", "configure agents", "add erne to project", or any first-time ERNE setup request. Always use this skill when the user wants to set up ERNE, even if they don't say "init" explicitly.
4
4
  ---
5
5
 
6
- # /erne-init — Initialize ERNE
6
+ # Initialize ERNE
7
7
 
8
- You are executing the `/erne-init` command. This sets up ERNE in the current project.
8
+ ERNE is an AI agent harness for React Native and Expo. Setting it up involves choosing a few preferences and then running a single CLI command. The reason you need to ask the user before running is that the CLI's interactive prompts don't work reliably in Claude Code — so you handle the interaction here in conversation instead.
9
9
 
10
- ## Before Running
10
+ ## Step 1: Ask preferences
11
11
 
12
- You MUST ask the user these questions before proceeding. Do NOT skip this step. Do NOT default silently.
12
+ Before running anything, ask the user two things in a single message:
13
13
 
14
- ### Question 1: Hook Profile
14
+ **Profile:** ERNE uses hook profiles to control how much code quality checking happens automatically. Ask which one they'd like:
15
+ - **minimal** — Almost no automated checks. Good for quick prototyping or when you just want the agents without guardrails.
16
+ - **standard** — Catches common issues (formatting, console.logs, platform-specific bugs) without slowing things down. This is what most people pick.
17
+ - **strict** — Adds security scanning, accessibility checks, and test gates. Good for production apps or teams.
15
18
 
16
- Ask the user:
19
+ **MCP servers:** ERNE can configure agent-device (controls iOS Simulator and Android Emulator for screenshots, taps, navigation) and GitHub integration. Ask if they want these set up now or later.
17
20
 
18
- > Which hook profile would you like?
19
- >
20
- > **a) minimal** — Fast iteration, minimal checks. For rapid prototyping.
21
- > **b) standard** (recommended) — Balanced quality and speed. For most projects.
22
- > **c) strict** — Production-grade enforcement. For teams requiring CI-level quality.
21
+ Wait for the user to respond before continuing.
23
22
 
24
- Wait for their response.
23
+ ## Step 2: Run the init command
25
24
 
26
- ### Question 2: MCP Servers
25
+ Once you have their preferences, run this in Bash:
27
26
 
28
- Ask the user:
27
+ ```bash
28
+ npx erne-universal@latest init --yes --profile <profile> [--no-mcp]
29
+ ```
29
30
 
30
- > ERNE can configure MCP servers for device control and GitHub integration. Configure now?
31
- >
32
- > **a) Yes** — Set up agent-device (simulator/emulator control) + GitHub
33
- > **b) No** — Skip MCP setup, configure later
31
+ Replace `<profile>` with their choice (minimal, standard, or strict). Add `--no-mcp` only if they said no to MCP servers.
34
32
 
35
- Wait for their response.
33
+ The `--yes` flag is important — it tells the CLI to skip its own interactive prompts since you already gathered the preferences.
36
34
 
37
- ## Run Init
35
+ ## Step 3: Launch the dashboard
38
36
 
39
- Based on their answers, construct and run the command:
37
+ After init completes, start the dashboard:
40
38
 
41
39
  ```bash
42
- npx erne-universal init --yes --profile <chosen-profile> [--no-mcp if they chose no]
40
+ npx erne-universal dashboard &
43
41
  ```
44
42
 
45
- Examples:
46
- - User chose standard + yes to MCP: `npx erne-universal init --yes --profile standard`
47
- - User chose strict + no to MCP: `npx erne-universal init --yes --profile strict --no-mcp`
48
- - User chose minimal + yes to MCP: `npx erne-universal init --yes --profile minimal`
43
+ The dashboard runs in the background. Read the output to find which port it started on (usually 3333, but it auto-selects a free port if 3333 is taken).
44
+
45
+ If the dashboard needs to install dependencies on first run (takes about 2 minutes), tell the user it's installing and will open in the browser when ready.
49
46
 
50
- ## After Init
47
+ ## Step 4: Tell the user what happened
51
48
 
52
- 1. Tell the user to **restart the Claude Code session** to activate MCP servers and hooks
53
- 2. Mention they can run `npx erne-universal dashboard` to launch the visual dashboard
54
- 3. List available commands: `/erne-plan`, `/erne-perf`, `/erne-doctor`, etc.
49
+ Summarize in a clear message:
50
+ - What was set up (agents, hooks, rules, skills, MCP servers)
51
+ - The dashboard URL (e.g., http://localhost:3333)
52
+ - That they need to **restart this Claude Code session** for MCP servers and hooks to activate
53
+ - A few commands they can try after restart:
54
+ - `/erne-plan` — plan a new feature
55
+ - `/erne-perf` — profile performance issues
56
+ - `/erne-doctor` — check project health
57
+ - `/erne-code-review` — review code quality
58
+ - Type `/erne-` and press Tab to see all commands
package/lib/init.js CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const fs = require('fs');
11
12
  const path = require('path');
12
13
  const readline = require('readline/promises');
13
14
  const { stdin, stdout } = require('process');
@@ -16,6 +17,24 @@ const { detectProject } = require('./detect');
16
17
  const { generateConfig, determineRuleLayers } = require('./generate');
17
18
  const { handleClaudeMd } = require('./claude-md');
18
19
 
20
+ // ─── ANSI helpers ────────────────────────────────────────────────────────────
21
+
22
+ const c = {
23
+ reset: '\x1b[0m',
24
+ bold: '\x1b[1m',
25
+ dim: '\x1b[2m',
26
+ green: '\x1b[32m',
27
+ yellow: '\x1b[33m',
28
+ red: '\x1b[31m',
29
+ cyan: '\x1b[36m',
30
+ white: '\x1b[37m',
31
+ };
32
+
33
+ const ok = `${c.green}\u2713${c.reset}`;
34
+ const fail = `${c.red}\u2717${c.reset}`;
35
+ const warn = `${c.yellow}\u26A0${c.reset}`;
36
+ const info = `${c.cyan}\u2139${c.reset}`;
37
+
19
38
  // ─── Label maps for detection report ─────────────────────────────────────────
20
39
 
21
40
  const labels = {
@@ -88,41 +107,56 @@ function parseArgs() {
88
107
 
89
108
  function printDetectionReport(detection) {
90
109
  const s = detection.stack;
91
- const lbl = (map, key) => map[key] || key;
110
+ const lbl = (map, key) => (key in map ? map[key] : key);
92
111
 
93
112
  // Build state display: combine client + server state
94
113
  let stateDisplay = lbl(labels.state, s.state);
95
114
  const serverLabel = lbl(labels.serverState, s.serverState);
96
115
  if (serverLabel) stateDisplay += ` + ${serverLabel}`;
97
116
 
98
- const claudeMdStatus = detection.existingClaudeMd
99
- ? 'Exists (will append, backup to CLAUDE.md.pre-erne)'
100
- : 'Will generate';
101
-
102
117
  const rows = [
103
- ['Framework', `${frameworkLabels[detection.framework] || detection.framework}`],
118
+ ['Framework', frameworkLabels[detection.framework] || detection.framework],
104
119
  ['State', stateDisplay],
105
120
  ['Navigation', lbl(labels.navigation, s.navigation)],
106
121
  ['Styling', lbl(labels.styling, s.styling)],
107
122
  ['Lists', lbl(labels.lists, s.lists)],
108
123
  ['Images', lbl(labels.images, s.images)],
109
- ['Forms', lbl(labels.forms, s.forms)],
110
- ['Storage', lbl(labels.storage, s.storage)],
111
124
  ['Testing', lbl(labels.testing, s.testing)],
112
- ['Build', lbl(labels.build, s.build)],
113
125
  ['TypeScript', detection.hasTypescript ? 'Yes' : 'No'],
114
126
  ['New Arch', detection.hasNewArch ? 'Yes' : 'No'],
115
- ['Monorepo', detection.hasMonorepo ? 'Yes' : 'No'],
116
- ['Components', detection.componentStyle === 'functional' ? 'Functional' : detection.componentStyle === 'class' ? 'Class' : 'Mixed'],
117
- ['CLAUDE.md', claudeMdStatus],
118
127
  ];
119
128
 
120
129
  console.log();
121
130
  for (const [label, value] of rows) {
122
- console.log(` ${label.padEnd(13)}${value}`);
131
+ console.log(` ${c.dim}${label.padEnd(14)}${c.reset}${c.cyan}${value}${c.reset}`);
123
132
  }
124
133
  }
125
134
 
135
+ // ─── Count generated files ───────────────────────────────────────────────────
136
+
137
+ function countFiles(claudeDir) {
138
+ let agentCount = 0;
139
+ let skillCount = 0;
140
+
141
+ try {
142
+ const agentsDir = path.join(claudeDir, 'agents');
143
+ if (fs.existsSync(agentsDir)) {
144
+ agentCount = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length;
145
+ }
146
+ } catch { /* skip */ }
147
+
148
+ try {
149
+ const skillsDir = path.join(claudeDir, 'skills');
150
+ if (fs.existsSync(skillsDir)) {
151
+ skillCount = fs.readdirSync(skillsDir).filter(f => {
152
+ try { return fs.statSync(path.join(skillsDir, f)).isDirectory(); } catch { return false; }
153
+ }).length;
154
+ }
155
+ } catch { /* skip */ }
156
+
157
+ return { agentCount, skillCount };
158
+ }
159
+
126
160
  // ─── Main init flow ──────────────────────────────────────────────────────────
127
161
 
128
162
  module.exports = async function init() {
@@ -137,18 +171,18 @@ module.exports = async function init() {
137
171
  const cwd = process.cwd();
138
172
 
139
173
  try {
140
- console.log('\n erne Setting up AI agent harness for React Native & Expo\n');
174
+ console.log(`\n ${c.bold}erne${c.reset} ${c.dim}\u2014 Setting up AI agent harness for React Native & Expo${c.reset}\n`);
141
175
 
142
176
  // ─── Step 1: Detect project type ───
143
- console.log(' Scanning project...');
177
+ console.log(` ${c.bold}Step 1:${c.reset} Deep-scanning project...`);
144
178
  const detection = detectProject(cwd);
145
179
  printDetectionReport(detection);
146
180
 
147
181
  if (!detection.isRNProject) {
148
182
  if (nonInteractive) {
149
- console.log('\n No React Native project detected continuing (non-interactive mode).');
183
+ console.log(`\n ${warn} No React Native project detected \u2014 continuing (non-interactive mode).`);
150
184
  } else {
151
- console.log('\n No React Native project detected in current directory.');
185
+ console.log(`\n ${warn} No React Native project detected in current directory.`);
152
186
  const proceed = await rl.question(' Continue anyway? (y/N) ');
153
187
  if (proceed.toLowerCase() !== 'y') {
154
188
  console.log(' Aborted.');
@@ -163,15 +197,15 @@ module.exports = async function init() {
163
197
 
164
198
  if (opts.profile && validProfiles.includes(opts.profile)) {
165
199
  profile = opts.profile;
166
- console.log(`\n Step 2: Hook profile: ${profile} (from --profile flag)`);
200
+ console.log(`\n ${c.bold}Step 2:${c.reset} Hook profile \u2192 ${c.cyan}${profile}${c.reset}`);
167
201
  } else if (nonInteractive) {
168
202
  profile = 'standard';
169
- console.log('\n Step 2: Hook profile: standard (default)');
203
+ console.log(`\n ${c.bold}Step 2:${c.reset} Hook profile \u2192 ${c.cyan}standard${c.reset}`);
170
204
  } else {
171
- console.log('\n Step 2: Select hook profile:\n');
172
- console.log(' (a) minimal fast iteration, minimal checks');
173
- console.log(' (b) standard balanced quality + speed [recommended]');
174
- console.log(' (c) strict production-grade enforcement');
205
+ console.log(`\n ${c.bold}Step 2:${c.reset} Select hook profile:\n`);
206
+ console.log(' (a) minimal \u2014 fast iteration, minimal checks');
207
+ console.log(' (b) standard \u2014 balanced quality + speed [recommended]');
208
+ console.log(' (c) strict \u2014 production-grade enforcement');
175
209
  console.log();
176
210
 
177
211
  let profileChoice = await rl.question(' Profile (a/b/c) [b]: ');
@@ -186,32 +220,33 @@ module.exports = async function init() {
186
220
  const defaultMcpKeys = ['agent-device', 'github'];
187
221
 
188
222
  if (opts.noMcp) {
189
- console.log('\n Step 3: MCP servers: none (--no-mcp)');
223
+ console.log(`\n ${c.bold}Step 3:${c.reset} MCP servers \u2192 ${c.dim}none${c.reset}`);
190
224
  for (const key of allMcpKeys) mcpSelections[key] = false;
191
225
  } else if (opts.mcp !== null) {
192
- console.log(`\n Step 3: MCP servers: ${opts.mcp.join(', ') || 'none'} (from --mcp flag)`);
226
+ const mcpDisplay = opts.mcp.length > 0 ? opts.mcp.join(', ') : 'none';
227
+ console.log(`\n ${c.bold}Step 3:${c.reset} MCP servers \u2192 ${c.cyan}${mcpDisplay}${c.reset}`);
193
228
  for (const key of allMcpKeys) mcpSelections[key] = opts.mcp.includes(key);
194
229
  } else if (opts.yes) {
195
- console.log(`\n Step 3: MCP servers: ${defaultMcpKeys.join(', ')} (defaults)`);
230
+ console.log(`\n ${c.bold}Step 3:${c.reset} MCP servers \u2192 ${c.cyan}${defaultMcpKeys.join(', ')}${c.reset}`);
196
231
  for (const key of allMcpKeys) mcpSelections[key] = defaultMcpKeys.includes(key);
197
232
  } else {
198
- console.log('\n Step 3: MCP server integrations:\n');
233
+ console.log(`\n ${c.bold}Step 3:${c.reset} MCP server integrations:\n`);
199
234
 
200
235
  // Recommended servers
201
236
  console.log(' Recommended:');
202
- const agentDevice = await rl.question(' [Y/n] agent-device Control iOS Simulator & Android Emulator: ');
237
+ const agentDevice = await rl.question(' [Y/n] agent-device \u2014 Control iOS Simulator & Android Emulator: ');
203
238
  mcpSelections['agent-device'] = agentDevice.toLowerCase() !== 'n';
204
239
 
205
- const github = await rl.question(' [Y/n] GitHub PR management, issue tracking: ');
240
+ const github = await rl.question(' [Y/n] GitHub \u2014 PR management, issue tracking: ');
206
241
  mcpSelections['github'] = github.toLowerCase() !== 'n';
207
242
 
208
243
  // Optional servers
209
244
  console.log('\n Optional (press Enter to skip):');
210
245
  const optionalServers = [
211
- { key: 'supabase', label: 'Supabase Database & auth' },
212
- { key: 'firebase', label: 'Firebase Analytics & push' },
213
- { key: 'figma', label: 'Figma Design token sync' },
214
- { key: 'sentry', label: 'Sentry Error tracking' },
246
+ { key: 'supabase', label: 'Supabase \u2014 Database & auth' },
247
+ { key: 'firebase', label: 'Firebase \u2014 Analytics & push' },
248
+ { key: 'figma', label: 'Figma \u2014 Design token sync' },
249
+ { key: 'sentry', label: 'Sentry \u2014 Error tracking' },
215
250
  ];
216
251
 
217
252
  for (const server of optionalServers) {
@@ -221,7 +256,7 @@ module.exports = async function init() {
221
256
  }
222
257
 
223
258
  // ─── Step 4: Generate config ───
224
- console.log('\n Step 4: Generating configuration...\n');
259
+ console.log();
225
260
 
226
261
  const erneRoot = path.resolve(__dirname, '..');
227
262
  const claudeDir = path.join(cwd, '.claude');
@@ -233,51 +268,52 @@ module.exports = async function init() {
233
268
 
234
269
  const { ruleLayers, mcpCount } = generateConfig(erneRoot, claudeDir, detection, profile, enabledMcp);
235
270
 
236
- // Print what was generated
237
- console.log(` ✓ .claude/ (agents, skills, rules, contexts, hooks)`);
238
- console.log(` ✓ .claude/rules/ (layers: ${ruleLayers.join(', ')})`);
239
- console.log(` ✓ .claude/hooks.json (${profile} profile)`);
240
- console.log(` ✓ .claude/mcp/ (${mcpCount} servers)`);
241
- if (mcpCount > 0) {
242
- console.log(' ⚠ MCP servers configured in .mcp.json — restart Claude Code session to activate');
243
- }
244
- console.log(' ✓ .claude/settings.json');
245
-
246
271
  // Handle CLAUDE.md
247
272
  const claudeMdResult = handleClaudeMd(cwd, detection, profile, ruleLayers);
248
273
 
274
+ // Count what was generated
275
+ const { agentCount, skillCount } = countFiles(claudeDir);
276
+
277
+ // ─── Summary ───
278
+ console.log(` ${ok} ${c.bold}${agentCount}${c.reset} agents matched to your stack`);
279
+ console.log(` ${ok} ${c.bold}${skillCount}${c.reset} skills installed`);
280
+ console.log(` ${ok} .claude/ configured \u2014 agents, skills, rules, hooks`);
281
+ if (mcpCount > 0) {
282
+ console.log(` ${ok} .mcp.json \u2014 ${c.bold}${mcpCount}${c.reset} MCP servers configured`);
283
+ }
284
+
249
285
  const claudeMdMessages = {
250
- appended: 'CLAUDE.md (appended — original backed up to CLAUDE.md.pre-erne)',
251
- regenerated: 'CLAUDE.md (regenerated for detected stack)',
252
- generated: 'CLAUDE.md (generated for detected stack)',
286
+ appended: 'CLAUDE.md updated (original backed up)',
287
+ regenerated: 'CLAUDE.md regenerated for detected stack',
288
+ generated: 'CLAUDE.md generated',
253
289
  };
254
- console.log(` ${claudeMdMessages[claudeMdResult]}`);
290
+ console.log(` ${ok} ${claudeMdMessages[claudeMdResult]}`);
255
291
 
256
292
  // ─── Step 5: Project Audit ───
257
- console.log('\n Step 5: Running project audit...\n');
293
+ console.log();
258
294
  try {
259
295
  const { runAudit } = require('./audit');
260
296
  const { score, findings, strengths } = runAudit(cwd);
261
297
  const critical = findings.filter(f => f.severity === 'critical');
262
298
  const warnings = findings.filter(f => f.severity === 'warning');
263
- const scoreColor = score >= 80 ? '\x1b[32m' : score >= 60 ? '\x1b[33m' : '\x1b[31m';
264
- const reset = '\x1b[0m';
265
- console.log(` Score: ${scoreColor}${score}/100${reset} (${strengths.length} strengths, ${critical.length} critical, ${warnings.length} warnings)`);
299
+ const scoreColor = score >= 80 ? c.green : score >= 60 ? c.yellow : c.red;
300
+ console.log(` Audit score: ${scoreColor}${c.bold}${score}/100${c.reset} (${strengths.length} strengths, ${critical.length} critical, ${warnings.length} warnings)`);
266
301
  if (critical.length > 0) {
267
302
  for (const f of critical) {
268
- console.log(` \x1b[31m✗\x1b[0m ${f.title} Fix: ${f.fix}`);
303
+ console.log(` ${fail} ${f.title} \u2014 Fix: ${f.fix}`);
269
304
  }
270
305
  }
271
- console.log(' ✓ .erne/audit.md (full report)');
272
306
  } catch (err) {
273
- console.log(` Audit skipped: ${err.message}`);
307
+ console.log(` ${warn} Audit skipped: ${err.message}`);
274
308
  }
275
309
 
276
- // ─── Dashboard ───
277
- console.log(' ℹ Dashboard: run \x1b[36mnpx erne-universal dashboard\x1b[0m to launch');
278
- console.log('');
279
- console.log(' \x1b[32mDone!\x1b[0m ERNE is ready. Use /erne- commands (e.g. /erne-plan, /erne-perf, /erne-doctor).');
280
- console.log('');
310
+ // ─── Footer ───
311
+ console.log();
312
+ console.log(` ${info} Dashboard: run ${c.cyan}npx erne-universal dashboard${c.reset} to launch`);
313
+ console.log();
314
+ console.log(` ${c.green}Done!${c.reset} Use /erne- commands (e.g. /erne-plan, /erne-perf, /erne-doctor)`);
315
+ console.log(` Restart Claude Code session to activate MCP servers and hooks.`);
316
+ console.log();
281
317
  } finally {
282
318
  if (rl) rl.close();
283
319
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erne-universal",
3
- "version": "0.10.18",
3
+ "version": "0.10.20",
4
4
  "description": "Complete AI coding agent harness for React Native and Expo development",
5
5
  "keywords": [
6
6
  "react-native",
@@ -1,9 +1,12 @@
1
1
  'use strict';
2
+
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
 
5
6
  const projectDir = process.env.ERNE_PROJECT_DIR || process.cwd();
6
7
 
8
+ // ─── Helper functions for layer detection ────────────────────────────────────
9
+
7
10
  function fileExists(relPath) {
8
11
  return fs.existsSync(path.join(projectDir, relPath));
9
12
  }
@@ -46,7 +49,8 @@ function hasExpoDependency(pkg) {
46
49
  return 'expo' in deps;
47
50
  }
48
51
 
49
- // Detect layers
52
+ // ─── Detect layers (always needed for backward compat) ───────────────────────
53
+
50
54
  const layers = ['common'];
51
55
  const pkg = readPackageJson();
52
56
  const hasIosDir = dirExists('ios');
@@ -68,12 +72,17 @@ if (hasAndroidDir && findFilesWithExt('android', '.kt')) {
68
72
 
69
73
  const hasSignals = layers.length > 1;
70
74
 
71
- // Read ERNE settings for richer session info
75
+ // ─── Read ERNE settings for richer banner ────────────────────────────────────
76
+
77
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
78
+ let version = '';
72
79
  let profile = 'unknown';
73
80
  let agentCount = 0;
74
- let version = '';
81
+ let hasSettings = false;
82
+
75
83
  try {
76
- const settings = JSON.parse(fs.readFileSync(path.join(projectDir, '.claude', 'settings.json'), 'utf8'));
84
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
85
+ hasSettings = true;
77
86
  profile = settings.profile || 'standard';
78
87
  version = settings.erneVersion || '';
79
88
  } catch { /* no settings */ }
@@ -86,23 +95,60 @@ try {
86
95
  }
87
96
  } catch { /* skip */ }
88
97
 
89
- // Find dashboard port
90
- let dashboardInfo = '';
91
- try {
92
- const { resolveDashboardPort } = require('./lib/port-registry');
93
- const port = resolveDashboardPort(projectDir);
94
- if (port) dashboardInfo = ` | Dashboard: http://localhost:${port}`;
95
- } catch { /* no registry */ }
96
-
97
- // Build status line
98
- const parts = [];
99
- if (version) parts.push(`v${version}`);
100
- parts.push(`${profile} profile`);
101
- if (agentCount > 0) parts.push(`${agentCount} agents`);
102
- parts.push(`layers: ${layers.join(', ')}`);
103
-
104
- console.log(`ERNE ${parts.join(' | ')}${dashboardInfo}`);
105
- console.log(`Use /erne- commands (e.g. /erne-plan, /erne-perf, /erne-doctor)`);
98
+ // ─── Dashboard auto-start ────────────────────────────────────────────────────
99
+
100
+ let dashboardUrl = '';
101
+
102
+ if (hasSettings) {
103
+ try {
104
+ const { getRegisteredPort } = require('./lib/port-registry');
105
+ const existingPort = getRegisteredPort(projectDir);
106
+
107
+ if (existingPort) {
108
+ dashboardUrl = `http://localhost:${existingPort}`;
109
+ } else if (!process.env.ERNE_SKIP_DASHBOARD) {
110
+ // Try to start dashboard in background
111
+ try {
112
+ const dashboardDir = path.resolve(__dirname, '..', '..', 'dashboard');
113
+ const serverScript = path.join(dashboardDir, 'server.js');
114
+ const depsExist = fs.existsSync(path.join(dashboardDir, 'node_modules', 'better-sqlite3'));
115
+
116
+ if (fs.existsSync(serverScript) && depsExist) {
117
+ const { fork } = require('child_process');
118
+ const child = fork(serverScript, [], {
119
+ cwd: projectDir,
120
+ detached: true,
121
+ stdio: 'ignore',
122
+ env: { ...process.env, ERNE_PROJECT_DIR: projectDir },
123
+ });
124
+ child.unref();
125
+ dashboardUrl = 'http://localhost:3333 (starting...)';
126
+ }
127
+ } catch { /* dashboard not available — skip */ }
128
+ }
129
+ } catch { /* port-registry not available — skip */ }
130
+ }
131
+
132
+ // ─── Print banner ────────────────────────────────────────────────────────────
133
+
134
+ if (hasSettings) {
135
+ // Rich banner when ERNE is properly installed
136
+ const parts = [];
137
+ if (version) parts.push(`v${version}`);
138
+ parts.push(profile);
139
+ if (agentCount > 0) parts.push(`${agentCount} agents`);
140
+ if (dashboardUrl) parts.push(`Dashboard: ${dashboardUrl}`);
141
+ console.log(`ERNE ${parts.join(' | ')}`);
142
+ } else {
143
+ // Fallback: layer-based output for projects without full ERNE init
144
+ const parts = [];
145
+ if (version) parts.push(`v${version}`);
146
+ parts.push(`${profile} profile`);
147
+ if (agentCount > 0) parts.push(`${agentCount} agents`);
148
+ parts.push(`layers: ${layers.join(', ')}`);
149
+ console.log(`ERNE ${parts.join(' | ')}`);
150
+ console.log(`Use /erne- commands (e.g. /erne-plan, /erne-perf, /erne-doctor)`);
151
+ }
106
152
 
107
153
  if (!hasSignals) {
108
154
  process.exit(2); // warn