erne-universal 0.1.0
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-plugin/plugin.json +92 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/agents/architect.md +64 -0
- package/agents/code-reviewer.md +72 -0
- package/agents/expo-config-resolver.md +77 -0
- package/agents/native-bridge-builder.md +98 -0
- package/agents/performance-profiler.md +89 -0
- package/agents/tdd-guide.md +86 -0
- package/agents/ui-designer.md +100 -0
- package/agents/upgrade-assistant.md +106 -0
- package/bin/cli.js +55 -0
- package/commands/animate.md +70 -0
- package/commands/build-fix.md +57 -0
- package/commands/code-review.md +51 -0
- package/commands/component.md +93 -0
- package/commands/debug.md +74 -0
- package/commands/deploy.md +82 -0
- package/commands/learn.md +56 -0
- package/commands/native-module.md +51 -0
- package/commands/navigate.md +69 -0
- package/commands/perf.md +68 -0
- package/commands/plan.md +49 -0
- package/commands/quality-gate.md +80 -0
- package/commands/retrospective.md +70 -0
- package/commands/setup-device.md +99 -0
- package/commands/tdd.md +51 -0
- package/commands/upgrade.md +78 -0
- package/contexts/dev.md +29 -0
- package/contexts/review.md +32 -0
- package/contexts/vibe.md +44 -0
- package/docs/agents.md +41 -0
- package/docs/commands.md +53 -0
- package/docs/creating-skills.md +63 -0
- package/docs/getting-started.md +60 -0
- package/docs/hooks-profiles.md +73 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-1-infrastructure-hooks.md +3973 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-2-content-layer.md +4496 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-3-skills-knowledge-base.md +1952 -0
- package/docs/superpowers/plans/2026-03-10-erne-plan-4-install-cli-distribution.md +1624 -0
- package/docs/superpowers/specs/2026-03-10-everything-react-native-expo-design.md +581 -0
- package/examples/claude-md-bare-rn.md +46 -0
- package/examples/claude-md-expo-managed.md +45 -0
- package/examples/eas-json-standard.json +41 -0
- package/hooks/hooks.json +113 -0
- package/hooks/profiles/minimal.json +9 -0
- package/hooks/profiles/standard.json +17 -0
- package/hooks/profiles/strict.json +22 -0
- package/install.sh +50 -0
- package/mcp-configs/agent-device.json +10 -0
- package/mcp-configs/appstore-connect.json +15 -0
- package/mcp-configs/expo-api.json +13 -0
- package/mcp-configs/figma.json +13 -0
- package/mcp-configs/firebase.json +14 -0
- package/mcp-configs/github.json +13 -0
- package/mcp-configs/memory.json +13 -0
- package/mcp-configs/play-console.json +14 -0
- package/mcp-configs/sentry.json +15 -0
- package/mcp-configs/supabase.json +14 -0
- package/package.json +50 -0
- package/rules/bare-rn/coding-style.md +62 -0
- package/rules/bare-rn/patterns.md +54 -0
- package/rules/bare-rn/security.md +58 -0
- package/rules/bare-rn/testing.md +78 -0
- package/rules/common/coding-style.md +50 -0
- package/rules/common/development-workflow.md +55 -0
- package/rules/common/git-workflow.md +40 -0
- package/rules/common/navigation.md +56 -0
- package/rules/common/patterns.md +59 -0
- package/rules/common/performance.md +55 -0
- package/rules/common/security.md +64 -0
- package/rules/common/state-management.md +86 -0
- package/rules/common/testing.md +61 -0
- package/rules/expo/coding-style.md +54 -0
- package/rules/expo/patterns.md +71 -0
- package/rules/expo/security.md +41 -0
- package/rules/expo/testing.md +68 -0
- package/rules/native-android/coding-style.md +81 -0
- package/rules/native-android/patterns.md +77 -0
- package/rules/native-android/security.md +80 -0
- package/rules/native-android/testing.md +94 -0
- package/rules/native-ios/coding-style.md +72 -0
- package/rules/native-ios/patterns.md +72 -0
- package/rules/native-ios/security.md +59 -0
- package/rules/native-ios/testing.md +79 -0
- package/schemas/hooks.schema.json +34 -0
- package/schemas/plugin.schema.json +55 -0
- package/scripts/hooks/accessibility-check.js +117 -0
- package/scripts/hooks/bundle-size-check.js +31 -0
- package/scripts/hooks/check-console-log.js +37 -0
- package/scripts/hooks/check-expo-config.js +40 -0
- package/scripts/hooks/check-platform-specific.js +40 -0
- package/scripts/hooks/check-reanimated-worklet.js +51 -0
- package/scripts/hooks/continuous-learning-observer.js +24 -0
- package/scripts/hooks/evaluate-session.js +26 -0
- package/scripts/hooks/lib/hook-utils.js +52 -0
- package/scripts/hooks/native-compat-check.js +42 -0
- package/scripts/hooks/performance-budget.js +57 -0
- package/scripts/hooks/post-edit-format.js +38 -0
- package/scripts/hooks/post-edit-typecheck.js +31 -0
- package/scripts/hooks/pre-commit-lint.js +44 -0
- package/scripts/hooks/pre-edit-test-gate.js +68 -0
- package/scripts/hooks/run-with-flags.js +93 -0
- package/scripts/hooks/security-scan.js +65 -0
- package/scripts/hooks/session-start.js +77 -0
- package/scripts/lint-content.js +62 -0
- package/scripts/validate-all.js +137 -0
- package/skills/coding-standards/SKILL.md +88 -0
- package/skills/continuous-learning-v2/SKILL.md +61 -0
- package/skills/continuous-learning-v2/agent-prompts/pattern-analyzer.md +51 -0
- package/skills/continuous-learning-v2/agent-prompts/skill-generator.md +74 -0
- package/skills/continuous-learning-v2/config.json +25 -0
- package/skills/continuous-learning-v2/hook-templates/evaluate-session.cjs.template +69 -0
- package/skills/continuous-learning-v2/hook-templates/observer-hook.cjs.template +54 -0
- package/skills/continuous-learning-v2/scripts/analyze-patterns.js +50 -0
- package/skills/continuous-learning-v2/scripts/extract-session-patterns.js +54 -0
- package/skills/continuous-learning-v2/scripts/validate-content.js +88 -0
- package/skills/native-module-scaffold/SKILL.md +118 -0
- package/skills/performance-optimization/SKILL.md +103 -0
- package/skills/security-review/SKILL.md +99 -0
- package/skills/tdd-workflow/SKILL.md +142 -0
- package/skills/upgrade-workflow/SKILL.md +140 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/validate-all.js — Validate all ERNE content files
|
|
3
|
+
// Checks: frontmatter format, JSON validity, required fields, file counts
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
let errors = 0;
|
|
11
|
+
let warnings = 0;
|
|
12
|
+
let checked = 0;
|
|
13
|
+
|
|
14
|
+
function error(msg) { errors++; console.error(` ✗ ${msg}`); }
|
|
15
|
+
function warn(msg) { warnings++; console.warn(` ⚠ ${msg}`); }
|
|
16
|
+
function ok(msg) { console.log(` ✓ ${msg}`); }
|
|
17
|
+
|
|
18
|
+
// ─── Validate frontmatter in .md files ───
|
|
19
|
+
function validateFrontmatter(filePath, requiredFields) {
|
|
20
|
+
checked++;
|
|
21
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
22
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
23
|
+
|
|
24
|
+
if (!match) {
|
|
25
|
+
error(`${filePath}: Missing frontmatter`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const frontmatter = match[1];
|
|
30
|
+
for (const field of requiredFields) {
|
|
31
|
+
if (!frontmatter.includes(`${field}:`)) {
|
|
32
|
+
error(`${filePath}: Missing required field '${field}'`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Validate JSON files ───
|
|
38
|
+
function validateJson(filePath) {
|
|
39
|
+
checked++;
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
42
|
+
JSON.parse(content);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
error(`${filePath}: Invalid JSON — ${e.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Validate directory file counts ───
|
|
49
|
+
function validateCount(dir, ext, expected, label) {
|
|
50
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith(ext));
|
|
51
|
+
if (files.length !== expected) {
|
|
52
|
+
error(`${label}: Expected ${expected} files, found ${files.length}`);
|
|
53
|
+
} else {
|
|
54
|
+
ok(`${label}: ${files.length} files`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Main validation ───
|
|
59
|
+
console.log('\n ERNE Content Validation\n');
|
|
60
|
+
|
|
61
|
+
// Agents
|
|
62
|
+
console.log(' Agents:');
|
|
63
|
+
validateCount('agents', '.md', 8, 'agents/');
|
|
64
|
+
const agentFiles = fs.readdirSync('agents').filter(f => f.endsWith('.md'));
|
|
65
|
+
for (const f of agentFiles) {
|
|
66
|
+
validateFrontmatter(path.join('agents', f), ['name', 'description']);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Commands
|
|
70
|
+
console.log(' Commands:');
|
|
71
|
+
validateCount('commands', '.md', 16, 'commands/');
|
|
72
|
+
const cmdFiles = fs.readdirSync('commands').filter(f => f.endsWith('.md'));
|
|
73
|
+
for (const f of cmdFiles) {
|
|
74
|
+
validateFrontmatter(path.join('commands', f), ['name', 'description']);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Rules
|
|
78
|
+
console.log(' Rules:');
|
|
79
|
+
const ruleLayers = ['common', 'expo', 'bare-rn', 'native-ios', 'native-android'];
|
|
80
|
+
for (const layer of ruleLayers) {
|
|
81
|
+
const layerDir = path.join('rules', layer);
|
|
82
|
+
if (!fs.existsSync(layerDir)) {
|
|
83
|
+
error(`rules/${layer}/: Missing directory`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const ruleFiles = fs.readdirSync(layerDir).filter(f => f.endsWith('.md'));
|
|
87
|
+
ok(`rules/${layer}/: ${ruleFiles.length} files`);
|
|
88
|
+
for (const f of ruleFiles) {
|
|
89
|
+
validateFrontmatter(path.join(layerDir, f), ['description']);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Hook profiles
|
|
94
|
+
console.log(' Hooks:');
|
|
95
|
+
validateJson('hooks/hooks.json');
|
|
96
|
+
for (const profile of ['minimal', 'standard', 'strict']) {
|
|
97
|
+
validateJson(path.join('hooks', 'profiles', `${profile}.json`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MCP configs
|
|
101
|
+
console.log(' MCP Configs:');
|
|
102
|
+
const mcpFiles = fs.readdirSync('mcp-configs').filter(f => f.endsWith('.json'));
|
|
103
|
+
ok(`mcp-configs/: ${mcpFiles.length} files`);
|
|
104
|
+
for (const f of mcpFiles) {
|
|
105
|
+
validateJson(path.join('mcp-configs', f));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Contexts
|
|
109
|
+
console.log(' Contexts:');
|
|
110
|
+
validateCount('contexts', '.md', 3, 'contexts/');
|
|
111
|
+
|
|
112
|
+
// Skills
|
|
113
|
+
console.log(' Skills:');
|
|
114
|
+
const skillDirs = fs.readdirSync('skills', { withFileTypes: true })
|
|
115
|
+
.filter(d => d.isDirectory())
|
|
116
|
+
.map(d => d.name);
|
|
117
|
+
ok(`skills/: ${skillDirs.length} skill directories`);
|
|
118
|
+
for (const dir of skillDirs) {
|
|
119
|
+
const skillMd = path.join('skills', dir, 'SKILL.md');
|
|
120
|
+
if (!fs.existsSync(skillMd)) {
|
|
121
|
+
error(`skills/${dir}/: Missing SKILL.md`);
|
|
122
|
+
} else {
|
|
123
|
+
checked++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Schemas
|
|
128
|
+
console.log(' Schemas:');
|
|
129
|
+
validateJson('schemas/hooks.schema.json');
|
|
130
|
+
validateJson('schemas/plugin.schema.json');
|
|
131
|
+
|
|
132
|
+
// Summary
|
|
133
|
+
console.log(`\n Checked ${checked} files: ${errors} errors, ${warnings} warnings\n`);
|
|
134
|
+
|
|
135
|
+
if (errors > 0) {
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coding-standards
|
|
3
|
+
description: Enforce React Native coding standards as an actionable workflow
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Coding Standards Enforcement
|
|
7
|
+
|
|
8
|
+
You are enforcing coding standards for a React Native project. This skill turns passive rules into an active audit workflow.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Invoke when:
|
|
13
|
+
- Starting work on a new codebase
|
|
14
|
+
- Reviewing code for standards compliance
|
|
15
|
+
- Setting up a new project's conventions
|
|
16
|
+
|
|
17
|
+
## Audit Process
|
|
18
|
+
|
|
19
|
+
### Step 1: Detect Project Configuration
|
|
20
|
+
|
|
21
|
+
Read the project's `.claude/rules/` to understand which standards apply:
|
|
22
|
+
- `common/` rules always apply
|
|
23
|
+
- `expo/` rules if Expo managed project
|
|
24
|
+
- `bare-rn/` rules if bare React Native
|
|
25
|
+
- `native-ios/` if iOS native code present
|
|
26
|
+
- `native-android/` if Android native code present
|
|
27
|
+
|
|
28
|
+
### Step 2: Scan for Violations
|
|
29
|
+
|
|
30
|
+
Check each category systematically:
|
|
31
|
+
|
|
32
|
+
**Component Structure:**
|
|
33
|
+
- [ ] Functional components only (no class components)
|
|
34
|
+
- [ ] Named exports (not default exports)
|
|
35
|
+
- [ ] Props interface defined above component
|
|
36
|
+
- [ ] Proper TypeScript types (no `any`)
|
|
37
|
+
|
|
38
|
+
**Styling:**
|
|
39
|
+
- [ ] NativeWind/Tailwind classes used (no `StyleSheet.create`)
|
|
40
|
+
- [ ] Consistent color usage (design tokens, not hex literals)
|
|
41
|
+
- [ ] Responsive design using NativeWind breakpoints
|
|
42
|
+
- [ ] Dark mode support via `dark:` variants
|
|
43
|
+
|
|
44
|
+
**State Management:**
|
|
45
|
+
- [ ] Zustand for client state (no Redux, no Context for global state)
|
|
46
|
+
- [ ] TanStack Query for server state (no manual fetch+useState)
|
|
47
|
+
- [ ] No prop drilling beyond 2 levels
|
|
48
|
+
- [ ] Computed values derived, not stored
|
|
49
|
+
|
|
50
|
+
**Navigation:**
|
|
51
|
+
- [ ] Expo Router file-based routing
|
|
52
|
+
- [ ] Typed routes using `href` type safety
|
|
53
|
+
- [ ] Proper layout files (`_layout.tsx`)
|
|
54
|
+
- [ ] Deep linking configured
|
|
55
|
+
|
|
56
|
+
**Performance:**
|
|
57
|
+
- [ ] `FlashList` for lists (not `FlatList`)
|
|
58
|
+
- [ ] `expo-image` for images (not `Image`)
|
|
59
|
+
- [ ] Memoization where appropriate (`useMemo`, `useCallback`)
|
|
60
|
+
- [ ] Animations on UI thread (Reanimated worklets)
|
|
61
|
+
|
|
62
|
+
**Testing:**
|
|
63
|
+
- [ ] Tests exist for new code
|
|
64
|
+
- [ ] Tests use RNTL (not Enzyme)
|
|
65
|
+
- [ ] Tests test behavior, not implementation
|
|
66
|
+
- [ ] Mock at boundaries only
|
|
67
|
+
|
|
68
|
+
### Step 3: Generate Report
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
## Coding Standards Audit
|
|
72
|
+
|
|
73
|
+
### Summary
|
|
74
|
+
- Files scanned: N
|
|
75
|
+
- Violations found: N
|
|
76
|
+
- Auto-fixable: N
|
|
77
|
+
|
|
78
|
+
### Violations by Category
|
|
79
|
+
[Category]: [count]
|
|
80
|
+
- [file:line] — [description]
|
|
81
|
+
|
|
82
|
+
### Recommendations
|
|
83
|
+
[Prioritized list of fixes]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Step 4: Apply Fixes
|
|
87
|
+
|
|
88
|
+
For auto-fixable violations (imports, styling patterns), offer to fix them. For manual fixes (architecture changes), provide specific guidance.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: continuous-learning-v2
|
|
3
|
+
description: Auto-generate skills and rules from observed React Native development patterns
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Continuous Learning v2
|
|
7
|
+
|
|
8
|
+
This skill manages the continuous learning pipeline — observing patterns during development sessions and converting them into persistent rules and skills.
|
|
9
|
+
|
|
10
|
+
## Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
PostToolUse hook (real-time)
|
|
14
|
+
→ continuous-learning-observer.cjs (lightweight pattern capture)
|
|
15
|
+
→ patterns stored in .claude/memory/observations/
|
|
16
|
+
|
|
17
|
+
/learn command (manual, comprehensive)
|
|
18
|
+
→ extract-session-patterns.js (full session analysis)
|
|
19
|
+
→ analyze-patterns.js (pattern clustering + dedup)
|
|
20
|
+
→ skill-generator prompt (create new content)
|
|
21
|
+
→ validate-content.js (verify new content is valid)
|
|
22
|
+
|
|
23
|
+
/retrospective command (session end)
|
|
24
|
+
→ evaluate-session.js (quality metrics + suggestions)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## How It Works
|
|
28
|
+
|
|
29
|
+
### Real-Time (Automatic)
|
|
30
|
+
|
|
31
|
+
The `continuous-learning-observer.cjs` hook runs on `PostToolUse` events. It:
|
|
32
|
+
1. Captures the tool name, file paths, and outcome
|
|
33
|
+
2. Detects repeated patterns (same fix applied > 3 times)
|
|
34
|
+
3. Stores observations in `.claude/memory/observations/` as JSON
|
|
35
|
+
4. Lightweight — adds < 50ms to each tool call
|
|
36
|
+
|
|
37
|
+
### Manual Analysis (`/learn`)
|
|
38
|
+
|
|
39
|
+
When the user runs `/learn`, the pipeline:
|
|
40
|
+
1. Reads all observations from the current session
|
|
41
|
+
2. Clusters them by type (style fix, import pattern, architecture choice)
|
|
42
|
+
3. Compares against existing rules and skills
|
|
43
|
+
4. Generates candidates for new content
|
|
44
|
+
5. Presents candidates for user approval
|
|
45
|
+
6. Writes approved content to `.claude/rules/` or `.claude/skills/`
|
|
46
|
+
|
|
47
|
+
### Session Evaluation (`/retrospective`)
|
|
48
|
+
|
|
49
|
+
At session end, `evaluate-session.js`:
|
|
50
|
+
1. Aggregates all metrics (files changed, tests added, build status)
|
|
51
|
+
2. Evaluates which rules triggered and their usefulness
|
|
52
|
+
3. Suggests rule calibration (tighten/loosen globs, adjust content)
|
|
53
|
+
4. Generates a session quality report
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
See `config.json` for tuning parameters:
|
|
58
|
+
- `observationThreshold`: How many times a pattern must repeat before flagging (default: 3)
|
|
59
|
+
- `maxObservationsPerSession`: Prevent memory bloat (default: 100)
|
|
60
|
+
- `autoApprove`: If true, auto-approve low-risk content (default: false)
|
|
61
|
+
- `contentTypes`: What types to generate — `["rule", "skill"]`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pattern-analyzer
|
|
3
|
+
description: Internal prompt for analyzing collected patterns — NOT a top-level agent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pattern Analysis Prompt
|
|
7
|
+
|
|
8
|
+
You are analyzing patterns observed during a React Native development session. Your job is to identify recurring patterns worth capturing as rules or skills.
|
|
9
|
+
|
|
10
|
+
## Input
|
|
11
|
+
|
|
12
|
+
You receive a JSON array of observations:
|
|
13
|
+
```json
|
|
14
|
+
[
|
|
15
|
+
{
|
|
16
|
+
"timestamp": "2024-03-10T14:30:00Z",
|
|
17
|
+
"tool": "Edit",
|
|
18
|
+
"files": ["src/components/Button.tsx"],
|
|
19
|
+
"pattern": "Replaced StyleSheet.create with NativeWind classes",
|
|
20
|
+
"count": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Analysis Process
|
|
26
|
+
|
|
27
|
+
1. **Group by category** — Cluster similar observations
|
|
28
|
+
2. **Filter by threshold** — Only patterns with 3+ occurrences
|
|
29
|
+
3. **Check novelty** — Compare against existing rules in `.claude/rules/`
|
|
30
|
+
4. **Assess value** — Is this pattern worth encoding as a rule?
|
|
31
|
+
|
|
32
|
+
## Output
|
|
33
|
+
|
|
34
|
+
For each candidate:
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"type": "rule",
|
|
38
|
+
"category": "common/coding-style",
|
|
39
|
+
"title": "Prefer NativeWind over StyleSheet",
|
|
40
|
+
"confidence": "high",
|
|
41
|
+
"occurrences": 5,
|
|
42
|
+
"suggestedContent": "..."
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Rules for Analysis
|
|
47
|
+
|
|
48
|
+
- High confidence: Pattern appeared 5+ times with same fix
|
|
49
|
+
- Medium confidence: Pattern appeared 3-4 times
|
|
50
|
+
- Low confidence: Pattern appeared but context varied
|
|
51
|
+
- Skip: One-off patterns, project-specific hacks, temporary workarounds
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-generator
|
|
3
|
+
description: Internal prompt for generating new skill content from analyzed patterns — NOT a top-level agent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill/Rule Generation Prompt
|
|
7
|
+
|
|
8
|
+
You are generating a new Claude Code rule or skill from an analyzed pattern. Create content that follows ERNE conventions.
|
|
9
|
+
|
|
10
|
+
## Input
|
|
11
|
+
|
|
12
|
+
You receive a pattern analysis:
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"type": "rule",
|
|
16
|
+
"category": "common/coding-style",
|
|
17
|
+
"title": "Prefer NativeWind over StyleSheet",
|
|
18
|
+
"confidence": "high",
|
|
19
|
+
"occurrences": 5,
|
|
20
|
+
"examples": ["..."]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Generation Rules
|
|
25
|
+
|
|
26
|
+
### For Rules (`.claude/rules/*.md`)
|
|
27
|
+
|
|
28
|
+
Format:
|
|
29
|
+
```markdown
|
|
30
|
+
---
|
|
31
|
+
description: [One-line description of what this rule enforces]
|
|
32
|
+
globs: [File patterns this applies to, e.g., "src/**/*.tsx"]
|
|
33
|
+
alwaysApply: false
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# [Rule Title]
|
|
37
|
+
|
|
38
|
+
[Clear statement of the rule]
|
|
39
|
+
|
|
40
|
+
## Do This
|
|
41
|
+
[Correct example with code]
|
|
42
|
+
|
|
43
|
+
## Don't Do This
|
|
44
|
+
[Incorrect example with code]
|
|
45
|
+
|
|
46
|
+
## Why
|
|
47
|
+
[Brief rationale]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### For Skills (`.claude/skills/*/SKILL.md`)
|
|
51
|
+
|
|
52
|
+
Format:
|
|
53
|
+
```markdown
|
|
54
|
+
---
|
|
55
|
+
name: [kebab-case-name]
|
|
56
|
+
description: [One-line description]
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# [Skill Title]
|
|
60
|
+
|
|
61
|
+
[When to invoke]
|
|
62
|
+
[Step-by-step workflow]
|
|
63
|
+
[Examples]
|
|
64
|
+
[Expected output]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quality Checks
|
|
68
|
+
|
|
69
|
+
Before outputting:
|
|
70
|
+
- [ ] Frontmatter is valid YAML
|
|
71
|
+
- [ ] Content is specific and actionable (not generic advice)
|
|
72
|
+
- [ ] Code examples are correct and runnable
|
|
73
|
+
- [ ] Rule doesn't conflict with existing rules
|
|
74
|
+
- [ ] Skill has clear invocation criteria
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"observationThreshold": 3,
|
|
4
|
+
"maxObservationsPerSession": 100,
|
|
5
|
+
"autoApprove": false,
|
|
6
|
+
"contentTypes": ["rule", "skill"],
|
|
7
|
+
"observationDir": ".claude/memory/observations",
|
|
8
|
+
"generatedDir": ".claude/memory/generated",
|
|
9
|
+
"patterns": {
|
|
10
|
+
"minOccurrences": 3,
|
|
11
|
+
"categories": [
|
|
12
|
+
"style-fix",
|
|
13
|
+
"import-pattern",
|
|
14
|
+
"architecture-choice",
|
|
15
|
+
"error-handling",
|
|
16
|
+
"testing-pattern",
|
|
17
|
+
"performance-fix"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"generation": {
|
|
21
|
+
"ruleTemplate": "agent-prompts/skill-generator.md",
|
|
22
|
+
"analyzerPrompt": "agent-prompts/pattern-analyzer.md",
|
|
23
|
+
"validationScript": "scripts/validate-content.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Template for evaluate-session.cjs hook
|
|
2
|
+
// This runs on session end to generate a retrospective
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const CONFIG = {
|
|
8
|
+
observationDir: '{{observationDir}}',
|
|
9
|
+
generatedDir: '{{generatedDir}}',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = async () => {
|
|
13
|
+
// Read all session observations
|
|
14
|
+
const obsDir = CONFIG.observationDir;
|
|
15
|
+
if (!fs.existsSync(obsDir)) {
|
|
16
|
+
return { message: 'No observations found for this session.' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sessionFiles = fs.readdirSync(obsDir)
|
|
20
|
+
.filter(f => f.startsWith('session-'))
|
|
21
|
+
.sort();
|
|
22
|
+
|
|
23
|
+
if (sessionFiles.length === 0) {
|
|
24
|
+
return { message: 'No observations found for this session.' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Aggregate observations
|
|
28
|
+
let allObservations = [];
|
|
29
|
+
for (const file of sessionFiles) {
|
|
30
|
+
try {
|
|
31
|
+
const data = JSON.parse(fs.readFileSync(path.join(obsDir, file), 'utf8'));
|
|
32
|
+
allObservations = allObservations.concat(data);
|
|
33
|
+
} catch { /* skip corrupt files */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Generate metrics
|
|
37
|
+
const metrics = {
|
|
38
|
+
totalObservations: allObservations.length,
|
|
39
|
+
toolUsage: {},
|
|
40
|
+
filesModified: new Set(),
|
|
41
|
+
timespan: {
|
|
42
|
+
start: allObservations[0]?.timestamp,
|
|
43
|
+
end: allObservations[allObservations.length - 1]?.timestamp,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
for (const obs of allObservations) {
|
|
48
|
+
metrics.toolUsage[obs.tool] = (metrics.toolUsage[obs.tool] || 0) + 1;
|
|
49
|
+
(obs.files || []).forEach(f => metrics.filesModified.add(f));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
metrics.filesModified = [...metrics.filesModified];
|
|
53
|
+
|
|
54
|
+
// Write retrospective
|
|
55
|
+
const retro = {
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
metrics,
|
|
58
|
+
observations: allObservations,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
fs.mkdirSync(CONFIG.generatedDir, { recursive: true });
|
|
62
|
+
const retroFile = path.join(CONFIG.generatedDir, `retro-${Date.now()}.json`);
|
|
63
|
+
fs.writeFileSync(retroFile, JSON.stringify(retro, null, 2));
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
message: `Retrospective saved to ${retroFile}`,
|
|
67
|
+
metrics,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Template for continuous-learning-observer.cjs hook
|
|
2
|
+
// This runs on PostToolUse events and captures patterns
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const CONFIG = {
|
|
8
|
+
observationDir: '{{observationDir}}',
|
|
9
|
+
maxPerSession: {{maxObservationsPerSession}},
|
|
10
|
+
threshold: {{observationThreshold}},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = async ({ tool, filePaths, result }) => {
|
|
14
|
+
// Only observe file-editing tools
|
|
15
|
+
if (!['Edit', 'Write', 'NotebookEdit'].includes(tool)) return;
|
|
16
|
+
|
|
17
|
+
const sessionFile = path.join(CONFIG.observationDir, `session-${Date.now()}.json`);
|
|
18
|
+
|
|
19
|
+
// Ensure observation directory exists
|
|
20
|
+
fs.mkdirSync(CONFIG.observationDir, { recursive: true });
|
|
21
|
+
|
|
22
|
+
// Read existing observations for this session
|
|
23
|
+
let observations = [];
|
|
24
|
+
const sessionFiles = fs.readdirSync(CONFIG.observationDir)
|
|
25
|
+
.filter(f => f.startsWith('session-'))
|
|
26
|
+
.sort()
|
|
27
|
+
.slice(-1);
|
|
28
|
+
|
|
29
|
+
if (sessionFiles.length > 0) {
|
|
30
|
+
try {
|
|
31
|
+
observations = JSON.parse(
|
|
32
|
+
fs.readFileSync(path.join(CONFIG.observationDir, sessionFiles[0]), 'utf8')
|
|
33
|
+
);
|
|
34
|
+
} catch { /* fresh session */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Don't exceed max observations
|
|
38
|
+
if (observations.length >= CONFIG.maxPerSession) return;
|
|
39
|
+
|
|
40
|
+
// Record observation
|
|
41
|
+
observations.push({
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
tool,
|
|
44
|
+
files: filePaths || [],
|
|
45
|
+
resultPreview: typeof result === 'string' ? result.slice(0, 200) : '',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Write back
|
|
49
|
+
const targetFile = sessionFiles.length > 0
|
|
50
|
+
? path.join(CONFIG.observationDir, sessionFiles[0])
|
|
51
|
+
: sessionFile;
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(targetFile, JSON.stringify(observations, null, 2));
|
|
54
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Analyze extracted patterns and generate candidates
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const CONFIG_PATH = path.resolve(__dirname, '../config.json');
|
|
8
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
9
|
+
|
|
10
|
+
function analyzePatterns(patterns) {
|
|
11
|
+
const candidates = [];
|
|
12
|
+
|
|
13
|
+
// Find repeated edits to same file patterns
|
|
14
|
+
const filePatterns = {};
|
|
15
|
+
for (const obs of patterns) {
|
|
16
|
+
for (const file of (obs.files || [])) {
|
|
17
|
+
const dir = path.dirname(file);
|
|
18
|
+
const ext = path.extname(file);
|
|
19
|
+
const key = `${dir}/*${ext}`;
|
|
20
|
+
if (!filePatterns[key]) filePatterns[key] = { count: 0, files: [] };
|
|
21
|
+
filePatterns[key].count++;
|
|
22
|
+
filePatterns[key].files.push(file);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Generate candidates for patterns above threshold
|
|
27
|
+
for (const [pattern, data] of Object.entries(filePatterns)) {
|
|
28
|
+
if (data.count >= config.observationThreshold) {
|
|
29
|
+
candidates.push({
|
|
30
|
+
type: 'rule',
|
|
31
|
+
pattern,
|
|
32
|
+
occurrences: data.count,
|
|
33
|
+
files: [...new Set(data.files)].slice(0, 5),
|
|
34
|
+
confidence: data.count >= 5 ? 'high' : 'medium',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return candidates;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Read from stdin or file
|
|
43
|
+
const input = process.argv[2];
|
|
44
|
+
if (input && fs.existsSync(input)) {
|
|
45
|
+
const data = JSON.parse(fs.readFileSync(input, 'utf8'));
|
|
46
|
+
const candidates = analyzePatterns(data);
|
|
47
|
+
console.log(JSON.stringify(candidates, null, 2));
|
|
48
|
+
} else {
|
|
49
|
+
console.log('Usage: node analyze-patterns.js <observations.json>');
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Extract patterns from session observations for /learn command
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const OBS_DIR = path.resolve('.claude/memory/observations');
|
|
8
|
+
|
|
9
|
+
function extractPatterns() {
|
|
10
|
+
if (!fs.existsSync(OBS_DIR)) {
|
|
11
|
+
console.log('No observations directory found.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const files = fs.readdirSync(OBS_DIR).filter(f => f.endsWith('.json'));
|
|
16
|
+
let allObs = [];
|
|
17
|
+
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(fs.readFileSync(path.join(OBS_DIR, file), 'utf8'));
|
|
21
|
+
allObs = allObs.concat(Array.isArray(data) ? data : [data]);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.error(`Skipping corrupt file: ${file}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`Loaded ${allObs.length} observations from ${files.length} files.`);
|
|
28
|
+
|
|
29
|
+
// Group by file extension to find patterns
|
|
30
|
+
const byExtension = {};
|
|
31
|
+
for (const obs of allObs) {
|
|
32
|
+
for (const file of (obs.files || [])) {
|
|
33
|
+
const ext = path.extname(file);
|
|
34
|
+
if (!byExtension[ext]) byExtension[ext] = [];
|
|
35
|
+
byExtension[ext].push(obs);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Group by tool to find repeated fix patterns
|
|
40
|
+
const byTool = {};
|
|
41
|
+
for (const obs of allObs) {
|
|
42
|
+
if (!byTool[obs.tool]) byTool[obs.tool] = [];
|
|
43
|
+
byTool[obs.tool].push(obs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Output summary
|
|
47
|
+
console.log('\n=== Pattern Summary ===');
|
|
48
|
+
console.log(`File types modified: ${Object.keys(byExtension).join(', ')}`);
|
|
49
|
+
console.log(`Tools used: ${JSON.stringify(byTool, (k, v) => Array.isArray(v) ? v.length : v)}`);
|
|
50
|
+
|
|
51
|
+
return { byExtension, byTool, total: allObs.length };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
extractPatterns();
|