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.
@@ -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 parallel:
76
+ Gather context before designing. Fire both in background:
77
77
 
78
78
  ```typescript
79
79
  // Codebase patterns
80
- Task({
81
- subagent_type: "explore",
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
- description: "Explore codebase for planning",
89
- });
88
+ title: "explore-for-plan",
89
+ }); // → bg_123_abc
90
90
 
91
91
  // External best practices
92
- Task({
93
- subagent_type: "scout",
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
- description: "Scout best practices for planning",
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: Sync and Report
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 parallel - don't wait for results
173
- Task({
174
- subagent_type: "explore",
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
- description: "Explore codebase for task",
181
- });
180
+ title: "explore-codebase",
181
+ }); // → bg_123_abc
182
182
 
183
- Task({
184
- subagent_type: "scout",
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
- description: "Scout external resources",
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 return to you (build agent) for integration
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
- ### Active LSP Nudge
114
+ ### MANDATORY LSP-First Workflow
115
115
 
116
- When a tool output displays `[LSP NAVIGATION AVAILABLE]`, it means relevant code files were found.
117
- **Rule**: You MUST immediately execute the suggested `lsp_lsp_*` tool calls to retrieve code context without waiting for user instruction. This ensures you always have the latest code context for your implementation.
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
  },
@@ -13,6 +13,7 @@
13
13
  "dependencies": {
14
14
  "@lancedb/lancedb": "^0.23.0",
15
15
  "@opencode-ai/plugin": "1.1.8",
16
+ "@opencode-ai/sdk": "^1.1.8",
16
17
  "openai": "^6.15.0"
17
18
  },
18
19
  "devDependencies": {
@@ -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;