opencodekit 0.17.3 → 0.17.5
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/.version +1 -0
- package/dist/template/.opencode/AGENTS.md +42 -0
- package/dist/template/.opencode/agent/explore.md +8 -0
- package/dist/template/.opencode/agent/scout.md +8 -0
- package/dist/template/.opencode/command/create.md +23 -1
- package/dist/template/.opencode/command/plan.md +8 -0
- package/dist/template/.opencode/command/research.md +26 -0
- package/dist/template/.opencode/command/review-codebase.md +43 -1
- package/dist/template/.opencode/command/ship.md +26 -33
- package/dist/template/.opencode/command/start.md +15 -0
- package/dist/template/.opencode/command/status.md +23 -1
- package/dist/template/.opencode/command/verify.md +21 -0
- package/dist/template/.opencode/dcp.jsonc +81 -80
- package/dist/template/.opencode/memory/project/gotchas.md +37 -1
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +1054 -1054
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/sessions.ts +60 -0
- package/dist/template/.opencode/plugin/skill-mcp.ts +48 -0
- package/dist/template/.opencode/skill/context-management/SKILL.md +44 -10
- package/dist/template/.opencode/skill/obsidian/SKILL.md +182 -0
- package/dist/template/.opencode/skill/obsidian/mcp.json +22 -0
- package/dist/template/.opencode/skill/structured-edit/SKILL.md +168 -0
- package/dist/template/.opencode/tool/action-queue.ts +9 -4
- package/dist/template/.opencode/tool/memory-search.ts +12 -7
- package/dist/template/.opencode/tool/observation.ts +65 -4
- package/dist/template/.opencode/tool/swarm.ts +14 -6
- package/package.json +1 -1
|
@@ -7,7 +7,67 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
7
7
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
8
8
|
|
|
9
9
|
export const SessionsPlugin: Plugin = async ({ client }) => {
|
|
10
|
+
const getProjectContext = () =>
|
|
11
|
+
"Project context: OpenCodeKit CLI repo; sessions often mention .opencode/ plugins, commands, memory rules, beads IDs, and Bun/TypeScript constraints.";
|
|
12
|
+
|
|
13
|
+
const getBestPractices = () =>
|
|
14
|
+
"Best practices: Prefer recent sessions, keep queries focused, and reference exact file paths or IDs found in the session.";
|
|
15
|
+
|
|
10
16
|
return {
|
|
17
|
+
"tool.definition": async (input, output) => {
|
|
18
|
+
const toolID = input.toolID;
|
|
19
|
+
const projectContext = getProjectContext();
|
|
20
|
+
const commonBestPractices = getBestPractices();
|
|
21
|
+
|
|
22
|
+
switch (toolID) {
|
|
23
|
+
case "list_sessions": {
|
|
24
|
+
output.description = `${output.description}
|
|
25
|
+
|
|
26
|
+
${projectContext}
|
|
27
|
+
${commonBestPractices}
|
|
28
|
+
|
|
29
|
+
Recent session examples:
|
|
30
|
+
list_sessions({ since: "this week", limit: 5 })
|
|
31
|
+
list_sessions({ since: "yesterday", limit: 3 })`;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case "read_session": {
|
|
35
|
+
output.description = `${output.description}
|
|
36
|
+
|
|
37
|
+
${projectContext}
|
|
38
|
+
Best practices: Use "last" for most recent context; add a short focus keyword (e.g., "sessions", "plugin", "beads") to reduce noise.
|
|
39
|
+
|
|
40
|
+
Recent session examples:
|
|
41
|
+
read_session({ session_reference: "last" })
|
|
42
|
+
read_session({ session_reference: "last", focus: "sessions plugin" })`;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "search_session": {
|
|
46
|
+
output.description = `${output.description}
|
|
47
|
+
|
|
48
|
+
${projectContext}
|
|
49
|
+
Best practices: Start with 1-2 specific keywords, then read the top match for full context.
|
|
50
|
+
|
|
51
|
+
Recent session examples:
|
|
52
|
+
search_session({ query: "sessions plugin", limit: 5 })
|
|
53
|
+
search_session({ query: ".opencode", limit: 5 })`;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "summarize_session": {
|
|
57
|
+
output.description = `${output.description}
|
|
58
|
+
|
|
59
|
+
${projectContext}
|
|
60
|
+
Best practices: Summarize long sessions only after confirming the ID from list_sessions; check back later for the generated summary.
|
|
61
|
+
|
|
62
|
+
Recent session examples:
|
|
63
|
+
summarize_session({ session_id: "abc123" }) // from list_sessions
|
|
64
|
+
summarize_session({ session_id: "def456" })`;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
11
71
|
tool: {
|
|
12
72
|
list_sessions: tool({
|
|
13
73
|
description: `List OpenCode sessions with metadata.
|
|
@@ -338,7 +338,53 @@ export const SkillMcpPlugin: Plugin = async ({ directory }) => {
|
|
|
338
338
|
state.clients.clear();
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
function buildLoadedMcpDetails(): {
|
|
342
|
+
summary: string;
|
|
343
|
+
examples: string;
|
|
344
|
+
} {
|
|
345
|
+
const loadedEntries = Array.from(state.loadedSkills.entries());
|
|
346
|
+
if (loadedEntries.length === 0) {
|
|
347
|
+
return {
|
|
348
|
+
summary:
|
|
349
|
+
"Loaded MCP skills: (none). Load a skill with MCP config via skill() before using this tool.",
|
|
350
|
+
examples:
|
|
351
|
+
'Examples:\n- skill("playwright")\n- skill_mcp(skill_name="playwright", list_tools=true)',
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const summaryLines = ["Loaded MCP skills and servers:"];
|
|
356
|
+
const examples: string[] = [];
|
|
357
|
+
for (const [skillName, config] of loadedEntries) {
|
|
358
|
+
const serverNames = Object.keys(config);
|
|
359
|
+
summaryLines.push(`- ${skillName}: ${serverNames.join(", ")}`);
|
|
360
|
+
|
|
361
|
+
const serverHint =
|
|
362
|
+
serverNames.length > 1 ? `, mcp_name="${serverNames[0]}"` : "";
|
|
363
|
+
examples.push(
|
|
364
|
+
`- skill_mcp(skill_name="${skillName}", list_tools=true${serverHint})`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
summary: summaryLines.join("\n"),
|
|
370
|
+
examples: `Examples:\n${examples.slice(0, 3).join("\n")}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
341
374
|
return {
|
|
375
|
+
"tool.definition": async (input, output) => {
|
|
376
|
+
const toolID = input.toolID;
|
|
377
|
+
if (toolID === "skill_mcp") {
|
|
378
|
+
const details = buildLoadedMcpDetails();
|
|
379
|
+
output.description = `${output.description}\n\n${details.summary}\n\n${details.examples}`;
|
|
380
|
+
}
|
|
381
|
+
if (toolID === "skill_mcp_status") {
|
|
382
|
+
output.description = `${output.description}
|
|
383
|
+
|
|
384
|
+
Connection status: Shows currently active MCP server connections from skill-embedded MCP servers.
|
|
385
|
+
Example: skill_mcp_status({})`;
|
|
386
|
+
}
|
|
387
|
+
},
|
|
342
388
|
tool: {
|
|
343
389
|
skill_mcp: tool({
|
|
344
390
|
description: `Invoke MCP tools from skill-embedded MCP servers.
|
|
@@ -399,6 +445,8 @@ The skill must be loaded first via the skill() tool to register its MCP config.`
|
|
|
399
445
|
});
|
|
400
446
|
}
|
|
401
447
|
|
|
448
|
+
state.loadedSkills.set(skill_name, mcpConfig);
|
|
449
|
+
|
|
402
450
|
// Determine which MCP server to use
|
|
403
451
|
const serverNames = Object.keys(mcpConfig);
|
|
404
452
|
const targetServer = mcp_name || serverNames[0];
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: context-management
|
|
3
|
-
description: Use when context is growing large, needing to prune/distill tool outputs, or managing conversation size - covers
|
|
3
|
+
description: Use when context is growing large, needing to prune/distill tool outputs, or managing conversation size - covers DCP slash commands and context budgets
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Context Management
|
|
7
7
|
|
|
8
|
-
Manage conversation context to prevent degradation. Uses
|
|
8
|
+
Manage conversation context to prevent degradation. Uses DCP plugin.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## DCP Integration
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
| --------- | ----------------------------- | ---------------------------------------- |
|
|
14
|
-
| `distill` | Extract key info, then remove | Large outputs with valuable details |
|
|
15
|
-
| `prune` | Remove tool outputs (no save) | Noise, irrelevant reads, superseded info |
|
|
12
|
+
This project uses the **DCP (Dynamic Context Pruning)** plugin. Prefer slash commands over tool calls for better reliability.
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
### Slash Commands (Recommended)
|
|
15
|
+
|
|
16
|
+
| Command | Purpose | When to Use |
|
|
17
|
+
| ----------------------- | ---------------------------------------- | ----------------------------------- |
|
|
18
|
+
| `/dcp compress [focus]` | Collapse conversation range into summary | Phase complete, research done |
|
|
19
|
+
| `/dcp sweep [count]` | Prune all tools since last user message | Cleanup noise, quick prune |
|
|
20
|
+
| `/dcp distill [focus]` | Distill key findings before removing | Large outputs with valuable details |
|
|
21
|
+
| `/dcp context` | Show token breakdown by category | Check context usage |
|
|
22
|
+
| `/dcp stats` | Show cumulative pruning stats | Review efficiency |
|
|
23
|
+
|
|
24
|
+
### Tool Calls (Fallback)
|
|
25
|
+
|
|
26
|
+
Use tool calls when slash commands aren't suitable:
|
|
27
|
+
|
|
28
|
+
| Tool | Purpose | When to Use |
|
|
29
|
+
| ---------- | ----------------------------- | ---------------------------------------- |
|
|
30
|
+
| `distill` | Extract key info, then remove | Large outputs with valuable details |
|
|
31
|
+
| `prune` | Remove tool outputs (no save) | Noise, irrelevant reads, superseded info |
|
|
32
|
+
| `compress` | Collapse conversation range | When slash command fails |
|
|
33
|
+
|
|
34
|
+
**Note:** Compress tool has boundary matching issues. Prefer `/dcp compress` slash command.
|
|
35
|
+
|
|
36
|
+
## DCP Auto-Strategies
|
|
37
|
+
|
|
38
|
+
DCP runs these automatically (zero LLM cost):
|
|
39
|
+
|
|
40
|
+
- **Deduplication** — removes duplicate tool calls (same tool + same args)
|
|
41
|
+
- **Supersede Writes** — removes write inputs when file is later read
|
|
42
|
+
- **Purge Errors** — removes errored tool inputs after 4 turns
|
|
43
|
+
|
|
44
|
+
You don't need to manually prune these.
|
|
18
45
|
|
|
19
46
|
## When to Evaluate
|
|
20
47
|
|
|
@@ -94,8 +121,15 @@ Auto-protected from pruning:
|
|
|
94
121
|
## Quick Reference
|
|
95
122
|
|
|
96
123
|
```
|
|
97
|
-
|
|
98
|
-
|
|
124
|
+
DCP SLASH COMMANDS (preferred):
|
|
125
|
+
/dcp compress [focus] → Collapse range into summary
|
|
126
|
+
/dcp distill [focus] → Distill key findings
|
|
127
|
+
/dcp sweep [count] → Prune all since last user
|
|
128
|
+
/dcp context → Show token breakdown
|
|
129
|
+
|
|
130
|
+
TOOL CALLS (fallback):
|
|
131
|
+
distill({ targets: [{ id, distillation }] })
|
|
132
|
+
prune({ ids: [...] })
|
|
99
133
|
|
|
100
134
|
BUDGET: <50k start → 50-100k mid → >100k distill → >150k restart
|
|
101
135
|
TIMING: Manage at turn START, not turn END
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: obsidian
|
|
3
|
+
description: Use when working with Obsidian vault via MCP - read/write notes, search, tag management, and vault operations
|
|
4
|
+
mcp:
|
|
5
|
+
server: @mauricio.wolff/mcp-obsidian
|
|
6
|
+
args: ["{env:OBSIDIAN_VAULT_PATH}"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Obsidian Vault (MCP)
|
|
10
|
+
|
|
11
|
+
MCP server for safe Obsidian vault access. Read/write notes, search, manage tags.
|
|
12
|
+
|
|
13
|
+
## Available Tools
|
|
14
|
+
|
|
15
|
+
### Read Operations
|
|
16
|
+
|
|
17
|
+
| Tool | Purpose | Arguments |
|
|
18
|
+
| --------------------- | --------------------------------- | ------------------------------------------------- |
|
|
19
|
+
| `read_note` | Read note with parsed frontmatter | `path`, `prettyPrint` |
|
|
20
|
+
| `read_multiple_notes` | Batch read (max 10) | `paths[]`, `includeContent`, `includeFrontmatter` |
|
|
21
|
+
| `get_frontmatter` | Extract only frontmatter | `path` |
|
|
22
|
+
| `get_notes_info` | Get metadata without content | `paths[]` |
|
|
23
|
+
| `list_directory` | List files and directories | `path` |
|
|
24
|
+
|
|
25
|
+
### Write Operations
|
|
26
|
+
|
|
27
|
+
| Tool | Purpose | Arguments |
|
|
28
|
+
| -------------------- | ------------------------------------- | ---------------------------------------- |
|
|
29
|
+
| `write_note` | Write note (overwrite/append/prepend) | `path`, `content`, `frontmatter`, `mode` |
|
|
30
|
+
| `update_frontmatter` | Update frontmatter only | `path`, `frontmatter`, `merge` |
|
|
31
|
+
| `delete_note` | Delete note (requires confirmation) | `path`, `confirmPath` |
|
|
32
|
+
| `move_note` | Move/rename note | `oldPath`, `newPath`, `overwrite` |
|
|
33
|
+
|
|
34
|
+
### Search & Tags
|
|
35
|
+
|
|
36
|
+
| Tool | Purpose | Arguments |
|
|
37
|
+
| -------------- | ----------------------------- | ------------------------------------------------------ |
|
|
38
|
+
| `search_notes` | Search by content/frontmatter | `query`, `limit`, `searchContent`, `searchFrontmatter` |
|
|
39
|
+
| `manage_tags` | Add/remove/list tags | `path`, `operation`, `tags[]` |
|
|
40
|
+
|
|
41
|
+
## Write Modes
|
|
42
|
+
|
|
43
|
+
| Mode | Description |
|
|
44
|
+
| ----------- | ------------------------------------- |
|
|
45
|
+
| `overwrite` | Replace entire file content (default) |
|
|
46
|
+
| `append` | Add content to end of file |
|
|
47
|
+
| `prepend` | Add content to beginning of file |
|
|
48
|
+
|
|
49
|
+
## Usage Examples
|
|
50
|
+
|
|
51
|
+
### Read Notes
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Read a single note
|
|
55
|
+
skill_mcp({
|
|
56
|
+
skill_name: "obsidian",
|
|
57
|
+
tool_name: "read_note",
|
|
58
|
+
arguments: '{"path": "projects/project-ideas.md"}',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Read multiple notes
|
|
62
|
+
skill_mcp({
|
|
63
|
+
skill_name: "obsidian",
|
|
64
|
+
tool_name: "read_multiple_notes",
|
|
65
|
+
arguments: '{"paths": ["note1.md", "note2.md"], "includeContent": true}',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// List directory
|
|
69
|
+
skill_mcp({
|
|
70
|
+
skill_name: "obsidian",
|
|
71
|
+
tool_name: "list_directory",
|
|
72
|
+
arguments: '{"path": "Projects"}',
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Write Notes
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Create new note (overwrite)
|
|
80
|
+
skill_mcp({
|
|
81
|
+
skill_name: "obsidian",
|
|
82
|
+
tool_name: "write_note",
|
|
83
|
+
arguments: `{
|
|
84
|
+
"path": "meeting-notes.md",
|
|
85
|
+
"content": "# Team Meeting\\n\\n## Agenda\\n- Project updates",
|
|
86
|
+
"frontmatter": {"title": "Team Meeting", "date": "2025-02-13", "tags": ["meetings", "team"]},
|
|
87
|
+
"mode": "overwrite"
|
|
88
|
+
}`,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Append to existing note
|
|
92
|
+
skill_mcp({
|
|
93
|
+
skill_name: "obsidian",
|
|
94
|
+
tool_name: "write_note",
|
|
95
|
+
arguments:
|
|
96
|
+
'{"path": "daily-log.md", "content": "\\n\\n## 3:00 PM\\n- Completed review", "mode": "append"}',
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Search & Tags
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Search notes
|
|
104
|
+
skill_mcp({
|
|
105
|
+
skill_name: "obsidian",
|
|
106
|
+
tool_name: "search_notes",
|
|
107
|
+
arguments: '{"query": "machine learning", "limit": 5, "searchContent": true}',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Add tags
|
|
111
|
+
skill_mcp({
|
|
112
|
+
skill_name: "obsidian",
|
|
113
|
+
tool_name: "manage_tags",
|
|
114
|
+
arguments: '{"path": "research-notes.md", "operation": "add", "tags": ["important", "ai"]}',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// List tags
|
|
118
|
+
skill_mcp({
|
|
119
|
+
skill_name: "obsidian",
|
|
120
|
+
tool_name: "manage_tags",
|
|
121
|
+
arguments: '{"path": "research-notes.md", "operation": "list"}',
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Delete (Safe)
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Delete requires confirmation (both paths must match)
|
|
129
|
+
skill_mcp({
|
|
130
|
+
skill_name: "obsidian",
|
|
131
|
+
tool_name: "delete_note",
|
|
132
|
+
arguments: '{"path": "old-draft.md", "confirmPath": "old-draft.md"}',
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
### Environment Variable
|
|
139
|
+
|
|
140
|
+
Set your vault path:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
export OBSIDIAN_VAULT_PATH="/path/to/your/obsidian/vault"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or configure in opencode.json:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcp": {
|
|
151
|
+
"obsidian": {
|
|
152
|
+
"command": "npx",
|
|
153
|
+
"args": ["@mauricio.wolff/mcp-obsidian", "/path/to/vault"]
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Security
|
|
160
|
+
|
|
161
|
+
- Path traversal protection: prevents access outside vault
|
|
162
|
+
- Auto-excludes: `.obsidian`, `.git`, `node_modules`
|
|
163
|
+
- Frontmatter validation: blocks dangerous objects
|
|
164
|
+
- Confirmation required for deletions
|
|
165
|
+
|
|
166
|
+
## Common Use Cases
|
|
167
|
+
|
|
168
|
+
| Task | Tool | Example |
|
|
169
|
+
| -------------------- | ---------------- | ----------------------------------------------------------------------------- |
|
|
170
|
+
| List vault files | `list_directory` | `list_directory({ path: "" })` |
|
|
171
|
+
| Read specific note | `read_note` | `read_note({ path: "tasks/project.md" })` |
|
|
172
|
+
| Create/update note | `write_note` | `write_note({ path: "new.md", content: "...", mode: "overwrite" })` |
|
|
173
|
+
| Search notes | `search_notes` | `search_notes({ query: "API", limit: 10 })` |
|
|
174
|
+
| Add tags | `manage_tags` | `manage_tags({ path: "note.md", operation: "add", tags: ["urgent"] })` |
|
|
175
|
+
| Append to daily note | `write_note` | `write_note({ path: "daily/2025-02-13.md", content: "...", mode: "append" })` |
|
|
176
|
+
|
|
177
|
+
## Tips
|
|
178
|
+
|
|
179
|
+
- Use `prettyPrint: true` for debugging, `false` (default) for production
|
|
180
|
+
- Batch reads with `read_multiple_notes` (max 10)
|
|
181
|
+
- Search supports content and frontmatter filtering
|
|
182
|
+
- Frontmatter is auto-parsed on read
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"obsidian": {
|
|
3
|
+
"command": "npx",
|
|
4
|
+
"args": ["-y", "@mauricio.wolff/mcp-obsidian", "{env:OBSIDIAN_VAULT_PATH}"],
|
|
5
|
+
"env": {
|
|
6
|
+
"OBSIDIAN_VAULT_PATH": "{env:OBSIDIAN_VAULT_PATH}"
|
|
7
|
+
},
|
|
8
|
+
"includeTools": [
|
|
9
|
+
"read_note",
|
|
10
|
+
"read_multiple_notes",
|
|
11
|
+
"get_frontmatter",
|
|
12
|
+
"get_notes_info",
|
|
13
|
+
"list_directory",
|
|
14
|
+
"write_note",
|
|
15
|
+
"update_frontmatter",
|
|
16
|
+
"delete_note",
|
|
17
|
+
"move_note",
|
|
18
|
+
"search_notes",
|
|
19
|
+
"manage_tags"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: structured-edit
|
|
3
|
+
description: Use when editing files to reduce str_replace failures - combines LSP location with read-verify-edit pattern for reliable edits
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured Edit Protocol
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The `str_replace` edit tool is the #1 source of failures in LLM coding. Models reproduce content with subtle differences (whitespace, encoding, line endings) causing "string not found" errors.
|
|
11
|
+
|
|
12
|
+
**Core principle:** Don't guess content — locate, read, verify, then edit.
|
|
13
|
+
|
|
14
|
+
## Why This Exists
|
|
15
|
+
|
|
16
|
+
`str_replace` failures happen when:
|
|
17
|
+
|
|
18
|
+
| Cause | Example |
|
|
19
|
+
| -------------------- | ------------------------------------------ |
|
|
20
|
+
| Whitespace mismatch | Tabs vs spaces, trailing spaces |
|
|
21
|
+
| Content changed | File modified since last read |
|
|
22
|
+
| Multiple matches | Same string appears twice in file |
|
|
23
|
+
| Encoding differences | Line endings (CRLF vs LF), invisible chars |
|
|
24
|
+
| Stale context | Editing from memory instead of fresh read |
|
|
25
|
+
|
|
26
|
+
**Result:** Wasted tokens on retries, frustrated developers, broken workflows.
|
|
27
|
+
|
|
28
|
+
## The Protocol
|
|
29
|
+
|
|
30
|
+
### Step 1: LOCATE
|
|
31
|
+
|
|
32
|
+
Use LSP to find exact positions instead of guessing:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Find where a function is defined
|
|
36
|
+
lsp({ operation: "goToDefinition", filePath, line, character });
|
|
37
|
+
|
|
38
|
+
// Find all references to a symbol
|
|
39
|
+
lsp({ operation: "findReferences", filePath, line, character });
|
|
40
|
+
|
|
41
|
+
// Get all symbols in a file
|
|
42
|
+
lsp({ operation: "documentSymbol", filePath, line: 1, character: 1 });
|
|
43
|
+
|
|
44
|
+
// Search for symbol across workspace
|
|
45
|
+
lsp({ operation: "workspaceSymbol", filePath, line: 1, character: 1 });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Step 2: READ
|
|
49
|
+
|
|
50
|
+
Get fresh file content at the located position:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Read context around target line
|
|
54
|
+
read({ filePath, offset: line - 10, limit: 30 });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Always include context** — don't just read the target line.
|
|
58
|
+
|
|
59
|
+
### Step 3: VERIFY
|
|
60
|
+
|
|
61
|
+
Confirm expected content exists at the location:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Check that what you expect is actually there
|
|
65
|
+
if (!content.includes(expectedSubstring)) {
|
|
66
|
+
// STOP — investigate before proceeding
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Step 4: EDIT
|
|
71
|
+
|
|
72
|
+
Apply minimal, scoped change with unique context:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Include enough surrounding lines for uniqueness
|
|
76
|
+
edit({
|
|
77
|
+
filePath,
|
|
78
|
+
oldString: " // unique context before\n targetLine\n // unique context after",
|
|
79
|
+
newString: " // unique context before\n modifiedLine\n // unique context after",
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Guidelines:**
|
|
84
|
+
|
|
85
|
+
- Include 2-3 lines before/after for uniqueness
|
|
86
|
+
- Never use just the target line
|
|
87
|
+
- Keep oldString minimal but unique
|
|
88
|
+
|
|
89
|
+
### Step 5: CONFIRM
|
|
90
|
+
|
|
91
|
+
Verify the edit succeeded:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Read back the modified section
|
|
95
|
+
read({ filePath, offset: line - 5, limit: 15 });
|
|
96
|
+
|
|
97
|
+
// Confirm content matches intent
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Red Flags — STOP
|
|
101
|
+
|
|
102
|
+
If you catch yourself:
|
|
103
|
+
|
|
104
|
+
- Editing without reading first
|
|
105
|
+
- Assuming content hasn't changed
|
|
106
|
+
- Using large multi-line oldString values
|
|
107
|
+
- Skipping the verify step
|
|
108
|
+
- Guessing line numbers without LSP
|
|
109
|
+
|
|
110
|
+
**STOP.** Return to Step 1.
|
|
111
|
+
|
|
112
|
+
## Best Practices
|
|
113
|
+
|
|
114
|
+
### File Size Matters
|
|
115
|
+
|
|
116
|
+
From research on the "harness problem":
|
|
117
|
+
|
|
118
|
+
| File Size | Strategy |
|
|
119
|
+
| ------------- | ------------------------------------------ |
|
|
120
|
+
| < 100 lines | Full rewrite often easier than str_replace |
|
|
121
|
+
| 100-400 lines | Structured edit with good context |
|
|
122
|
+
| > 400 lines | Strongly prefer structured edits |
|
|
123
|
+
|
|
124
|
+
**Prefer smaller files** — composition over monoliths.
|
|
125
|
+
|
|
126
|
+
### Unique Context Selection
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// BAD: Non-unique, whitespace-sensitive
|
|
130
|
+
oldString: "return result;";
|
|
131
|
+
|
|
132
|
+
// GOOD: Unique with surrounding context
|
|
133
|
+
oldString: " // Calculate final value\n const result = compute(input);\n return result;";
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### When to Use LSP vs Direct
|
|
137
|
+
|
|
138
|
+
| Scenario | Approach |
|
|
139
|
+
| ------------------------- | --------------------- |
|
|
140
|
+
| Finding function/class | LSP goToDefinition |
|
|
141
|
+
| Finding all usages | LSP findReferences |
|
|
142
|
+
| Modifying specific symbol | LSP + structured edit |
|
|
143
|
+
| Large refactoring | Consider full rewrite |
|
|
144
|
+
| Simple one-line change | Direct edit OK |
|
|
145
|
+
|
|
146
|
+
## Integration with Other Skills
|
|
147
|
+
|
|
148
|
+
**Use with:**
|
|
149
|
+
|
|
150
|
+
- `verification-before-completion` — Always verify edits succeeded
|
|
151
|
+
- `systematic-debugging` — When edit failures indicate deeper issues
|
|
152
|
+
- `defense-in-depth` — Add validation after structural changes
|
|
153
|
+
|
|
154
|
+
## Quick Reference
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
LOCATE → lsp({ operation: "goToDefinition" | "findReferences", ... })
|
|
158
|
+
READ → read({ filePath, offset: line-10, limit: 30 })
|
|
159
|
+
VERIFY → Check expected content exists
|
|
160
|
+
EDIT → edit({ oldString: "...unique context...", newString: "..." })
|
|
161
|
+
CONFIRM → read() again to verify success
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## The Bottom Line
|
|
165
|
+
|
|
166
|
+
**Don't trust your memory. Don't guess content. Locate, read, verify, edit, confirm.**
|
|
167
|
+
|
|
168
|
+
This protocol eliminates the #1 source of LLM coding failures.
|
|
@@ -237,10 +237,15 @@ Returns a consumable queue with:
|
|
|
237
237
|
- ready tasks
|
|
238
238
|
- idle workers
|
|
239
239
|
|
|
240
|
-
Operations:
|
|
241
|
-
- status: Read last stored queue snapshot
|
|
242
|
-
- refresh: Recompute queue from Beads + swarm progress and store snapshot
|
|
243
|
-
- clear: Clear stored queue snapshot
|
|
240
|
+
Operations with context-aware hints:
|
|
241
|
+
- status: Read last stored queue snapshot. Use for quick checks or when you just refreshed and want a stable view without re-scanning.
|
|
242
|
+
- refresh: Recompute queue from Beads + swarm progress and store snapshot. Use when work has changed (new beads, workers progressed, approvals resolved) or when status shows stale counts.
|
|
243
|
+
- clear: Clear stored queue snapshot. Use when you want to force a clean slate before a fresh refresh or when stale data is confusing the queue view.
|
|
244
|
+
|
|
245
|
+
Examples:
|
|
246
|
+
- You see pending approvals but know they were resolved: run refresh to rebuild from live worker progress.
|
|
247
|
+
- You want a quick glance during a long session: run status to reuse the last snapshot.
|
|
248
|
+
- You suspect the snapshot is corrupted or out of date: run clear, then refresh to rebuild from scratch.`,
|
|
244
249
|
args: {
|
|
245
250
|
op: tool.schema
|
|
246
251
|
.enum(["status", "refresh", "clear"])
|
|
@@ -188,18 +188,23 @@ function formatFallbackResults(
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
export default tool({
|
|
191
|
-
description: `Search observations
|
|
191
|
+
description: `Search memory across observations and markdown archives.
|
|
192
192
|
|
|
193
193
|
Purpose:
|
|
194
|
-
- Fast, ranked search across all observations in SQLite
|
|
194
|
+
- Fast, ranked search across all observations in SQLite (when FTS5 is available)
|
|
195
195
|
- Returns compact index (~50-100 tokens per result) for progressive disclosure
|
|
196
196
|
- Use memory-get for full details after identifying relevant observations
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- "
|
|
198
|
+
FTS5 availability:
|
|
199
|
+
- Auto-detected at runtime; if unavailable, observation searches fall back to file scan
|
|
200
|
+
|
|
201
|
+
Search modes and hints:
|
|
202
|
+
- "observations" (default): Best for decisions, bugs, learnings; uses FTS5 ranking when available
|
|
203
|
+
- "handoffs": Use for past session handoffs and summaries
|
|
204
|
+
- "research": Use for research notes and external findings
|
|
205
|
+
- "templates": Use for memory templates and boilerplate references
|
|
206
|
+
- "beads": Use for task artifacts in .beads/artifacts
|
|
207
|
+
- "all": Use when you are unsure where info lives; searches SQLite + markdown + beads
|
|
203
208
|
|
|
204
209
|
Example:
|
|
205
210
|
memory-search({ query: "authentication" })
|