@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 +136 -42
- package/package.json +10 -5
- package/scripts/install-claude-hooks-auto.js +23 -9
- package/templates/claude-hooks/hooks.json +4 -2
- package/templates/claude-hooks/on-task-complete.js +91 -0
- package/templates/claude-hooks/skill-eval.cjs +411 -0
- package/templates/claude-hooks/skill-eval.sh +31 -0
- package/templates/claude-hooks/skill-rules.json +274 -0
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
|
-
- **
|
|
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
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
|
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
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
#
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, and
|
|
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
|
-
"
|
|
48
|
-
"
|
|
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 = [
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
}
|