@stackmemoryai/stackmemory 0.5.67 → 0.6.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/README.md CHANGED
@@ -14,7 +14,9 @@ StackMemory is a **production-ready memory runtime** for AI coding tools that pr
14
14
  - **Full Linear integration** with bidirectional sync
15
15
  - **Context persistence** that survives /clear operations
16
16
  - **Hierarchical frame organization** (nested call stack model)
17
- - **490 tests passing** with comprehensive coverage
17
+ - **Skills system** with `/spec` and `/linear-run` for Claude Code
18
+ - **Automatic hooks** for task tracking, Linear sync, and spec progress
19
+ - **498 tests passing** with comprehensive coverage
18
20
 
19
21
  Instead of a linear chat log, StackMemory organizes memory as a **call stack** of scoped work (frames), with intelligent LLM-driven retrieval and team collaboration features.
20
22
 
@@ -34,13 +36,13 @@ Tools forget decisions and constraints between sessions. StackMemory makes conte
34
36
 
35
37
  ## Features (at a glance)
36
38
 
37
- - MCP tools for Claude Code: 26+ tools; context on every request
38
- - Safe branches: worktree isolation with `--worktree` or `-w`
39
- - Persistent context: frames, anchors, decisions, retrieval
40
- - Optional boosts: model routing, prompt optimization (GEPA)
41
- - Integrations: Linear, Greptile, DiffMem, Browser MCP
42
-
43
- See the docs directory for deeper feature guides.
39
+ - **MCP tools** for Claude Code: 26+ tools; context on every request
40
+ - **Skills**: `/spec` (iterative spec generation), `/linear-run` (task execution via RLM)
41
+ - **Hooks**: automatic context save, task tracking, Linear sync, PROMPT_PLAN updates
42
+ - **Prompt Forge**: watches AGENTS.md and CLAUDE.md for prompt optimization (GEPA)
43
+ - **Safe branches**: worktree isolation with `--worktree` or `-w`
44
+ - **Persistent context**: frames, anchors, decisions, retrieval
45
+ - **Integrations**: Linear, Greptile, DiffMem, Browser MCP
44
46
 
45
47
  ---
46
48
 
@@ -131,13 +133,127 @@ Env defaults (optional):
131
133
 
132
134
  ---
133
135
 
134
- ## Quick Start
136
+ ## Skills System
137
+
138
+ StackMemory ships Claude Code skills that integrate directly into your workflow. Skills are invoked via `/skill-name` in Claude Code or `stackmemory skills <name>` from the CLI.
139
+
140
+ ### Spec Generator (`/spec`)
141
+
142
+ Generates iterative spec documents following a 4-doc progressive chain. Each document reads previous ones from disk for context.
143
+
144
+ ```
145
+ ONE_PAGER.md → DEV_SPEC.md → PROMPT_PLAN.md → AGENTS.md
146
+ (standalone) (reads 1) (reads 1+2) (reads 1+2+3)
147
+ ```
148
+
149
+ ```bash
150
+ # Generate specs in order
151
+ /spec one-pager "My App" # Problem, audience, core flow, MVP
152
+ /spec dev-spec # Architecture, tech stack, APIs
153
+ /spec prompt-plan # TDD stages A-G with checkboxes
154
+ /spec agents # Agent guardrails and responsibilities
155
+
156
+ # Manage progress
157
+ /spec list # Show existing specs
158
+ /spec update prompt-plan "auth" # Check off matching items
159
+ /spec validate prompt-plan # Check completion status
160
+
161
+ # CLI equivalent
162
+ stackmemory skills spec one-pager "My App"
163
+ ```
164
+
165
+ Output goes to `docs/specs/`. Use `--force` to regenerate an existing spec.
166
+
167
+ ### Linear Task Runner (`/linear-run`)
168
+
169
+ Pulls tasks from Linear, executes them via the RLM orchestrator (8 subagent types), and syncs results back.
170
+
171
+ ```bash
172
+ /linear-run next # Execute next todo task
173
+ /linear-run next --priority high # Filter by priority
174
+ /linear-run all # Execute all pending tasks
175
+ /linear-run all --dry-run # Preview without executing
176
+ /linear-run task STA-123 # Run a specific task
177
+ /linear-run preview # Show execution plan
178
+
179
+ # CLI equivalent
180
+ stackmemory ralph linear next
181
+ ```
182
+
183
+ On task completion:
184
+ 1. Marks the Linear task as `done`
185
+ 2. Auto-checks matching PROMPT_PLAN items
186
+ 3. Syncs metrics (tokens, cost, tests) back to Linear
187
+
188
+ Options: `--priority <level>`, `--tag <tag>`, `--dry-run`, `--maxConcurrent <n>`
189
+
190
+ ---
191
+
192
+ ## Hooks (Automatic)
193
+
194
+ StackMemory installs Claude Code hooks that run automatically during your session. Hooks are non-blocking and fail silently to never interrupt your workflow.
195
+
196
+ ### Installed Hooks
197
+
198
+ | Hook | Trigger | What it does |
199
+ |------|---------|-------------|
200
+ | `on-task-complete` | Task marked done | Saves context, syncs Linear (STA-* tasks), auto-checks PROMPT_PLAN items |
201
+ | `on-startup` | Session start | Loads StackMemory context, initializes frame |
202
+ | `on-clear` | `/clear` command | Persists context before clearing |
203
+ | `skill-eval` | User prompt | Scores prompt against 28 skill patterns, recommends relevant skills |
204
+ | `tool-use-trace` | Tool invocation | Logs tool usage for context tracking |
205
+
206
+ ### Skill Evaluation
207
+
208
+ When you type a prompt, the `skill-eval` hook scores it against `skill-rules.json` (28 mapped skills with keyword, pattern, intent, and directory matching). Skills scoring above the threshold (default: 3) are recommended.
209
+
210
+ ```json
211
+ // Example: user types "generate a spec for the auth system"
212
+ // skill-eval recommends:
213
+ {
214
+ "recommendedSkills": [
215
+ { "skillName": "spec-generator", "score": 8 },
216
+ { "skillName": "frame-management", "score": 5 }
217
+ ]
218
+ }
219
+ ```
220
+
221
+ ### Hook Installation
222
+
223
+ Hooks install automatically during `npm install` (with user consent). To install or reinstall manually:
224
+
225
+ ```bash
226
+ # Automatic (prompted during npm install)
227
+ npm install -g @stackmemoryai/stackmemory
228
+
229
+ # Manual install
230
+ stackmemory hooks install
135
231
 
136
- See above for install and minimal usage. For advanced options, see Setup.
232
+ # Skip hooks (CI/non-interactive)
233
+ STACKMEMORY_AUTO_HOOKS=true npm install -g @stackmemoryai/stackmemory
234
+ ```
235
+
236
+ Hooks are stored in `~/.claude/hooks/` and configured via `~/.claude/hooks.json`.
237
+
238
+ ### PROMPT_PLAN Auto-Progress
239
+
240
+ When a task completes (via hook or `/linear-run`), StackMemory fuzzy-matches the task title against unchecked `- [ ]` items in `docs/specs/PROMPT_PLAN.md` and checks them off automatically. One item per task completion, best-effort.
137
241
 
