opencodekit 0.14.1 → 0.14.2
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/dist/index.js +1 -1
- package/dist/template/.opencode/.background-tasks.json +96 -0
- package/dist/template/.opencode/.ralph-state.json +12 -0
- package/dist/template/.opencode/AGENTS.md +76 -5
- package/dist/template/.opencode/agent/build.md +16 -7
- package/dist/template/.opencode/agent/looker.md +124 -0
- package/dist/template/.opencode/agent/rush.md +18 -6
- package/dist/template/.opencode/agent/scout.md +0 -1
- package/dist/template/.opencode/agent/vision.md +0 -1
- package/dist/template/.opencode/command/implement.md +51 -10
- package/dist/template/.opencode/command/new-feature.md +68 -10
- package/dist/template/.opencode/command/plan.md +59 -10
- package/dist/template/.opencode/command/ralph-loop.md +97 -0
- package/dist/template/.opencode/command/start.md +13 -10
- package/dist/template/.opencode/memory/{project/beads-workflow.md → beads-workflow.md} +53 -0
- package/dist/template/.opencode/memory/project/conventions.md +53 -3
- package/dist/template/.opencode/opencode.json +4 -0
- package/dist/template/.opencode/package.json +1 -0
- package/dist/template/.opencode/plugin/lsp.ts +299 -0
- package/dist/template/.opencode/plugin/ralph-wiggum.ts +182 -0
- package/dist/template/.opencode/tool/background.ts +461 -0
- package/dist/template/.opencode/tool/ralph.ts +203 -0
- package/package.json +1 -1
- /package/dist/template/.opencode/memory/{project/README.md → README.md} +0 -0
- /package/dist/template/.opencode/plugin/{notification.ts → notification.ts.bak} +0 -0
|
@@ -73,31 +73,35 @@ If memory search fails (Ollama not running), continue to subagent research.
|
|
|
73
73
|
|
|
74
74
|
## Phase 1: Parallel Subagent Research
|
|
75
75
|
|
|
76
|
-
Gather context before designing. Fire both in
|
|
76
|
+
Gather context before designing. Fire both in background:
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
79
|
// Codebase patterns
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
background_start({
|
|
81
|
+
agent: "explore",
|
|
82
82
|
prompt: `For planning $ARGUMENTS, research the codebase:
|
|
83
83
|
1. Find similar implementations or patterns
|
|
84
84
|
2. Identify affected files and their structure
|
|
85
85
|
3. Find related tests and testing patterns
|
|
86
86
|
4. Check for potential conflicts with in-progress work
|
|
87
87
|
Return: File paths, code patterns, test approach, conflicts`,
|
|
88
|
-
|
|
89
|
-
});
|
|
88
|
+
title: "explore-for-plan",
|
|
89
|
+
}); // → bg_123_abc
|
|
90
90
|
|
|
91
91
|
// External best practices
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
background_start({
|
|
93
|
+
agent: "scout",
|
|
94
94
|
prompt: `Research implementation approaches for $ARGUMENTS:
|
|
95
95
|
1. Best practices from official documentation
|
|
96
96
|
2. Common patterns in open source projects
|
|
97
97
|
3. Pitfalls and anti-patterns to avoid
|
|
98
98
|
Return: Recommendations, code examples, warnings`,
|
|
99
|
-
|
|
100
|
-
});
|
|
99
|
+
title: "scout-for-plan",
|
|
100
|
+
}); // → bg_456_def
|
|
101
|
+
|
|
102
|
+
// Collect when ready
|
|
103
|
+
background_output({ taskId: "bg_123_abc" });
|
|
104
|
+
background_output({ taskId: "bg_456_def" });
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
**Continue working while subagents research.**
|
|
@@ -413,7 +417,52 @@ If issues detected after deployment:
|
|
|
413
417
|
|
|
414
418
|
---
|
|
415
419
|
|
|
416
|
-
## Phase 7:
|
|
420
|
+
## Phase 7: Parallel Task Execution (if --parallel)
|
|
421
|
+
|
|
422
|
+
After creating hierarchy, execute READY tasks in parallel:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Check what's ready to start
|
|
426
|
+
bd ready --json
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// Fire all READY tasks in parallel with beads integration
|
|
431
|
+
for (const task of readyTasks) {
|
|
432
|
+
background_start({
|
|
433
|
+
agent: "build",
|
|
434
|
+
prompt: `Execute ${task.id}: ${task.title}
|
|
435
|
+
|
|
436
|
+
Context: Part of plan for $ARGUMENTS
|
|
437
|
+
Spec: See .beads/artifacts/$ARGUMENTS/plan.md
|
|
438
|
+
|
|
439
|
+
Requirements:
|
|
440
|
+
- Complete work items for this task
|
|
441
|
+
- Run verification commands from plan
|
|
442
|
+
- Commit with bead ID in message
|
|
443
|
+
|
|
444
|
+
Return: Changes made, verification results`,
|
|
445
|
+
beadId: task.id,
|
|
446
|
+
autoCloseBead: true,
|
|
447
|
+
title: `exec-${task.id}`
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Collect results
|
|
452
|
+
for (const taskId of backgroundTaskIds) {
|
|
453
|
+
background_output({ taskId }) // → beadClosed: true
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Check newly unblocked tasks
|
|
457
|
+
bd ready // → Next wave of tasks now READY
|
|
458
|
+
|
|
459
|
+
// Cleanup
|
|
460
|
+
background_cancel({ all: true })
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Phase 8: Sync and Report
|
|
417
466
|
|
|
418
467
|
```bash
|
|
419
468
|
bd sync
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Start Ralph Wiggum autonomous loop for task completion
|
|
3
|
+
argument-hint: "<task> [--prd <file>] [--max <iterations>] [--afk]"
|
|
4
|
+
agent: build
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ralph Wiggum Loop
|
|
8
|
+
|
|
9
|
+
You are starting a Ralph Wiggum autonomous loop. This pattern enables you to work autonomously on a task list until completion.
|
|
10
|
+
|
|
11
|
+
## Task
|
|
12
|
+
|
|
13
|
+
$ARGUMENTS
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
1. **Start the loop** by calling the `ralph-start` tool:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
ralph -
|
|
21
|
+
start({
|
|
22
|
+
task: "$1",
|
|
23
|
+
prdFile: "$2" || null, // Optional: PRD.md, tasks.md, etc.
|
|
24
|
+
progressFile: "progress.txt",
|
|
25
|
+
maxIterations: 50,
|
|
26
|
+
mode: "hitl", // or "afk" for autonomous
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **Create progress.txt** if it doesn't exist:
|
|
31
|
+
|
|
32
|
+
```markdown
|
|
33
|
+
# Progress Log
|
|
34
|
+
|
|
35
|
+
## Session Started: [date]
|
|
36
|
+
|
|
37
|
+
### Completed Tasks
|
|
38
|
+
|
|
39
|
+
(none yet)
|
|
40
|
+
|
|
41
|
+
### Notes for Next Iteration
|
|
42
|
+
|
|
43
|
+
- Starting fresh
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Loop Behavior
|
|
47
|
+
|
|
48
|
+
After each iteration, the loop will automatically:
|
|
49
|
+
|
|
50
|
+
1. Check if you output `<promise>COMPLETE</promise>`
|
|
51
|
+
2. If yes → Loop ends, success!
|
|
52
|
+
3. If no → Send continuation prompt for next iteration
|
|
53
|
+
4. Repeat until completion or max iterations
|
|
54
|
+
|
|
55
|
+
## Your Instructions
|
|
56
|
+
|
|
57
|
+
For each iteration:
|
|
58
|
+
|
|
59
|
+
1. **Review** the PRD/task list and progress file
|
|
60
|
+
2. **Choose** the highest-priority incomplete task (YOU decide, not first in list)
|
|
61
|
+
3. **Implement** ONE feature only (small steps prevent context rot)
|
|
62
|
+
4. **Validate** with feedback loops:
|
|
63
|
+
- `npm run typecheck` (must pass)
|
|
64
|
+
- `npm run test` (must pass)
|
|
65
|
+
- `npm run lint` (must pass)
|
|
66
|
+
5. **Commit** if all pass
|
|
67
|
+
6. **Update** progress.txt with:
|
|
68
|
+
- Task completed
|
|
69
|
+
- Key decisions made
|
|
70
|
+
- Files changed
|
|
71
|
+
- Notes for next iteration
|
|
72
|
+
|
|
73
|
+
## Exit Conditions
|
|
74
|
+
|
|
75
|
+
Output `<promise>COMPLETE</promise>` when:
|
|
76
|
+
|
|
77
|
+
- ALL tasks in the PRD are complete
|
|
78
|
+
- ALL feedback loops pass
|
|
79
|
+
- Code is committed
|
|
80
|
+
|
|
81
|
+
The loop will also stop if:
|
|
82
|
+
|
|
83
|
+
- Max iterations reached
|
|
84
|
+
- You call `ralph-stop` tool
|
|
85
|
+
- An error occurs
|
|
86
|
+
|
|
87
|
+
## Best Practices
|
|
88
|
+
|
|
89
|
+
- **Small steps**: One feature per iteration
|
|
90
|
+
- **Quality over speed**: Never skip tests
|
|
91
|
+
- **Explicit scope**: Vague tasks loop forever
|
|
92
|
+
- **Track progress**: Update progress.txt every iteration
|
|
93
|
+
- **Prioritize risk**: Hard tasks first, easy wins last
|
|
94
|
+
|
|
95
|
+
## Start Now
|
|
96
|
+
|
|
97
|
+
Call `ralph-start` with the task description to begin the loop.
|
|
@@ -169,33 +169,36 @@ cd .worktrees/$ARGUMENTS
|
|
|
169
169
|
For complex tasks, gather context before diving in:
|
|
170
170
|
|
|
171
171
|
```typescript
|
|
172
|
-
// Fire subagents in
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
// Fire subagents in background - don't wait for results
|
|
173
|
+
background_start({
|
|
174
|
+
agent: "explore",
|
|
175
175
|
prompt: `Research codebase patterns for $ARGUMENTS:
|
|
176
176
|
- Find similar implementations
|
|
177
177
|
- Identify affected files
|
|
178
178
|
- Note testing patterns used
|
|
179
179
|
Return: File list, patterns found, testing approach`,
|
|
180
|
-
|
|
181
|
-
});
|
|
180
|
+
title: "explore-codebase",
|
|
181
|
+
}); // → bg_123_abc
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
background_start({
|
|
184
|
+
agent: "scout",
|
|
185
185
|
prompt: `Research external docs for $ARGUMENTS:
|
|
186
186
|
- API documentation for libraries involved
|
|
187
187
|
- Best practices for the approach
|
|
188
188
|
- Common pitfalls to avoid
|
|
189
189
|
Return: Key findings, code examples, warnings`,
|
|
190
|
-
|
|
191
|
-
});
|
|
190
|
+
title: "scout-docs",
|
|
191
|
+
}); // → bg_456_def
|
|
192
|
+
|
|
193
|
+
// Collect later with: background_output({ taskId: "bg_123_abc" })
|
|
192
194
|
```
|
|
193
195
|
|
|
194
196
|
**Subagent delegation rules:**
|
|
195
197
|
|
|
196
198
|
- Subagents are **read-only** - they don't modify beads state
|
|
197
|
-
- Results
|
|
199
|
+
- Results collected via `background_output({ taskId })` when ready
|
|
198
200
|
- Use for research, not for implementation
|
|
201
|
+
- Cleanup at session end: `background_cancel({ all: true })`
|
|
199
202
|
|
|
200
203
|
## Existing Artifacts
|
|
201
204
|
|
|
@@ -426,6 +426,59 @@ git worktree remove .worktrees/bd-epic # Cleanup worktree
|
|
|
426
426
|
/finish bd-epic
|
|
427
427
|
```
|
|
428
428
|
|
|
429
|
+
### Pattern 8: Ralph Wiggum Autonomous Loop
|
|
430
|
+
|
|
431
|
+
For tasks that can run autonomously until completion. The agent loops until it outputs `<promise>COMPLETE</promise>` or hits max iterations.
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
/ralph-loop "Migrate all Jest tests to Vitest"
|
|
435
|
+
# Or with PRD file:
|
|
436
|
+
/ralph-loop "Complete PRD tasks" --prd PRD.md --max 50
|
|
437
|
+
|
|
438
|
+
# Agent automatically:
|
|
439
|
+
# 1. Picks highest-priority incomplete task
|
|
440
|
+
# 2. Implements ONE feature
|
|
441
|
+
# 3. Runs feedback loops (typecheck, test, lint)
|
|
442
|
+
# 4. Commits if all pass
|
|
443
|
+
# 5. Updates progress.txt
|
|
444
|
+
# 6. Loops until <promise>COMPLETE</promise> or max iterations
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**When to use Ralph:**
|
|
448
|
+
|
|
449
|
+
| Scenario | Use Ralph? | Why |
|
|
450
|
+
| ------------------------- | ---------- | ------------------------------------ |
|
|
451
|
+
| Test coverage improvement | ✅ Yes | Clear success criteria, safe to loop |
|
|
452
|
+
| Linting fixes | ✅ Yes | Deterministic, feedback-driven |
|
|
453
|
+
| Migration (Jest→Vitest) | ✅ Yes | Repetitive, well-defined end state |
|
|
454
|
+
| Feature implementation | ⚠️ HITL | Need oversight for design decisions |
|
|
455
|
+
| Architectural changes | ❌ No | Too risky for autonomous work |
|
|
456
|
+
| Vague "improve X" tasks | ❌ No | No clear completion criteria |
|
|
457
|
+
|
|
458
|
+
**Ralph + Beads Integration:**
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
# Create PRD from beads
|
|
462
|
+
bd list --status=open --json > PRD.json
|
|
463
|
+
|
|
464
|
+
# Start Ralph loop
|
|
465
|
+
/ralph-loop "Complete all open tasks" --prd PRD.json
|
|
466
|
+
|
|
467
|
+
# Ralph will work through tasks, you can:
|
|
468
|
+
ralph-status # Check progress
|
|
469
|
+
ralph-stop # Stop gracefully
|
|
470
|
+
|
|
471
|
+
# After completion, sync beads
|
|
472
|
+
bd sync
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Key constraints:**
|
|
476
|
+
|
|
477
|
+
- ONE feature per iteration (prevents context rot)
|
|
478
|
+
- MUST pass all feedback loops before commit
|
|
479
|
+
- Small steps > large changes
|
|
480
|
+
- Always cap iterations (never infinite loops)
|
|
481
|
+
|
|
429
482
|
## Agent Boundaries
|
|
430
483
|
|
|
431
484
|
### Leader Agents (Touch Beads)
|
|
@@ -111,10 +111,60 @@ This transforms passive detection into active agent behavior (e.g., Memory plugi
|
|
|
111
111
|
|
|
112
112
|
## Agent Behavior Rules
|
|
113
113
|
|
|
114
|
-
###
|
|
114
|
+
### MANDATORY LSP-First Workflow
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
**HARD RULE**: Before editing ANY code file, you MUST use LSP tools first.
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
grep/read → LSP → understand → THEN edit
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**After EVERY grep/glob/read that returns code files:**
|
|
123
|
+
|
|
124
|
+
1. **IMMEDIATELY** call `lsp` with `documentSymbol` to understand file structure
|
|
125
|
+
2. **IMMEDIATELY** call `lsp` with `findReferences` on symbols you'll modify
|
|
126
|
+
3. **IMMEDIATELY** call `lsp` with `goToDefinition` to trace dependencies
|
|
127
|
+
4. **USE** additional LSP operations as needed:
|
|
128
|
+
- `hover` - Get type info and documentation
|
|
129
|
+
- `goToImplementation` - Find implementations of interface/abstract
|
|
130
|
+
- `workspaceSymbol` - Search symbols across entire workspace
|
|
131
|
+
- `prepareCallHierarchy` - Get call hierarchy item at position
|
|
132
|
+
- `incomingCalls` - Find what calls this function
|
|
133
|
+
- `outgoingCalls` - Find what this function calls
|
|
134
|
+
5. **ONLY THEN** proceed with edits
|
|
135
|
+
|
|
136
|
+
**Example - The Correct Flow:**
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
User: "Fix the auth bug in src/auth.ts"
|
|
140
|
+
|
|
141
|
+
# Step 1: Read the file
|
|
142
|
+
read({ filePath: "src/auth.ts" })
|
|
143
|
+
|
|
144
|
+
# Step 2: MANDATORY LSP (before ANY edit) - use ALL relevant operations
|
|
145
|
+
lsp({ operation: "documentSymbol", filePath: "src/auth.ts", line: 1, character: 1 })
|
|
146
|
+
lsp({ operation: "findReferences", filePath: "src/auth.ts", line: 42, character: 10 })
|
|
147
|
+
lsp({ operation: "goToDefinition", filePath: "src/auth.ts", line: 42, character: 10 })
|
|
148
|
+
lsp({ operation: "hover", filePath: "src/auth.ts", line: 42, character: 10 })
|
|
149
|
+
lsp({ operation: "incomingCalls", filePath: "src/auth.ts", line: 42, character: 10 })
|
|
150
|
+
lsp({ operation: "outgoingCalls", filePath: "src/auth.ts", line: 42, character: 10 })
|
|
151
|
+
|
|
152
|
+
# Step 3: NOW you can edit
|
|
153
|
+
edit({ filePath: "src/auth.ts", ... })
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Why This Matters:**
|
|
157
|
+
|
|
158
|
+
- LSP gives you semantic understanding (types, references, call hierarchy)
|
|
159
|
+
- grep/read only gives you text (no understanding of relationships)
|
|
160
|
+
- Editing without LSP context leads to broken refactors
|
|
161
|
+
|
|
162
|
+
**Violations:**
|
|
163
|
+
|
|
164
|
+
- ❌ `read → edit` (WRONG - no LSP)
|
|
165
|
+
- ❌ `grep → edit` (WRONG - no LSP)
|
|
166
|
+
- ✅ `read → LSP → edit` (CORRECT)
|
|
167
|
+
- ✅ `grep → read → LSP → edit` (CORRECT)
|
|
118
168
|
|
|
119
169
|
## Patterns to Avoid
|
|
120
170
|
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"description": "Fast codebase search specialist",
|
|
14
14
|
"model": "opencode/grok-code"
|
|
15
15
|
},
|
|
16
|
+
"looker": {
|
|
17
|
+
"description": "Media extraction specialist for images, PDFs, diagrams",
|
|
18
|
+
"model": "proxypal/gemini-3-flash-preview"
|
|
19
|
+
},
|
|
16
20
|
"general": {
|
|
17
21
|
"disable": true
|
|
18
22
|
},
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Plugin - Active LSP Tool Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Forces agents to actively use LSP tools when code files are detected.
|
|
5
|
+
* This is NOT a suggestion - agents MUST execute LSP operations immediately.
|
|
6
|
+
*
|
|
7
|
+
* Mechanism:
|
|
8
|
+
* 1. Hooks into grep/glob/read tool outputs (tool.execute.after)
|
|
9
|
+
* 2. Hooks into user messages mentioning code files (chat.message)
|
|
10
|
+
* 3. Injects MANDATORY LSP execution commands
|
|
11
|
+
* 4. Uses strong language to override agent tendencies to skip
|
|
12
|
+
*
|
|
13
|
+
* Based on oh-my-opencode's LSP forcing pattern.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
17
|
+
|
|
18
|
+
// File extensions that support LSP
|
|
19
|
+
const LSP_SUPPORTED_EXTENSIONS = new Set([
|
|
20
|
+
".ts",
|
|
21
|
+
".tsx",
|
|
22
|
+
".js",
|
|
23
|
+
".jsx",
|
|
24
|
+
".py",
|
|
25
|
+
".go",
|
|
26
|
+
".rs",
|
|
27
|
+
".java",
|
|
28
|
+
".c",
|
|
29
|
+
".cpp",
|
|
30
|
+
".h",
|
|
31
|
+
".hpp",
|
|
32
|
+
".cs",
|
|
33
|
+
".rb",
|
|
34
|
+
".php",
|
|
35
|
+
".swift",
|
|
36
|
+
".kt",
|
|
37
|
+
".scala",
|
|
38
|
+
".lua",
|
|
39
|
+
".zig",
|
|
40
|
+
".vue",
|
|
41
|
+
".svelte",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// Regex to extract file:line patterns from tool output
|
|
45
|
+
const FILE_LINE_PATTERNS = [
|
|
46
|
+
// Standard grep output: path/file.ts:42: content
|
|
47
|
+
/^([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte)):(\d+):/gm,
|
|
48
|
+
// Just path with line: path/file.ts:42
|
|
49
|
+
/([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte)):(\d+)/g,
|
|
50
|
+
// Glob/list output with just paths
|
|
51
|
+
/^([^\s:]+\.(ts|tsx|js|jsx|py|go|rs|java|c|cpp|h|hpp|cs|rb|php|swift|kt|scala|lua|zig|vue|svelte))$/gm,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// Patterns that indicate user is asking about code
|
|
55
|
+
const CODE_INTENT_PATTERNS = [
|
|
56
|
+
/\b(edit|modify|change|update|fix|refactor|add|remove|delete)\b.*\.(ts|tsx|js|jsx|py|go|rs)/i,
|
|
57
|
+
/\b(function|class|method|variable|type|interface)\s+\w+/i,
|
|
58
|
+
/\b(implement|create|build)\b.*\b(feature|component|module)/i,
|
|
59
|
+
/@[^\s]+\.(ts|tsx|js|jsx|py|go|rs)/i, // @file.ts mentions
|
|
60
|
+
/\b(src|lib|app|components?)\/[^\s]+\.(ts|tsx|js|jsx)/i, // Path patterns
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
interface FileMatch {
|
|
64
|
+
filePath: string;
|
|
65
|
+
line?: number;
|
|
66
|
+
character?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function extractFileMatches(output: string): FileMatch[] {
|
|
70
|
+
const matches: FileMatch[] = [];
|
|
71
|
+
const seen = new Set<string>();
|
|
72
|
+
|
|
73
|
+
for (const pattern of FILE_LINE_PATTERNS) {
|
|
74
|
+
pattern.lastIndex = 0;
|
|
75
|
+
|
|
76
|
+
let match = pattern.exec(output);
|
|
77
|
+
while (match !== null) {
|
|
78
|
+
const filePath = match[1];
|
|
79
|
+
const line = match[3] ? Number.parseInt(match[3], 10) : undefined;
|
|
80
|
+
|
|
81
|
+
const key = `${filePath}:${line || 0}`;
|
|
82
|
+
if (!seen.has(key)) {
|
|
83
|
+
seen.add(key);
|
|
84
|
+
matches.push({
|
|
85
|
+
filePath,
|
|
86
|
+
line,
|
|
87
|
+
character: 1,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
match = pattern.exec(output);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return matches.slice(0, 5);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function extractFilesFromUserMessage(text: string): string[] {
|
|
99
|
+
const files: string[] = [];
|
|
100
|
+
const seen = new Set<string>();
|
|
101
|
+
|
|
102
|
+
// Match @file.ts patterns
|
|
103
|
+
const atMentions = text.match(/@([^\s]+\.(ts|tsx|js|jsx|py|go|rs))/gi) || [];
|
|
104
|
+
for (const mention of atMentions) {
|
|
105
|
+
const file = mention.replace("@", "");
|
|
106
|
+
if (!seen.has(file)) {
|
|
107
|
+
seen.add(file);
|
|
108
|
+
files.push(file);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Match path patterns like src/foo.ts
|
|
113
|
+
const pathPatterns =
|
|
114
|
+
text.match(
|
|
115
|
+
/\b(src|lib|app|components?)\/[^\s]+\.(ts|tsx|js|jsx|py|go|rs)/gi,
|
|
116
|
+
) || [];
|
|
117
|
+
for (const path of pathPatterns) {
|
|
118
|
+
if (!seen.has(path)) {
|
|
119
|
+
seen.add(path);
|
|
120
|
+
files.push(path);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return files.slice(0, 3);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateLspCommands(matches: FileMatch[]): string {
|
|
128
|
+
if (matches.length === 0) return "";
|
|
129
|
+
|
|
130
|
+
const commands: string[] = [];
|
|
131
|
+
|
|
132
|
+
for (const match of matches) {
|
|
133
|
+
if (match.line) {
|
|
134
|
+
// With line number - get definition and references
|
|
135
|
+
commands.push(
|
|
136
|
+
`lsp({ operation: "goToDefinition", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
137
|
+
);
|
|
138
|
+
commands.push(
|
|
139
|
+
`lsp({ operation: "findReferences", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
140
|
+
);
|
|
141
|
+
commands.push(
|
|
142
|
+
`lsp({ operation: "hover", filePath: "${match.filePath}", line: ${match.line}, character: ${match.character || 1} })`,
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
// Just file path - get document symbols
|
|
146
|
+
commands.push(
|
|
147
|
+
`lsp({ operation: "documentSymbol", filePath: "${match.filePath}", line: 1, character: 1 })`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const uniqueCommands = [...new Set(commands)].slice(0, 6);
|
|
153
|
+
|
|
154
|
+
// AGGRESSIVE enforcement format - designed to force action
|
|
155
|
+
return `
|
|
156
|
+
|
|
157
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
158
|
+
║ ⚡ LSP EXECUTION REQUIRED - DO NOT SKIP ║
|
|
159
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
160
|
+
║ ║
|
|
161
|
+
║ Code files detected. You MUST execute these LSP operations NOW. ║
|
|
162
|
+
║ DO NOT proceed with edits until LSP context is retrieved. ║
|
|
163
|
+
║ ║
|
|
164
|
+
║ EXECUTE IMMEDIATELY (copy-paste ready): ║
|
|
165
|
+
║ ║
|
|
166
|
+
${uniqueCommands.map((cmd) => `║ → ${cmd}`).join("\n")}
|
|
167
|
+
║ ║
|
|
168
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
169
|
+
║ WHY THIS IS MANDATORY: ║
|
|
170
|
+
║ • grep/read show text, LSP shows semantic meaning ║
|
|
171
|
+
║ • LSP provides accurate type info, definitions, call hierarchies ║
|
|
172
|
+
║ • Editing without LSP context leads to broken code ║
|
|
173
|
+
║ • This is a HARD RULE, not a suggestion ║
|
|
174
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
175
|
+
║ WORKFLOW: grep/read → LSP → understand → THEN edit ║
|
|
176
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function generateUserMessageNudge(files: string[]): string {
|
|
181
|
+
const lspCommands = files.map(
|
|
182
|
+
(f) =>
|
|
183
|
+
`lsp({ operation: "documentSymbol", filePath: "${f}", line: 1, character: 1 })`,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return `
|
|
187
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
188
|
+
⚡ [LSP CONTEXT REQUIRED]
|
|
189
|
+
|
|
190
|
+
Before making ANY changes to these files, you MUST first retrieve LSP context:
|
|
191
|
+
|
|
192
|
+
${lspCommands.map((cmd) => `→ ${cmd}`).join("\n")}
|
|
193
|
+
|
|
194
|
+
This is a HARD RULE. Do NOT guess about code structure.
|
|
195
|
+
Workflow: READ file → LSP documentSymbol/hover → UNDERSTAND → THEN edit
|
|
196
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function shouldInjectNudge(toolName: string): boolean {
|
|
201
|
+
return ["grep", "glob", "read", "batch"].includes(toolName);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hasCodeFiles(output: string): boolean {
|
|
205
|
+
for (const ext of LSP_SUPPORTED_EXTENSIONS) {
|
|
206
|
+
if (output.includes(ext)) return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function userMessageMentionsCode(text: string): boolean {
|
|
212
|
+
return CODE_INTENT_PATTERNS.some((pattern) => pattern.test(text));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const Lsp: Plugin = async () => {
|
|
216
|
+
return {
|
|
217
|
+
name: "lsp",
|
|
218
|
+
version: "1.1.0",
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Hook: chat.message
|
|
222
|
+
* Injects LSP reminder when user mentions code files in their message
|
|
223
|
+
* This catches intent BEFORE tools are executed
|
|
224
|
+
*/
|
|
225
|
+
"chat.message": async (_input, output) => {
|
|
226
|
+
const { message, parts } = output;
|
|
227
|
+
|
|
228
|
+
// Only process user messages
|
|
229
|
+
if (message.role !== "user") return;
|
|
230
|
+
|
|
231
|
+
// Extract text from all parts
|
|
232
|
+
const fullText = parts
|
|
233
|
+
.filter((p) => p.type === "text")
|
|
234
|
+
.map((p) => ("text" in p ? p.text : ""))
|
|
235
|
+
.join(" ");
|
|
236
|
+
|
|
237
|
+
// Check if user is mentioning code files or asking about code
|
|
238
|
+
if (!userMessageMentionsCode(fullText)) return;
|
|
239
|
+
|
|
240
|
+
// Extract specific files mentioned
|
|
241
|
+
const files = extractFilesFromUserMessage(fullText);
|
|
242
|
+
|
|
243
|
+
// If files found, inject specific LSP commands
|
|
244
|
+
// If no specific files but code intent detected, inject general reminder
|
|
245
|
+
const nudgeText =
|
|
246
|
+
files.length > 0
|
|
247
|
+
? generateUserMessageNudge(files)
|
|
248
|
+
: `
|
|
249
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
250
|
+
⚡ [LSP FIRST - HARD RULE]
|
|
251
|
+
|
|
252
|
+
Code modification detected. Before editing:
|
|
253
|
+
1. Use grep/glob to find relevant files
|
|
254
|
+
2. Use READ to view the file
|
|
255
|
+
3. Use LSP (documentSymbol, goToDefinition, findReferences) to understand structure
|
|
256
|
+
4. ONLY THEN make edits
|
|
257
|
+
|
|
258
|
+
Do NOT skip LSP. Editing without semantic context leads to broken code.
|
|
259
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
260
|
+
`;
|
|
261
|
+
|
|
262
|
+
// Inject synthetic message part - cast to any for synthetic property
|
|
263
|
+
(parts as unknown[]).push({
|
|
264
|
+
type: "text",
|
|
265
|
+
text: nudgeText,
|
|
266
|
+
synthetic: true,
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Hook: tool.execute.after
|
|
272
|
+
* Injects LSP commands after grep/glob/read returns code file results
|
|
273
|
+
* This catches AFTER tools show code files
|
|
274
|
+
*/
|
|
275
|
+
"tool.execute.after": async (input, output) => {
|
|
276
|
+
const { tool: toolName } = input;
|
|
277
|
+
const result = output.output;
|
|
278
|
+
|
|
279
|
+
// Skip if not a search/read tool
|
|
280
|
+
if (!shouldInjectNudge(toolName)) return;
|
|
281
|
+
|
|
282
|
+
// Skip if no code files in output
|
|
283
|
+
if (typeof result !== "string" || !hasCodeFiles(result)) return;
|
|
284
|
+
|
|
285
|
+
// Extract file matches
|
|
286
|
+
const matches = extractFileMatches(result);
|
|
287
|
+
if (matches.length === 0) return;
|
|
288
|
+
|
|
289
|
+
// Generate LSP commands (not suggestions - COMMANDS)
|
|
290
|
+
const lspBlock = generateLspCommands(matches);
|
|
291
|
+
if (!lspBlock) return;
|
|
292
|
+
|
|
293
|
+
// Append mandatory LSP block to tool output
|
|
294
|
+
output.output = `${result}${lspBlock}`;
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export default Lsp;
|