opencodekit 0.14.6 → 0.15.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 +2 -2
- package/dist/index.js +100 -58
- package/dist/template/.opencode/.env.example +1 -0
- package/dist/template/.opencode/AGENTS.md +13 -24
- package/dist/template/.opencode/README.md +8 -119
- package/dist/template/.opencode/agent/explore.md +2 -3
- package/dist/template/.opencode/agent/general.md +56 -0
- package/dist/template/.opencode/agent/plan.md +54 -0
- package/dist/template/.opencode/agent/scout.md +15 -5
- package/dist/template/.opencode/command/analyze-project.md +2 -2
- package/dist/template/.opencode/command/brainstorm.md +1 -1
- package/dist/template/.opencode/command/design-audit.md +4 -5
- package/dist/template/.opencode/command/design.md +4 -13
- package/dist/template/.opencode/command/generate-pattern.md +2 -9
- package/dist/template/.opencode/command/implement.md +4 -4
- package/dist/template/.opencode/command/init.md +1 -1
- package/dist/template/.opencode/command/new-feature.md +2 -3
- package/dist/template/.opencode/command/plan.md +1 -1
- package/dist/template/.opencode/command/pr.md +0 -1
- package/dist/template/.opencode/command/research.md +20 -6
- package/dist/template/.opencode/command/restore-image.md +1 -9
- package/dist/template/.opencode/command/revert-feature.md +1 -1
- package/dist/template/.opencode/command/review-codebase.md +4 -4
- package/dist/template/.opencode/command/status.md +1 -2
- package/dist/template/.opencode/command/summarize.md +1 -2
- package/dist/template/.opencode/command/triage.md +4 -32
- package/dist/template/.opencode/dcp.jsonc +68 -68
- package/dist/template/.opencode/memory/_templates/README.md +35 -0
- package/dist/template/.opencode/memory/_templates/project/architecture.md +60 -0
- package/dist/template/.opencode/memory/_templates/project/commands.md +72 -0
- package/dist/template/.opencode/memory/_templates/project/conventions.md +68 -0
- package/dist/template/.opencode/memory/_templates/project/gotchas.md +41 -0
- package/dist/template/.opencode/memory/beads-workflow.md +30 -29
- package/dist/template/.opencode/memory/project/architecture.md +31 -50
- package/dist/template/.opencode/memory/project/commands.md +41 -22
- package/dist/template/.opencode/memory/project/conventions.md +39 -177
- package/dist/template/.opencode/memory/project/gotchas.md +21 -177
- package/dist/template/.opencode/memory/user.example.md +5 -0
- package/dist/template/.opencode/opencode.json +644 -579
- package/dist/template/.opencode/package.json +18 -21
- package/dist/template/.opencode/plugin/compaction.ts +79 -85
- package/dist/template/.opencode/plugin/env-ctx.ts +19 -19
- package/dist/template/.opencode/plugin/lib/notify.ts +41 -45
- package/dist/template/.opencode/plugin/lsp.ts +197 -200
- package/dist/template/.opencode/plugin/memory.ts +14 -112
- package/dist/template/.opencode/plugin/package.json +5 -5
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +486 -521
- package/dist/template/.opencode/plugin/truncator.ts +47 -50
- package/dist/template/.opencode/plugin/tsconfig.json +14 -14
- package/dist/template/.opencode/skill/chrome-devtools/mcp.json +17 -17
- package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +17 -12
- package/dist/template/.opencode/skill/condition-based-waiting/example.ts +63 -69
- package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +14 -8
- package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +14 -3
- package/dist/template/.opencode/skill/playwright/mcp.json +14 -14
- package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +21 -8
- package/dist/template/.opencode/skill/requesting-code-review/review.md +14 -0
- package/dist/template/.opencode/skill/root-cause-tracing/SKILL.md +18 -4
- package/dist/template/.opencode/skill/source-code-research/SKILL.md +9 -7
- package/dist/template/.opencode/skill/test-driven-development/SKILL.md +49 -32
- package/dist/template/.opencode/skill/testing-anti-patterns/SKILL.md +40 -22
- package/dist/template/.opencode/skill/testing-skills-with-subagents/SKILL.md +46 -26
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +117 -44
- package/dist/template/.opencode/skill/v0/SKILL.md +1 -7
- package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +27 -19
- package/dist/template/.opencode/skill/writing-skills/anthropic-best-practices.md +171 -148
- package/dist/template/.opencode/skill/writing-skills/persuasion-principles.md +39 -6
- package/dist/template/.opencode/tool/memory-read.ts +44 -56
- package/dist/template/.opencode/tool/memory-search.ts +8 -291
- package/dist/template/.opencode/tool/memory-update.ts +47 -51
- package/dist/template/.opencode/tool/observation.ts +6 -180
- package/dist/template/.opencode/tsconfig.json +19 -19
- package/package.json +19 -15
- package/dist/template/.opencode/.background-tasks.json +0 -114
- package/dist/template/.opencode/.ralph-state.json +0 -12
- package/dist/template/.opencode/agent/build.md +0 -327
- package/dist/template/.opencode/agent/ninja.md +0 -351
- package/dist/template/.opencode/agent/planner.md +0 -281
- package/dist/template/.opencode/agent/rush.md +0 -223
- package/dist/template/.opencode/memory/handoffs/README.md +0 -83
- package/dist/template/.opencode/memory/observations/.gitkeep +0 -0
- package/dist/template/.opencode/memory/observations/2026-01-09-pattern-ampcode-mcp-json-includetools-pattern.md +0 -42
- package/dist/template/.opencode/memory/vector_db/memories.lance/_transactions/0-0d25ba80-ba3b-4209-9046-b45d6093b4da.txn +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/_versions/1.manifest +0 -0
- package/dist/template/.opencode/memory/vector_db/memories.lance/data/1111100101010101011010004a9ef34df6b29f36a9a53a2892.lance +0 -0
- package/dist/template/.opencode/tool/ast-grep.ts +0 -245
- package/dist/template/.opencode/tool/background.ts +0 -509
- package/dist/template/.opencode/tool/bd-inbox.ts +0 -110
- package/dist/template/.opencode/tool/bd-msg.ts +0 -62
- package/dist/template/.opencode/tool/bd-release.ts +0 -71
- package/dist/template/.opencode/tool/bd-reserve.ts +0 -121
- package/dist/template/.opencode/tool/memory-embed.ts +0 -183
- package/dist/template/.opencode/tool/memory-index.ts +0 -769
- package/dist/template/.opencode/tool/repo-map.ts +0 -451
|
@@ -9,54 +9,51 @@
|
|
|
9
9
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
10
10
|
|
|
11
11
|
export const TruncatorPlugin: Plugin = async ({ client }) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
};
|
|
12
|
+
const sessionContext = new Map<string, number>();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
event: async ({ event }) => {
|
|
16
|
+
const props = event.properties as Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
if (event.type === "session.updated") {
|
|
19
|
+
const info = props?.info as Record<string, unknown> | undefined;
|
|
20
|
+
const tokenStats = (info?.tokens || props?.tokens) as
|
|
21
|
+
| { used: number; limit: number }
|
|
22
|
+
| undefined;
|
|
23
|
+
const sessionId = (info?.id || props?.sessionID) as string | undefined;
|
|
24
|
+
|
|
25
|
+
if (sessionId && tokenStats?.used && tokenStats?.limit) {
|
|
26
|
+
sessionContext.set(sessionId, Math.round((tokenStats.used / tokenStats.limit) * 100));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (event.type === "session.deleted") {
|
|
31
|
+
const sessionId = props?.sessionID as string | undefined;
|
|
32
|
+
if (sessionId) {
|
|
33
|
+
sessionContext.delete(sessionId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
"tool.execute.after": async (input, output) => {
|
|
39
|
+
const pct = sessionContext.get(input.sessionID) || 0;
|
|
40
|
+
if (pct < 70) return; // Only warn under pressure
|
|
41
|
+
|
|
42
|
+
// Thresholds get tighter as context fills up
|
|
43
|
+
const threshold = pct >= 95 ? 5000 : pct >= 85 ? 10000 : 20000;
|
|
44
|
+
const outputStr = output.output || "";
|
|
45
|
+
|
|
46
|
+
if (outputStr.length > threshold) {
|
|
47
|
+
await client.app
|
|
48
|
+
.log({
|
|
49
|
+
body: {
|
|
50
|
+
service: "truncator",
|
|
51
|
+
level: pct >= 95 ? "warn" : "info",
|
|
52
|
+
message: `Large output from ${input.tool}: ${outputStr.length} chars (threshold: ${threshold}, context: ${pct}%)`,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
.catch(() => {});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
62
59
|
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": false,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["**/*.ts"],
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
16
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
"chrome-devtools": {
|
|
3
|
+
"command": "npx",
|
|
4
|
+
"args": ["-y", "chrome-devtools-mcp@latest", "--stdio"],
|
|
5
|
+
"includeTools": [
|
|
6
|
+
"take_snapshot",
|
|
7
|
+
"take_screenshot",
|
|
8
|
+
"navigate_page",
|
|
9
|
+
"new_page",
|
|
10
|
+
"list_pages",
|
|
11
|
+
"click",
|
|
12
|
+
"fill",
|
|
13
|
+
"hover",
|
|
14
|
+
"press_key",
|
|
15
|
+
"evaluate_script",
|
|
16
|
+
"wait_for"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
19
|
}
|
|
@@ -27,12 +27,14 @@ digraph when_to_use {
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
**Use when:**
|
|
30
|
+
|
|
30
31
|
- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
|
|
31
32
|
- Tests are flaky (pass sometimes, fail under load)
|
|
32
33
|
- Tests timeout when run in parallel
|
|
33
34
|
- Waiting for async operations to complete
|
|
34
35
|
|
|
35
36
|
**Don't use when:**
|
|
37
|
+
|
|
36
38
|
- Testing actual timing behavior (debounce, throttle intervals)
|
|
37
39
|
- Always document WHY if using arbitrary timeout
|
|
38
40
|
|
|
@@ -40,7 +42,7 @@ digraph when_to_use {
|
|
|
40
42
|
|
|
41
43
|
```typescript
|
|
42
44
|
// ❌ BEFORE: Guessing at timing
|
|
43
|
-
await new Promise(r => setTimeout(r, 50));
|
|
45
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
44
46
|
const result = getResult();
|
|
45
47
|
expect(result).toBeDefined();
|
|
46
48
|
|
|
@@ -52,22 +54,23 @@ expect(result).toBeDefined();
|
|
|
52
54
|
|
|
53
55
|
## Quick Patterns
|
|
54
56
|
|
|
55
|
-
| Scenario
|
|
56
|
-
|
|
57
|
-
| Wait for event
|
|
58
|
-
| Wait for state
|
|
59
|
-
| Wait for count
|
|
60
|
-
| Wait for file
|
|
61
|
-
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)`
|
|
57
|
+
| Scenario | Pattern |
|
|
58
|
+
| ----------------- | ---------------------------------------------------- |
|
|
59
|
+
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
|
|
60
|
+
| Wait for state | `waitFor(() => machine.state === 'ready')` |
|
|
61
|
+
| Wait for count | `waitFor(() => items.length >= 5)` |
|
|
62
|
+
| Wait for file | `waitFor(() => fs.existsSync(path))` |
|
|
63
|
+
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
|
|
62
64
|
|
|
63
65
|
## Implementation
|
|
64
66
|
|
|
65
67
|
Generic polling function:
|
|
68
|
+
|
|
66
69
|
```typescript
|
|
67
70
|
async function waitFor<T>(
|
|
68
71
|
condition: () => T | undefined | null | false,
|
|
69
72
|
description: string,
|
|
70
|
-
timeoutMs = 5000
|
|
73
|
+
timeoutMs = 5000,
|
|
71
74
|
): Promise<T> {
|
|
72
75
|
const startTime = Date.now();
|
|
73
76
|
|
|
@@ -79,7 +82,7 @@ async function waitFor<T>(
|
|
|
79
82
|
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
|
|
85
|
+
await new Promise((r) => setTimeout(r, 10)); // Poll every 10ms
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
```
|
|
@@ -101,12 +104,13 @@ See @example.ts for complete implementation with domain-specific helpers (`waitF
|
|
|
101
104
|
|
|
102
105
|
```typescript
|
|
103
106
|
// Tool ticks every 100ms - need 2 ticks to verify partial output
|
|
104
|
-
await waitForEvent(manager,
|
|
105
|
-
await new Promise(r => setTimeout(r, 200));
|
|
107
|
+
await waitForEvent(manager, "TOOL_STARTED"); // First: wait for condition
|
|
108
|
+
await new Promise((r) => setTimeout(r, 200)); // Then: wait for timed behavior
|
|
106
109
|
// 200ms = 2 ticks at 100ms intervals - documented and justified
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
**Requirements:**
|
|
113
|
+
|
|
110
114
|
1. First wait for triggering condition
|
|
111
115
|
2. Based on known timing (not guessing)
|
|
112
116
|
3. Comment explaining WHY
|
|
@@ -114,6 +118,7 @@ await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
|
|
|
114
118
|
## Real-World Impact
|
|
115
119
|
|
|
116
120
|
From debugging session (2025-10-03):
|
|
121
|
+
|
|
117
122
|
- Fixed 15 flaky tests across 3 files
|
|
118
123
|
- Pass rate: 60% → 100%
|
|
119
124
|
- Execution time: 40% faster
|
|
@@ -18,33 +18,29 @@ import type { LaceEvent, LaceEventType } from "~/threads/types";
|
|
|
18
18
|
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
19
|
*/
|
|
20
20
|
export function waitForEvent(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
threadManager: ThreadManager,
|
|
22
|
+
threadId: string,
|
|
23
|
+
eventType: LaceEventType,
|
|
24
|
+
timeoutMs = 5000,
|
|
25
25
|
): Promise<LaceEvent> {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const startTime = Date.now();
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const check = () => {
|
|
30
|
+
const events = threadManager.getEvents(threadId);
|
|
31
|
+
const event = events.find((e) => e.type === eventType);
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} else {
|
|
42
|
-
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
43
|
-
}
|
|
44
|
-
};
|
|
33
|
+
if (event) {
|
|
34
|
+
resolve(event);
|
|
35
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
+
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
|
|
37
|
+
} else {
|
|
38
|
+
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
39
|
+
}
|
|
40
|
+
};
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
check();
|
|
43
|
+
});
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
/**
|
|
@@ -62,34 +58,34 @@ export function waitForEvent(
|
|
|
62
58
|
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
63
59
|
*/
|
|
64
60
|
export function waitForEventCount(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
threadManager: ThreadManager,
|
|
62
|
+
threadId: string,
|
|
63
|
+
eventType: LaceEventType,
|
|
64
|
+
count: number,
|
|
65
|
+
timeoutMs = 5000,
|
|
70
66
|
): Promise<LaceEvent[]> {
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const startTime = Date.now();
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
const check = () => {
|
|
71
|
+
const events = threadManager.getEvents(threadId);
|
|
72
|
+
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
if (matchingEvents.length >= count) {
|
|
75
|
+
resolve(matchingEvents);
|
|
76
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
77
|
+
reject(
|
|
78
|
+
new Error(
|
|
79
|
+
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`,
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
setTimeout(check, 10);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
check();
|
|
88
|
+
});
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
/**
|
|
@@ -113,32 +109,30 @@ export function waitForEventCount(
|
|
|
113
109
|
* );
|
|
114
110
|
*/
|
|
115
111
|
export function waitForEventMatch(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
threadManager: ThreadManager,
|
|
113
|
+
threadId: string,
|
|
114
|
+
predicate: (event: LaceEvent) => boolean,
|
|
115
|
+
description: string,
|
|
116
|
+
timeoutMs = 5000,
|
|
121
117
|
): Promise<LaceEvent> {
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const startTime = Date.now();
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
const check = () => {
|
|
122
|
+
const events = threadManager.getEvents(threadId);
|
|
123
|
+
const event = events.find(predicate);
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
};
|
|
125
|
+
if (event) {
|
|
126
|
+
resolve(event);
|
|
127
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
128
|
+
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
|
|
129
|
+
} else {
|
|
130
|
+
setTimeout(check, 10);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
139
133
|
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
check();
|
|
135
|
+
});
|
|
142
136
|
}
|
|
143
137
|
|
|
144
138
|
// Usage example from actual debugging session:
|
|
@@ -17,6 +17,7 @@ Single validation: "We fixed the bug"
|
|
|
17
17
|
Multiple layers: "We made the bug impossible"
|
|
18
18
|
|
|
19
19
|
Different layers catch different cases:
|
|
20
|
+
|
|
20
21
|
- Entry validation catches most bugs
|
|
21
22
|
- Business logic catches edge cases
|
|
22
23
|
- Environment guards prevent context-specific dangers
|
|
@@ -25,12 +26,13 @@ Different layers catch different cases:
|
|
|
25
26
|
## The Four Layers
|
|
26
27
|
|
|
27
28
|
### Layer 1: Entry Point Validation
|
|
29
|
+
|
|
28
30
|
**Purpose:** Reject obviously invalid input at API boundary
|
|
29
31
|
|
|
30
32
|
```typescript
|
|
31
33
|
function createProject(name: string, workingDirectory: string) {
|
|
32
|
-
if (!workingDirectory || workingDirectory.trim() ===
|
|
33
|
-
throw new Error(
|
|
34
|
+
if (!workingDirectory || workingDirectory.trim() === "") {
|
|
35
|
+
throw new Error("workingDirectory cannot be empty");
|
|
34
36
|
}
|
|
35
37
|
if (!existsSync(workingDirectory)) {
|
|
36
38
|
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
|
|
@@ -43,31 +45,31 @@ function createProject(name: string, workingDirectory: string) {
|
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
### Layer 2: Business Logic Validation
|
|
48
|
+
|
|
46
49
|
**Purpose:** Ensure data makes sense for this operation
|
|
47
50
|
|
|
48
51
|
```typescript
|
|
49
52
|
function initializeWorkspace(projectDir: string, sessionId: string) {
|
|
50
53
|
if (!projectDir) {
|
|
51
|
-
throw new Error(
|
|
54
|
+
throw new Error("projectDir required for workspace initialization");
|
|
52
55
|
}
|
|
53
56
|
// ... proceed
|
|
54
57
|
}
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
### Layer 3: Environment Guards
|
|
61
|
+
|
|
58
62
|
**Purpose:** Prevent dangerous operations in specific contexts
|
|
59
63
|
|
|
60
64
|
```typescript
|
|
61
65
|
async function gitInit(directory: string) {
|
|
62
66
|
// In tests, refuse git init outside temp directories
|
|
63
|
-
if (process.env.NODE_ENV ===
|
|
67
|
+
if (process.env.NODE_ENV === "test") {
|
|
64
68
|
const normalized = normalize(resolve(directory));
|
|
65
69
|
const tmpDir = normalize(resolve(tmpdir()));
|
|
66
70
|
|
|
67
71
|
if (!normalized.startsWith(tmpDir)) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`Refusing git init outside temp dir during tests: ${directory}`
|
|
70
|
-
);
|
|
72
|
+
throw new Error(`Refusing git init outside temp dir during tests: ${directory}`);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
// ... proceed
|
|
@@ -75,12 +77,13 @@ async function gitInit(directory: string) {
|
|
|
75
77
|
```
|
|
76
78
|
|
|
77
79
|
### Layer 4: Debug Instrumentation
|
|
80
|
+
|
|
78
81
|
**Purpose:** Capture context for forensics
|
|
79
82
|
|
|
80
83
|
```typescript
|
|
81
84
|
async function gitInit(directory: string) {
|
|
82
85
|
const stack = new Error().stack;
|
|
83
|
-
logger.debug(
|
|
86
|
+
logger.debug("About to git init", {
|
|
84
87
|
directory,
|
|
85
88
|
cwd: process.cwd(),
|
|
86
89
|
stack,
|
|
@@ -103,12 +106,14 @@ When you find a bug:
|
|
|
103
106
|
Bug: Empty `projectDir` caused `git init` in source code
|
|
104
107
|
|
|
105
108
|
**Data flow:**
|
|
109
|
+
|
|
106
110
|
1. Test setup → empty string
|
|
107
111
|
2. `Project.create(name, '')`
|
|
108
112
|
3. `WorkspaceManager.createWorkspace('')`
|
|
109
113
|
4. `git init` runs in `process.cwd()`
|
|
110
114
|
|
|
111
115
|
**Four layers added:**
|
|
116
|
+
|
|
112
117
|
- Layer 1: `Project.create()` validates not empty/exists/writable
|
|
113
118
|
- Layer 2: `WorkspaceManager` validates projectDir not empty
|
|
114
119
|
- Layer 3: `WorktreeManager` refuses git init outside tmpdir in tests
|
|
@@ -119,6 +124,7 @@ Bug: Empty `projectDir` caused `git init` in source code
|
|
|
119
124
|
## Key Insight
|
|
120
125
|
|
|
121
126
|
All four layers were necessary. During testing, each layer caught bugs the others missed:
|
|
127
|
+
|
|
122
128
|
- Different code paths bypassed entry validation
|
|
123
129
|
- Mocks bypassed business logic checks
|
|
124
130
|
- Edge cases on different platforms needed environment guards
|
|
@@ -32,12 +32,14 @@ digraph when_to_use {
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
**Use when:**
|
|
35
|
+
|
|
35
36
|
- 3+ test files failing with different root causes
|
|
36
37
|
- Multiple subsystems broken independently
|
|
37
38
|
- Each problem can be understood without context from others
|
|
38
39
|
- No shared state between investigations
|
|
39
40
|
|
|
40
41
|
**Don't use when:**
|
|
42
|
+
|
|
41
43
|
- Failures are related (fix one might fix others)
|
|
42
44
|
- Need to understand full system state
|
|
43
45
|
- Agents would interfere with each other
|
|
@@ -47,6 +49,7 @@ digraph when_to_use {
|
|
|
47
49
|
### 1. Identify Independent Domains
|
|
48
50
|
|
|
49
51
|
Group failures by what's broken:
|
|
52
|
+
|
|
50
53
|
- File A tests: Tool approval flow
|
|
51
54
|
- File B tests: Batch completion behavior
|
|
52
55
|
- File C tests: Abort functionality
|
|
@@ -56,6 +59,7 @@ Each domain is independent - fixing tool approval doesn't affect abort tests.
|
|
|
56
59
|
### 2. Create Focused Agent Tasks
|
|
57
60
|
|
|
58
61
|
Each agent gets:
|
|
62
|
+
|
|
59
63
|
- **Specific scope:** One test file or subsystem
|
|
60
64
|
- **Clear goal:** Make these tests pass
|
|
61
65
|
- **Constraints:** Don't change other code
|
|
@@ -65,15 +69,16 @@ Each agent gets:
|
|
|
65
69
|
|
|
66
70
|
```typescript
|
|
67
71
|
// In Claude Code / AI environment
|
|
68
|
-
Task("Fix agent-tool-abort.test.ts failures")
|
|
69
|
-
Task("Fix batch-completion-behavior.test.ts failures")
|
|
70
|
-
Task("Fix tool-approval-race-conditions.test.ts failures")
|
|
72
|
+
Task("Fix agent-tool-abort.test.ts failures");
|
|
73
|
+
Task("Fix batch-completion-behavior.test.ts failures");
|
|
74
|
+
Task("Fix tool-approval-race-conditions.test.ts failures");
|
|
71
75
|
// All three run concurrently
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
### 4. Review and Integrate
|
|
75
79
|
|
|
76
80
|
When agents return:
|
|
81
|
+
|
|
77
82
|
- Read each summary
|
|
78
83
|
- Verify fixes don't conflict
|
|
79
84
|
- Run full test suite
|
|
@@ -82,6 +87,7 @@ When agents return:
|
|
|
82
87
|
## Agent Prompt Structure
|
|
83
88
|
|
|
84
89
|
Good agent prompts are:
|
|
90
|
+
|
|
85
91
|
1. **Focused** - One clear problem domain
|
|
86
92
|
2. **Self-contained** - All context needed to understand the problem
|
|
87
93
|
3. **Specific about output** - What should the agent return?
|
|
@@ -133,6 +139,7 @@ Return: Summary of what you found and what you fixed.
|
|
|
133
139
|
**Scenario:** 6 test failures across 3 files after major refactoring
|
|
134
140
|
|
|
135
141
|
**Failures:**
|
|
142
|
+
|
|
136
143
|
- agent-tool-abort.test.ts: 3 failures (timing issues)
|
|
137
144
|
- batch-completion-behavior.test.ts: 2 failures (tools not executing)
|
|
138
145
|
- tool-approval-race-conditions.test.ts: 1 failure (execution count = 0)
|
|
@@ -140,6 +147,7 @@ Return: Summary of what you found and what you fixed.
|
|
|
140
147
|
**Decision:** Independent domains - abort logic separate from batch completion separate from race conditions
|
|
141
148
|
|
|
142
149
|
**Dispatch:**
|
|
150
|
+
|
|
143
151
|
```
|
|
144
152
|
Agent 1 → Fix agent-tool-abort.test.ts
|
|
145
153
|
Agent 2 → Fix batch-completion-behavior.test.ts
|
|
@@ -147,6 +155,7 @@ Agent 3 → Fix tool-approval-race-conditions.test.ts
|
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
**Results:**
|
|
158
|
+
|
|
150
159
|
- Agent 1: Replaced timeouts with event-based waiting
|
|
151
160
|
- Agent 2: Fixed event structure bug (threadId in wrong place)
|
|
152
161
|
- Agent 3: Added wait for async tool execution to complete
|
|
@@ -165,6 +174,7 @@ Agent 3 → Fix tool-approval-race-conditions.test.ts
|
|
|
165
174
|
## Verification
|
|
166
175
|
|
|
167
176
|
After agents return:
|
|
177
|
+
|
|
168
178
|
1. **Review each summary** - Understand what changed
|
|
169
179
|
2. **Check for conflicts** - Did agents edit same code?
|
|
170
180
|
3. **Run full suite** - Verify all fixes work together
|
|
@@ -173,6 +183,7 @@ After agents return:
|
|
|
173
183
|
## Real-World Impact
|
|
174
184
|
|
|
175
185
|
From debugging session (2025-10-03):
|
|
186
|
+
|
|
176
187
|
- 6 failures across 3 files
|
|
177
188
|
- 3 agents dispatched in parallel
|
|
178
189
|
- All investigations completed concurrently
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
"playwright": {
|
|
3
|
+
"command": "npx",
|
|
4
|
+
"args": ["@playwright/mcp@latest"],
|
|
5
|
+
"includeTools": [
|
|
6
|
+
"browser_navigate",
|
|
7
|
+
"browser_snapshot",
|
|
8
|
+
"browser_take_screenshot",
|
|
9
|
+
"browser_click",
|
|
10
|
+
"browser_type",
|
|
11
|
+
"browser_fill",
|
|
12
|
+
"browser_wait_for",
|
|
13
|
+
"browser_resize"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
16
|
}
|