138
242
  ---
139
243
 
140
- ## 2. Detailed Setup
244
+ ## Prompt Forge (GEPA)
245
+
246
+ When launching via `claude-sm`, StackMemory watches `CLAUDE.md`, `AGENT.md`, and `AGENTS.md` for changes. On file modification, the GEPA optimizer analyzes content and suggests improvements for prompt clarity and structure. Runs as a detached background process.
247
+
248
+ ```bash
249
+ # Launch with Prompt Forge active
250
+ claude-sm
251
+
252
+ # Status shown in terminal:
253
+ # Prompt Forge: watching CLAUDE.md, AGENTS.md for optimization
254
+ ```
255
+
256
+ ---
141
257
 
142
258
  ### Install
143
259
 
@@ -180,15 +296,11 @@ stackmemory doctor
180
296
 
181
297
  Checks project initialization, database integrity, MCP config, and suggests fixes.
182
298
 
183
- ---
184
-
185
- ## 3. Advanced Setup
186
-
187
- See https://github.com/stackmemoryai/stackmemory/blob/main/docs/setup.md for advanced options (hosted mode, ChromaDB, manual MCP config, and available tools).
299
+ See [docs/setup.md](https://github.com/stackmemoryai/stackmemory/blob/main/docs/setup.md) for advanced options (hosted mode, ChromaDB, manual MCP config).
188
300
 
189
301
  ---
190
302
 
191
- ## 2. Open-Source Local Mode
303
+ ## Open-Source Local Mode
192
304
 
193
305
  ### Step 1: Clone & Build
194
306
 
@@ -285,35 +397,17 @@ StackMemory implements an intelligent two-tier storage architecture:
285
397
 
286
398
  ## Claude Code Integration
287
399
 
288
- StackMemory can automatically save context when using Claude Code, so your AI assistant has access to previous context and decisions.
289
-
290
- ### Quick Setup
400
+ StackMemory integrates with Claude Code via MCP tools, skills, and hooks. See the [Hooks](#hooks-automatic) and [Skills](#skills-system) sections above.
291
401
 
292
402
  ```bash
293
- # Add alias
294
- echo 'alias claude="~/Dev/stackmemory/scripts/claude-code-wrapper.sh"' >> ~/.zshrc
295
- source ~/.zshrc
296
-
297
- # Use: claude (saves context on exit)
298
- ```
299
-
300
- ### Integration Methods
301
-
302
- ```bash
303
- # 1. Shell wrapper (recommended)
304
- claude [--auto-sync] [--sync-interval=10]
305
-
306
- # 2. Linear auto-sync daemon
307
- ./scripts/linear-auto-sync.sh start [interval]
308
-
309
- # 3. Background daemon
310
- ./scripts/stackmemory-daemon.sh [interval] &
311
-
312
- # 4. Git hooks
313
- ./scripts/setup-git-hooks.sh
403
+ # Full setup (one-time)
404
+ npm install -g @stackmemoryai/stackmemory # installs hooks automatically
405
+ cd your-project && stackmemory init # init project
406
+ stackmemory setup-mcp # configure MCP
407
+ stackmemory doctor # verify everything works
314
408
  ```
315
409
 
316
- **Features:** Auto-save on exit, Linear sync, runs only in StackMemory projects, configurable sync intervals.
410
+ Additional integration methods: shell wrapper (`claude-sm`), Linear auto-sync daemon, background daemon, git hooks. See [docs/setup.md](https://github.com/stackmemoryai/stackmemory/blob/main/docs/setup.md).
317
411
 
318
412
  ## RLM (Recursive Language Model) Orchestration
319
413
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.5.67",
4
- "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, and smart retrieval.",
3
+ "version": "0.6.0",
4
+ "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -42,12 +42,17 @@
42
42
  "llm",
43
43
  "mcp",
44
44
  "claude",
45
+ "claude-code",
45
46
  "coding",
46
47
  "persistence",
47
- "code-review",
48
- "predictive-edit",
48
+ "skills",
49
+ "hooks",
50
+ "spec-generator",
51
+ "linear",
52
+ "task-runner",
49
53
  "prompt-optimization",
50
- "model-routing"
54
+ "model-routing",
55
+ "agent-orchestration"
51
56
  ],
52
57
  "scripts": {
53
58
  "start": "node dist/src/integrations/mcp/server.js",
@@ -58,6 +58,8 @@ async function askForConsent() {
58
58
  console.log(' - Track tool usage for better context');
59
59
  console.log(' - Enable session persistence across restarts');
60
60
  console.log(' - Sync context with Linear (optional)');
61
+ console.log(' - Auto-update PROMPT_PLAN on task completion');
62
+ console.log(' - Recommend relevant skills based on your prompts');
61
63
  console.log('\nThis will modify files in ~/.claude/\n');
62
64
 
63
65
  return new Promise((resolve) => {
@@ -89,11 +91,19 @@ async function installClaudeHooks() {
89
91
  console.log('Created ~/.claude/hooks directory');
90
92
  }
91
93
 
92
- // Copy hook files
93
- const hookFiles = ['tool-use-trace.js', 'on-startup.js', 'on-clear.js'];
94
+ // Copy hook files (scripts + data files)
95
+ const hookFiles = [
96
+ 'tool-use-trace.js',
97
+ 'on-startup.js',
98
+ 'on-clear.js',
99
+ 'on-task-complete.js',
100
+ 'skill-eval.sh',
101
+ 'skill-eval.cjs',
102
+ ];
103
+ const dataFiles = ['skill-rules.json'];
94
104
  let installed = 0;
95
105
 
96
- for (const hookFile of hookFiles) {
106
+ for (const hookFile of [...hookFiles, ...dataFiles]) {
97
107
  const srcPath = join(templatesDir, hookFile);
98
108
  const destPath = join(claudeHooksDir, hookFile);
99
109
 
@@ -107,12 +117,14 @@ async function installClaudeHooks() {
107
117
 
108
118
  copyFileSync(srcPath, destPath);
109
119
 
110
- // Make executable
111
- try {
112
- const { execSync } = await import('child_process');
113
- execSync(`chmod +x "${destPath}"`, { stdio: 'ignore' });
114
- } catch {
115
- // Silent fail on chmod
120
+ // Make executable (scripts only, not data files)
121
+ if (!dataFiles.includes(hookFile)) {
122
+ try {
123
+ const { execSync } = await import('child_process');
124
+ execSync(`chmod +x "${destPath}"`, { stdio: 'ignore' });
125
+ } catch {
126
+ // Silent fail on chmod
127
+ }
116
128
  }
117
129
 
118
130
  installed++;
@@ -136,6 +148,8 @@ async function installClaudeHooks() {
136
148
  'tool-use-approval': join(claudeHooksDir, 'tool-use-trace.js'),
137
149
  'on-startup': join(claudeHooksDir, 'on-startup.js'),
138
150
  'on-clear': join(claudeHooksDir, 'on-clear.js'),
151
+ 'on-task-complete': join(claudeHooksDir, 'on-task-complete.js'),
152
+ 'user-prompt-submit': join(claudeHooksDir, 'skill-eval.sh'),
139
153
  };
140
154
 
141
155
  writeFileSync(claudeConfigFile, JSON.stringify(newHooksConfig, null, 2));
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "tool-use-approval": "~/.claude/hooks/tool-use-trace.js",
3
3
  "on-startup": "~/.claude/hooks/on-startup.js",
4
- "on-clear": "~/.claude/hooks/on-clear.js"
5
- }
4
+ "on-clear": "~/.claude/hooks/on-clear.js",
5
+ "on-task-complete": "~/.claude/hooks/on-task-complete.js",
6
+ "user-prompt-submit": "~/.claude/hooks/skill-eval.sh"
7
+ }
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * On-task-complete hook for StackMemory
5
+ * Triggers when a task is marked as done in Claude Code.
6
+ *
7
+ * Actions:
8
+ * 1. Auto-updates PROMPT_PLAN.md checkboxes (fuzzy match on task keywords)
9
+ * 2. Syncs Linear tasks (if STA-* identifier present)
10
+ * 3. Logs to ~/.stackmemory/logs/hook-errors.log on failure (non-blocking)
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+
16
+ async function onTaskComplete() {
17
+ try {
18
+ const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
19
+
20
+ // Sync Linear if STA task
21
+ if (input.task && input.task.includes('STA-')) {
22
+ try {
23
+ const smBin = path.join(process.env.HOME || '', '.stackmemory', 'bin');
24
+ const syncScript = path.join(
25
+ process.cwd(),
26
+ 'scripts',
27
+ 'sync-linear-graphql.js'
28
+ );
29
+ if (fs.existsSync(syncScript)) {
30
+ const { execSync } = await import('child_process');
31
+ execSync(`node "${syncScript}"`, {
32
+ stdio: 'ignore',
33
+ timeout: 10000,
34
+ });
35
+ }
36
+ } catch (_e) {
37
+ // Non-blocking
38
+ }
39
+ }
40
+
41
+ // Auto-update PROMPT_PLAN checkboxes if spec exists
42
+ const promptPlanPath = path.join(
43
+ process.cwd(),
44
+ 'docs',
45
+ 'specs',
46
+ 'PROMPT_PLAN.md'
47
+ );
48
+ if (fs.existsSync(promptPlanPath) && input.task) {
49
+ try {
50
+ const content = fs.readFileSync(promptPlanPath, 'utf-8');
51
+ const taskWords = input.task.split(/\s+/).filter((w) => w.length > 3);
52
+ const lines = content.split('\n');
53
+ let updated = false;
54
+ for (let i = 0; i < lines.length; i++) {
55
+ if (
56
+ lines[i].includes('- [ ]') &&
57
+ taskWords.some((w) =>
58
+ lines[i].toLowerCase().includes(w.toLowerCase())
59
+ )
60
+ ) {
61
+ lines[i] = lines[i].replace('- [ ]', '- [x]');
62
+ updated = true;
63
+ break;
64
+ }
65
+ }
66
+ if (updated) {
67
+ fs.writeFileSync(promptPlanPath, lines.join('\n'));
68
+ }
69
+ } catch (_e) {
70
+ // Silently fail
71
+ }
72
+ }
73
+ } catch (error) {
74
+ const logDir = path.join(
75
+ process.env.HOME || '/tmp',
76
+ '.stackmemory',
77
+ 'logs'
78
+ );
79
+ try {
80
+ fs.mkdirSync(logDir, { recursive: true });
81
+ fs.appendFileSync(
82
+ path.join(logDir, 'hook-errors.log'),
83
+ `[${new Date().toISOString()}] on-task-complete: ${error.message}\n`
84
+ );
85
+ } catch (_e) {
86
+ // Last resort: silent
87
+ }
88
+ }
89
+ }
90
+
91
+ onTaskComplete();
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Skill Evaluation Engine v2.0
4
+ *
5
+ * Intelligent skill activation based on:
6
+ * - Keywords and patterns in prompts
7
+ * - File paths mentioned or being edited
8
+ * - Directory mappings
9
+ * - Intent detection
10
+ * - Content pattern matching
11
+ *
12
+ * Outputs a structured reminder with matched skills and reasons.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // Configuration
19
+ const RULES_PATH = path.join(__dirname, 'skill-rules.json');
20
+
21
+ /**
22
+ * @typedef {Object} SkillMatch
23
+ * @property {string} name - Skill name
24
+ * @property {number} score - Confidence score
25
+ * @property {string[]} reasons - Why this skill was matched
26
+ * @property {number} priority - Skill priority
27
+ */
28
+
29
+ /**
30
+ * Load skill rules from JSON file
31
+ * @returns {Object} Parsed skill rules
32
+ */
33
+ function loadRules() {
34
+ try {
35
+ const content = fs.readFileSync(RULES_PATH, 'utf-8');
36
+ return JSON.parse(content);
37
+ } catch (error) {
38
+ console.error(`Failed to load skill rules: ${error.message}`);
39
+ process.exit(0); // Exit gracefully to not block the prompt
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Extract file paths mentioned in the prompt
45
+ * @param {string} prompt - User's prompt text
46
+ * @returns {string[]} Array of detected file paths
47
+ */
48
+ function extractFilePaths(prompt) {
49
+ const paths = new Set();
50
+
51
+ // Match explicit paths with extensions (.tsx, .ts, .jsx, .js, .json, .gql, .yaml, .yml, .md, .sh)
52
+ const extensionPattern =
53
+ /(?:^|\s|["'`])([\w\-./]+\.(?:[tj]sx?|json|gql|ya?ml|md|sh))\b/gi;
54
+ let match;
55
+ while ((match = extensionPattern.exec(prompt)) !== null) {
56
+ paths.add(match[1]);
57
+ }
58
+
59
+ // Match paths starting with common directories
60
+ const dirPattern =
61
+ /(?:^|\s|["'`])((?:src|app|components|screens|hooks|utils|services|navigation|graphql|localization|\.claude|\.github|\.maestro)\/[\w\-./]+)/gi;
62
+ while ((match = dirPattern.exec(prompt)) !== null) {
63
+ paths.add(match[1]);
64
+ }
65
+
66
+ // Match quoted paths
67
+ const quotedPattern = /["'`]([\w\-./]+\/[\w\-./]+)["'`]/g;
68
+ while ((match = quotedPattern.exec(prompt)) !== null) {
69
+ paths.add(match[1]);
70
+ }
71
+
72
+ return Array.from(paths);
73
+ }
74
+
75
+ /**
76
+ * Check if a pattern matches the text
77
+ * @param {string} text - Text to search in
78
+ * @param {string} pattern - Regex pattern
79
+ * @param {string} flags - Regex flags
80
+ * @returns {boolean}
81
+ */
82
+ function matchesPattern(text, pattern, flags = 'i') {
83
+ try {
84
+ const regex = new RegExp(pattern, flags);
85
+ return regex.test(text);
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Check if a glob pattern matches a file path
93
+ * @param {string} filePath - File path to check
94
+ * @param {string} globPattern - Glob pattern (simplified)
95
+ * @returns {boolean}
96
+ */
97
+ function matchesGlob(filePath, globPattern) {
98
+ // Convert glob to regex (simplified)
99
+ const regexPattern = globPattern
100
+ .replace(/\./g, '\\.')
101
+ .replace(/\*\*\//g, '<<<DOUBLESTARSLASH>>>')
102
+ .replace(/\*\*/g, '<<<DOUBLESTAR>>>')
103
+ .replace(/\*/g, '[^/]*')
104
+ .replace(/<<<DOUBLESTARSLASH>>>/g, '(.*\\/)?')
105
+ .replace(/<<<DOUBLESTAR>>>/g, '.*')
106
+ .replace(/\?/g, '.');
107
+
108
+ try {
109
+ const regex = new RegExp(`^${regexPattern}$`, 'i');
110
+ return regex.test(filePath);
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Check if file path matches any directory mapping
118
+ * @param {string} filePath - File path to check
119
+ * @param {Object} mappings - Directory to skill mappings
120
+ * @returns {string|null} Matched skill name or null
121
+ */
122
+ function matchDirectoryMapping(filePath, mappings) {
123
+ for (const [dir, skillName] of Object.entries(mappings)) {
124
+ if (filePath === dir || filePath.startsWith(dir + '/')) {
125
+ return skillName;
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+
131
+ /**
132
+ * Evaluate a single skill against the prompt and context
133
+ * @param {string} skillName - Name of the skill
134
+ * @param {Object} skill - Skill configuration
135
+ * @param {string} prompt - User's prompt
136
+ * @param {string} promptLower - Lowercase prompt
137
+ * @param {string[]} filePaths - Extracted file paths
138
+ * @param {Object} rules - Full rules object
139
+ * @returns {SkillMatch|null}
140
+ */
141
+ function evaluateSkill(
142
+ skillName,
143
+ skill,
144
+ prompt,
145
+ promptLower,
146
+ filePaths,
147
+ rules
148
+ ) {
149
+ const { triggers = {}, excludePatterns = [], priority = 5 } = skill;
150
+ const scoring = rules.scoring;
151
+
152
+ let score = 0;
153
+ const reasons = [];
154
+
155
+ // Check exclude patterns first
156
+ for (const excludePattern of excludePatterns) {
157
+ if (matchesPattern(promptLower, excludePattern)) {
158
+ return null;
159
+ }
160
+ }
161
+
162
+ // 1. Check keywords
163
+ if (triggers.keywords) {
164
+ for (const keyword of triggers.keywords) {
165
+ if (promptLower.includes(keyword.toLowerCase())) {
166
+ score += scoring.keyword;
167
+ reasons.push(`keyword "${keyword}"`);
168
+ }
169
+ }
170
+ }
171
+
172
+ // 2. Check keyword patterns (regex)
173
+ if (triggers.keywordPatterns) {
174
+ for (const pattern of triggers.keywordPatterns) {
175
+ if (matchesPattern(promptLower, pattern)) {
176
+ score += scoring.keywordPattern;
177
+ reasons.push(`pattern /${pattern}/`);
178
+ }
179
+ }
180
+ }
181
+
182
+ // 3. Check intent patterns
183
+ if (triggers.intentPatterns) {
184
+ for (const pattern of triggers.intentPatterns) {
185
+ if (matchesPattern(promptLower, pattern)) {
186
+ score += scoring.intentPattern;
187
+ reasons.push(`intent detected`);
188
+ break;
189
+ }
190
+ }
191
+ }
192
+
193
+ // 4. Check context patterns
194
+ if (triggers.contextPatterns) {
195
+ for (const pattern of triggers.contextPatterns) {
196
+ if (promptLower.includes(pattern.toLowerCase())) {
197
+ score += scoring.contextPattern;
198
+ reasons.push(`context "${pattern}"`);
199
+ }
200
+ }
201
+ }
202
+
203
+ // 5. Check file paths against path patterns
204
+ if (triggers.pathPatterns && filePaths.length > 0) {
205
+ for (const filePath of filePaths) {
206
+ for (const pattern of triggers.pathPatterns) {
207
+ if (matchesGlob(filePath, pattern)) {
208
+ score += scoring.pathPattern;
209
+ reasons.push(`path "${filePath}"`);
210
+ break;
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ // 6. Check directory mappings
217
+ if (rules.directoryMappings && filePaths.length > 0) {
218
+ for (const filePath of filePaths) {
219
+ const mappedSkill = matchDirectoryMapping(
220
+ filePath,
221
+ rules.directoryMappings
222
+ );
223
+ if (mappedSkill === skillName) {
224
+ score += scoring.directoryMatch;
225
+ reasons.push(`directory mapping`);
226
+ break;
227
+ }
228
+ }
229
+ }
230
+
231
+ // 7. Check content patterns in prompt (for code snippets)
232
+ if (triggers.contentPatterns) {
233
+ for (const pattern of triggers.contentPatterns) {
234
+ if (matchesPattern(prompt, pattern)) {
235
+ score += scoring.contentPattern;
236
+ reasons.push(`code pattern detected`);
237
+ break;
238
+ }
239
+ }
240
+ }
241
+
242
+ if (score > 0) {
243
+ return {
244
+ name: skillName,
245
+ score,
246
+ reasons: [...new Set(reasons)],
247
+ priority,
248
+ };
249
+ }
250
+
251
+ return null;
252
+ }
253
+
254
+ /**
255
+ * Get related skills that should also be suggested
256
+ * @param {SkillMatch[]} matches - Current matches
257
+ * @param {Object} skills - All skill definitions
258
+ * @returns {string[]} Additional skill names to suggest
259
+ */
260
+ function getRelatedSkills(matches, skills) {
261
+ const matchedNames = new Set(matches.map((m) => m.name));
262
+ const related = new Set();
263
+
264
+ for (const match of matches) {
265
+ const skill = skills[match.name];
266
+ if (skill?.relatedSkills) {
267
+ for (const relatedName of skill.relatedSkills) {
268
+ if (!matchedNames.has(relatedName)) {
269
+ related.add(relatedName);
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ return Array.from(related);
276
+ }
277
+
278
+ /**
279
+ * Format confidence level based on score
280
+ * @param {number} score - Confidence score
281
+ * @param {number} minScore - Minimum threshold
282
+ * @returns {string} Confidence label
283
+ */
284
+ function formatConfidence(score, minScore) {
285
+ if (score >= minScore * 3) return 'HIGH';
286
+ if (score >= minScore * 2) return 'MEDIUM';
287
+ return 'LOW';
288
+ }
289
+
290
+ /**
291
+ * Main evaluation function
292
+ * @param {string} prompt - User's prompt
293
+ * @returns {string} Formatted output
294
+ */
295
+ function evaluate(prompt) {
296
+ const rules = loadRules();
297
+ const { config, skills } = rules;
298
+
299
+ const promptLower = prompt.toLowerCase();
300
+ const filePaths = extractFilePaths(prompt);
301
+
302
+ // Evaluate all skills
303
+ const matches = [];
304
+ for (const [name, skill] of Object.entries(skills)) {
305
+ const match = evaluateSkill(
306
+ name,
307
+ skill,
308
+ prompt,
309
+ promptLower,
310
+ filePaths,
311
+ rules
312
+ );
313
+ if (match && match.score >= config.minConfidenceScore) {
314
+ matches.push(match);
315
+ }
316
+ }
317
+
318
+ if (matches.length === 0) {
319
+ return '';
320
+ }
321
+
322
+ // Sort by score (descending), then by priority (descending)
323
+ matches.sort((a, b) => {
324
+ if (b.score !== a.score) return b.score - a.score;
325
+ return b.priority - a.priority;
326
+ });
327
+
328
+ // Limit to max skills
329
+ const topMatches = matches.slice(0, config.maxSkillsToShow);
330
+
331
+ // Check for related skills
332
+ const relatedSkills = getRelatedSkills(topMatches, skills);
333
+
334
+ // Format output
335
+ let output = '<user-prompt-submit-hook>\n';
336
+ output += 'SKILL ACTIVATION REQUIRED\n\n';
337
+
338
+ if (filePaths.length > 0) {
339
+ output += `Detected file paths: ${filePaths.join(', ')}\n\n`;
340
+ }
341
+
342
+ output += 'Matched skills (ranked by relevance):\n';
343
+
344
+ for (let i = 0; i < topMatches.length; i++) {
345
+ const match = topMatches[i];
346
+ const confidence = formatConfidence(match.score, config.minConfidenceScore);
347
+
348
+ output += `${i + 1}. ${match.name} (${confidence} confidence)\n`;
349
+
350
+ if (config.showMatchReasons && match.reasons.length > 0) {
351
+ output += ` Matched: ${match.reasons.slice(0, 3).join(', ')}\n`;
352
+ }
353
+ }
354
+
355
+ if (relatedSkills.length > 0) {
356
+ output += `\nRelated skills to consider: ${relatedSkills.join(', ')}\n`;
357
+ }
358
+
359
+ output += '\nBefore implementing, you MUST:\n';
360
+ output += '1. EVALUATE: State YES/NO for each skill with brief reasoning\n';
361
+ output += '2. ACTIVATE: Invoke the Skill tool for each YES skill\n';
362
+ output += '3. IMPLEMENT: Only proceed after skill activation\n';
363
+ output += '\nExample evaluation:\n';
364
+ output += `- ${topMatches[0].name}: YES - [your reasoning]\n`;
365
+ if (topMatches.length > 1) {
366
+ output += `- ${topMatches[1].name}: NO - [your reasoning]\n`;
367
+ }
368
+ output += '\nDO NOT skip this step. Invoke relevant skills NOW.\n';
369
+ output += '</user-prompt-submit-hook>';
370
+
371
+ return output;
372
+ }
373
+
374
+ // Main execution
375
+ function main() {
376
+ let input = '';
377
+
378
+ process.stdin.setEncoding('utf8');
379
+
380
+ process.stdin.on('data', (chunk) => {
381
+ input += chunk;
382
+ });
383
+
384
+ process.stdin.on('end', () => {
385
+ let prompt = '';
386
+
387
+ try {
388
+ const data = JSON.parse(input);
389
+ prompt = data.prompt || '';
390
+ } catch {
391
+ prompt = input;
392
+ }
393
+
394
+ if (!prompt.trim()) {
395
+ process.exit(0);
396
+ }
397
+
398
+ try {
399
+ const output = evaluate(prompt);
400
+ if (output) {
401
+ console.log(output);
402
+ }
403
+ } catch (error) {
404
+ console.error(`Skill evaluation failed: ${error.message}`);
405
+ }
406
+
407
+ process.exit(0);
408
+ });
409
+ }
410
+
411
+ main();
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # Skill Evaluation Hook v2.0
3
+ # Wrapper script that delegates to the Node.js evaluation engine
4
+ #
5
+ # This hook runs on UserPromptSubmit and analyzes the prompt for:
6
+ # - Keywords and patterns indicating skill relevance
7
+ # - File paths mentioned in the prompt
8
+ # - Intent patterns (what the user wants to do)
9
+ # - Directory mappings (what directories map to which skills)
10
+ #
11
+ # Configuration is in skill-rules.json
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ NODE_SCRIPT="$SCRIPT_DIR/skill-eval.cjs"
15
+
16
+ # Check if Node.js is available
17
+ if ! command -v node &>/dev/null; then
18
+ # Fallback: exit silently if Node.js not found
19
+ exit 0
20
+ fi
21
+
22
+ # Check if the Node script exists
23
+ if [[ ! -f "$NODE_SCRIPT" ]]; then
24
+ exit 0
25
+ fi
26
+
27
+ # Pipe stdin to the Node.js script (suppress stderr noise from nvm/shell init)
28
+ cat | node "$NODE_SCRIPT" 2>/dev/null
29
+
30
+ # Always exit 0 to allow the prompt through
31
+ exit 0
@@ -0,0 +1,274 @@
1
+ {
2
+ "$schema": "./skill-rules.schema.json",
3
+ "version": "2.0",
4
+ "config": {
5
+ "minConfidenceScore": 3,
6
+ "showMatchReasons": true,
7
+ "maxSkillsToShow": 5
8
+ },
9
+ "scoring": {
10
+ "keyword": 2,
11
+ "keywordPattern": 3,
12
+ "pathPattern": 4,
13
+ "directoryMatch": 5,
14
+ "intentPattern": 4,
15
+ "contentPattern": 3,
16
+ "contextPattern": 2
17
+ },
18
+ "directoryMappings": {
19
+ "src/core": "frame-management",
20
+ "src/cli": "cli-commands",
21
+ "src/integrations/linear": "linear-integration",
22
+ "src/integrations/mcp": "mcp-server",
23
+ "src/features/tui": "terminal-ui",
24
+ "src/core/context": "context-bridge",
25
+ "src/core/storage": "storage-tiers",
26
+ "src/core/tasks": "task-management",
27
+ ".claude": "claude-integration",
28
+ ".github/workflows": "github-actions",
29
+ "scripts": "build-scripts",
30
+ "docs": "documentation"
31
+ },
32
+ "skills": {
33
+ "frame-management": {
34
+ "description": "Frame stack and context management patterns",
35
+ "priority": 9,
36
+ "triggers": {
37
+ "keywords": ["frame", "stack", "context", "framemanager", "push", "pop"],
38
+ "keywordPatterns": ["\\bframe\\b", "\\bstack\\b", "context(?:Bridge)?"],
39
+ "pathPatterns": ["**/frame-*.ts", "**/context/*.ts", "**/stack-*.ts"],
40
+ "intentPatterns": [
41
+ "(?:manage|handle).*(?:frame|context)",
42
+ "(?:push|pop).*(?:frame|stack)"
43
+ ],
44
+ "contentPatterns": ["FrameManager", "pushFrame", "popFrame", "getContext"]
45
+ },
46
+ "relatedSkills": ["context-bridge", "storage-tiers"]
47
+ },
48
+ "linear-integration": {
49
+ "description": "Linear API integration and task sync",
50
+ "priority": 8,
51
+ "triggers": {
52
+ "keywords": ["linear", "issue", "sync", "task sync", "oauth"],
53
+ "keywordPatterns": ["\\blinear\\b", "\\bissue\\b", "sync.*task"],
54
+ "pathPatterns": ["**/linear/**", "**/linear-*.ts"],
55
+ "intentPatterns": [
56
+ "(?:sync|update).*(?:linear|issue)",
57
+ "(?:linear).*(?:task|issue|sync)"
58
+ ],
59
+ "contentPatterns": ["LinearClient", "syncIssue", "createIssue"]
60
+ }
61
+ },
62
+ "mcp-server": {
63
+ "description": "Model Context Protocol server implementation",
64
+ "priority": 8,
65
+ "triggers": {
66
+ "keywords": ["mcp", "tool", "server", "protocol", "claude code"],
67
+ "keywordPatterns": ["\\bmcp\\b", "model.context", "claude.code"],
68
+ "pathPatterns": ["**/mcp/**", "**/mcp-*.ts", "**/tools/**"],
69
+ "intentPatterns": [
70
+ "(?:implement|create).*(?:mcp|tool)",
71
+ "(?:mcp).*(?:server|tool|protocol)"
72
+ ],
73
+ "contentPatterns": ["MCPServer", "registerTool", "@tool", "toolCall"]
74
+ },
75
+ "relatedSkills": ["claude-integration"]
76
+ },
77
+ "testing-patterns": {
78
+ "description": "Jest testing and test infrastructure",
79
+ "priority": 7,
80
+ "triggers": {
81
+ "keywords": ["test", "jest", "spec", "mock", "fixture"],
82
+ "keywordPatterns": ["\\btest\\b", "\\bspec\\b", "\\bjest\\b"],
83
+ "pathPatterns": ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**"],
84
+ "intentPatterns": [
85
+ "(?:write|add|fix).*(?:test|spec)",
86
+ "(?:test).*(?:coverage|suite)"
87
+ ],
88
+ "contentPatterns": ["describe\\(", "it\\(", "expect\\(", "jest\\.mock"]
89
+ }
90
+ },
91
+ "cli-commands": {
92
+ "description": "CLI command implementation and structure",
93
+ "priority": 7,
94
+ "triggers": {
95
+ "keywords": ["cli", "command", "yargs", "terminal", "console"],
96
+ "keywordPatterns": ["\\bcli\\b", "\\bcommand\\b", "\\byargs\\b"],
97
+ "pathPatterns": ["**/cli/**", "**/commands/**", "**/cli-*.ts"],
98
+ "intentPatterns": [
99
+ "(?:add|create).*(?:command|cli)",
100
+ "(?:cli).*(?:command|option|argument)"
101
+ ],
102
+ "contentPatterns": ["yargs", "command\\(", "option\\(", "argv"]
103
+ }
104
+ },
105
+ "storage-tiers": {
106
+ "description": "3-tier storage system (Redis/Railway/GCS)",
107
+ "priority": 7,
108
+ "triggers": {
109
+ "keywords": ["storage", "redis", "railway", "bucket", "tier", "cache"],
110
+ "keywordPatterns": ["\\bstorage\\b", "\\btier\\b", "\\bredis\\b"],
111
+ "pathPatterns": ["**/storage/**", "**/railway-*.ts", "**/redis-*.ts"],
112
+ "intentPatterns": [
113
+ "(?:implement|configure).*(?:storage|tier)",
114
+ "(?:migrate).*(?:data|storage)"
115
+ ],
116
+ "contentPatterns": ["RailwayOptimizedStorage", "StorageTier", "Redis"]
117
+ }
118
+ },
119
+ "context-bridge": {
120
+ "description": "Cross-session context synchronization",
121
+ "priority": 8,
122
+ "triggers": {
123
+ "keywords": ["bridge", "sync", "session", "shared context", "handoff"],
124
+ "keywordPatterns": ["context.*bridge", "shared.*context", "handoff"],
125
+ "pathPatterns": ["**/context-bridge*.ts", "**/shared-context/**"],
126
+ "intentPatterns": [
127
+ "(?:sync|share).*(?:context|session)",
128
+ "(?:handoff).*(?:session|context)"
129
+ ],
130
+ "contentPatterns": ["ContextBridge", "SharedContext", "syncContext"]
131
+ },
132
+ "relatedSkills": ["frame-management"]
133
+ },
134
+ "task-management": {
135
+ "description": "Task creation, tracking, and persistence",
136
+ "priority": 7,
137
+ "triggers": {
138
+ "keywords": ["task", "todo", "progress", "tracking", "pebbles"],
139
+ "keywordPatterns": ["\\btask\\b", "\\btodo\\b", "pebbles"],
140
+ "pathPatterns": ["**/tasks/**", "**/task-*.ts", "**/pebbles-*.ts"],
141
+ "intentPatterns": [
142
+ "(?:create|manage).*(?:task|todo)",
143
+ "(?:track).*(?:progress|task)"
144
+ ],
145
+ "contentPatterns": ["TaskManager", "createTask", "updateTask", "PebblesTaskStore"]
146
+ }
147
+ },
148
+ "terminal-ui": {
149
+ "description": "Terminal UI with Ink and React",
150
+ "priority": 6,
151
+ "triggers": {
152
+ "keywords": ["tui", "terminal", "ink", "dashboard", "monitor"],
153
+ "keywordPatterns": ["\\btui\\b", "\\bink\\b", "terminal.*ui"],
154
+ "pathPatterns": ["**/tui/**", "**/terminal-*.tsx", "**/dashboard/**"],
155
+ "intentPatterns": [
156
+ "(?:create|build).*(?:tui|terminal)",
157
+ "(?:dashboard|monitor).*(?:ui|interface)"
158
+ ],
159
+ "contentPatterns": ["ink", "Text", "Box", "useInput"]
160
+ }
161
+ },
162
+ "claude-integration": {
163
+ "description": "Claude Code integration and hooks",
164
+ "priority": 8,
165
+ "triggers": {
166
+ "keywords": ["claude", "hook", "skill", "auto-trigger", "clear survival"],
167
+ "keywordPatterns": ["claude.*(?:code|hook)", "skill.*eval"],
168
+ "pathPatterns": [".claude/**", "**/claude-*.ts", "**/hooks/**"],
169
+ "intentPatterns": [
170
+ "(?:integrate|setup).*(?:claude)",
171
+ "(?:hook|trigger).*(?:claude|skill)"
172
+ ],
173
+ "contentPatterns": ["UserPromptSubmit", "PreToolUse", "PostToolUse", "skill-eval"]
174
+ },
175
+ "relatedSkills": ["mcp-server"]
176
+ },
177
+ "build-scripts": {
178
+ "description": "Build, deployment, and utility scripts",
179
+ "priority": 5,
180
+ "triggers": {
181
+ "keywords": ["script", "build", "deploy", "npm", "package"],
182
+ "keywordPatterns": ["\\bscript\\b", "\\bbuild\\b", "npm.*run"],
183
+ "pathPatterns": ["scripts/**", "**/*.sh", "**/build/**"],
184
+ "intentPatterns": [
185
+ "(?:create|write).*(?:script)",
186
+ "(?:build|deploy).*(?:project)"
187
+ ],
188
+ "contentPatterns": ["#!/bin/bash", "npm run", "NODE_ENV"]
189
+ }
190
+ },
191
+ "documentation": {
192
+ "description": "Documentation and API reference",
193
+ "priority": 4,
194
+ "triggers": {
195
+ "keywords": ["docs", "documentation", "readme", "api", "reference"],
196
+ "keywordPatterns": ["\\bdoc(?:s)?\\b", "readme", "api.*reference"],
197
+ "pathPatterns": ["docs/**", "**/*.md", "**/README*"],
198
+ "intentPatterns": [
199
+ "(?:write|update).*(?:doc|documentation)",
200
+ "(?:document).*(?:api|feature)"
201
+ ]
202
+ }
203
+ },
204
+ "github-actions": {
205
+ "description": "CI/CD workflows and GitHub Actions",
206
+ "priority": 6,
207
+ "triggers": {
208
+ "keywords": ["workflow", "action", "ci", "cd", "pipeline"],
209
+ "keywordPatterns": ["github.*action", "ci.*cd", "\\bworkflow\\b"],
210
+ "pathPatterns": [".github/workflows/**", ".github/**/*.yml"],
211
+ "intentPatterns": [
212
+ "(?:create|setup).*(?:workflow|pipeline)",
213
+ "(?:ci|cd).*(?:setup|configure)"
214
+ ],
215
+ "contentPatterns": ["runs-on:", "uses:", "steps:"]
216
+ }
217
+ },
218
+ "code-quality": {
219
+ "description": "Code review and quality checks",
220
+ "priority": 8,
221
+ "triggers": {
222
+ "keywords": ["review", "lint", "quality", "eslint", "prettier"],
223
+ "keywordPatterns": ["code.*review", "\\blint\\b", "quality.*check"],
224
+ "intentPatterns": [
225
+ "(?:review|check).*(?:code|quality)",
226
+ "(?:fix).*(?:lint|formatting)"
227
+ ]
228
+ }
229
+ },
230
+ "performance-optimization": {
231
+ "description": "Performance analysis and optimization",
232
+ "priority": 7,
233
+ "triggers": {
234
+ "keywords": ["performance", "optimize", "slow", "bottleneck", "profile"],
235
+ "keywordPatterns": ["\\bperf\\b", "optimiz", "bottleneck"],
236
+ "intentPatterns": [
237
+ "(?:optimize|improve).*(?:performance|speed)",
238
+ "(?:debug|fix).*(?:slow|performance)"
239
+ ],
240
+ "contentPatterns": ["performance\\.now", "console\\.time", "profiler"]
241
+ }
242
+ },
243
+ "spec-generator": {
244
+ "description": "Iterative spec document generation (one-pager, dev-spec, prompt-plan, agents)",
245
+ "priority": 8,
246
+ "triggers": {
247
+ "keywords": ["spec", "one-pager", "dev-spec", "prompt-plan", "agents.md", "scaffold"],
248
+ "keywordPatterns": ["\\bspec\\b", "one.pager", "dev.spec", "prompt.plan"],
249
+ "pathPatterns": ["docs/specs/**", "**/AGENTS.md", "**/ONE_PAGER.md", "**/PROMPT_PLAN.md"],
250
+ "intentPatterns": [
251
+ "(?:generate|create).*(?:spec|one-pager|dev-spec)",
252
+ "(?:spec).*(?:generate|validate|update|list)"
253
+ ],
254
+ "contentPatterns": ["SpecGeneratorSkill", "ONE_PAGER", "DEV_SPEC", "PROMPT_PLAN"]
255
+ },
256
+ "relatedSkills": ["linear-task-runner"]
257
+ },
258
+ "linear-task-runner": {
259
+ "description": "Execute Linear tasks via RLM orchestrator",
260
+ "priority": 8,
261
+ "triggers": {
262
+ "keywords": ["linear-run", "run task", "execute task", "task runner", "ralph linear"],
263
+ "keywordPatterns": ["linear.run", "run.*task", "task.*runner"],
264
+ "pathPatterns": ["**/linear-task-runner.ts"],
265
+ "intentPatterns": [
266
+ "(?:run|execute).*(?:linear|task)",
267
+ "(?:linear).*(?:run|execute|next|all)"
268
+ ],
269
+ "contentPatterns": ["LinearTaskRunner", "runNext", "runAll", "runTask"]
270
+ },
271
+ "relatedSkills": ["spec-generator", "linear-integration"]
272
+ }
273
+ }
274
+ }