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 +24 -174
- package/commands/erne-init.md +35 -31
- package/lib/init.js +96 -60
- package/package.json +1 -1
- package/scripts/hooks/session-start.js +67 -21
package/CLAUDE.md
CHANGED
|
@@ -1,185 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<!-- ERNE-GENERATED -->
|
|
2
|
+
<!-- erne-profile: standard -->
|
|
3
|
+
# erne-universal — ERNE Configuration
|
|
4
4
|
|
|
5
5
|
## Project Stack
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **Testing**:
|
|
14
|
-
- **
|
|
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
|
-
-
|
|
23
|
-
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
##
|
|
31
|
+
## Rules
|
|
32
|
+
@import .claude/rules/common/
|
|
177
33
|
|
|
178
|
-
Skills
|
|
179
|
-
|
|
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/
|
package/commands/erne-init.md
CHANGED
|
@@ -1,54 +1,58 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: erne-init
|
|
3
|
-
description: ERNE —
|
|
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
|
-
#
|
|
6
|
+
# Initialize ERNE
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
##
|
|
10
|
+
## Step 1: Ask preferences
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Before running anything, ask the user two things in a single message:
|
|
13
13
|
|
|
14
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
## Step 2: Run the init command
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Once you have their preferences, run this in Bash:
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
```bash
|
|
28
|
+
npx erne-universal@latest init --yes --profile <profile> [--no-mcp]
|
|
29
|
+
```
|
|
29
30
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
35
|
+
## Step 3: Launch the dashboard
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
After init completes, start the dashboard:
|
|
40
38
|
|
|
41
39
|
```bash
|
|
42
|
-
npx erne-universal
|
|
40
|
+
npx erne-universal dashboard &
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
##
|
|
47
|
+
## Step 4: Tell the user what happened
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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]
|
|
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',
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
183
|
+
console.log(`\n ${warn} No React Native project detected \u2014 continuing (non-interactive mode).`);
|
|
150
184
|
} else {
|
|
151
|
-
console.log(
|
|
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
|
|
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(
|
|
203
|
+
console.log(`\n ${c.bold}Step 2:${c.reset} Hook profile \u2192 ${c.cyan}standard${c.reset}`);
|
|
170
204
|
} else {
|
|
171
|
-
console.log(
|
|
172
|
-
console.log(' (a) minimal
|
|
173
|
-
console.log(' (b) standard
|
|
174
|
-
console.log(' (c) strict
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
212
|
-
{ key: 'firebase', label: 'Firebase
|
|
213
|
-
{ key: 'figma', label: 'Figma
|
|
214
|
-
{ key: 'sentry', label: 'Sentry
|
|
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(
|
|
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 (
|
|
251
|
-
regenerated: 'CLAUDE.md
|
|
252
|
-
generated: 'CLAUDE.md
|
|
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(`
|
|
290
|
+
console.log(` ${ok} ${claudeMdMessages[claudeMdResult]}`);
|
|
255
291
|
|
|
256
292
|
// ─── Step 5: Project Audit ───
|
|
257
|
-
console.log(
|
|
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 ?
|
|
264
|
-
|
|
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(`
|
|
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(`
|
|
307
|
+
console.log(` ${warn} Audit skipped: ${err.message}`);
|
|
274
308
|
}
|
|
275
309
|
|
|
276
|
-
// ───
|
|
277
|
-
console.log(
|
|
278
|
-
console.log(
|
|
279
|
-
console.log(
|
|
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,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
|
|
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
|
|
81
|
+
let hasSettings = false;
|
|
82
|
+
|
|
75
83
|
try {
|
|
76
|
-
const settings = JSON.parse(fs.readFileSync(
|
|
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
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|