@kennethsolomon/shipkit 1.0.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 +321 -0
- package/bin/shipkit.js +146 -0
- package/commands/sk/brainstorm.md +63 -0
- package/commands/sk/branch.md +35 -0
- package/commands/sk/config.md +96 -0
- package/commands/sk/execute-plan.md +85 -0
- package/commands/sk/features.md +238 -0
- package/commands/sk/finish-feature.md +154 -0
- package/commands/sk/help.md +103 -0
- package/commands/sk/hotfix.md +61 -0
- package/commands/sk/plan.md +30 -0
- package/commands/sk/release.md +72 -0
- package/commands/sk/security-check.md +188 -0
- package/commands/sk/set-profile.md +71 -0
- package/commands/sk/status.md +25 -0
- package/commands/sk/update-task.md +35 -0
- package/commands/sk/write-plan.md +72 -0
- package/package.json +23 -0
- package/skills/sk:accessibility/LICENSE.txt +177 -0
- package/skills/sk:accessibility/SKILL.md +150 -0
- package/skills/sk:api-design/LICENSE.txt +177 -0
- package/skills/sk:api-design/SKILL.md +158 -0
- package/skills/sk:brainstorming/SKILL.md +124 -0
- package/skills/sk:debug/SKILL.md +252 -0
- package/skills/sk:debug/debug_conductor.py +177 -0
- package/skills/sk:debug/lib/__init__.py +1 -0
- package/skills/sk:debug/lib/bug_gatherer.py +55 -0
- package/skills/sk:debug/lib/context_reader.py +139 -0
- package/skills/sk:debug/lib/findings_writer.py +76 -0
- package/skills/sk:debug/lib/lessons_writer.py +165 -0
- package/skills/sk:debug/lib/step_runner.py +326 -0
- package/skills/sk:features/SKILL.md +238 -0
- package/skills/sk:frontend-design/LICENSE.txt +177 -0
- package/skills/sk:frontend-design/SKILL.md +191 -0
- package/skills/sk:laravel-init/SKILL.md +37 -0
- package/skills/sk:laravel-new/SKILL.md +68 -0
- package/skills/sk:lint/SKILL.md +113 -0
- package/skills/sk:perf/LICENSE.txt +177 -0
- package/skills/sk:perf/SKILL.md +188 -0
- package/skills/sk:release/SKILL.md +113 -0
- package/skills/sk:release/references/android-checklist.md +269 -0
- package/skills/sk:release/references/ios-checklist.md +339 -0
- package/skills/sk:release/release.sh +378 -0
- package/skills/sk:review/SKILL.md +346 -0
- package/skills/sk:review/references/security-checklist.md +223 -0
- package/skills/sk:schema-migrate/SKILL.md +125 -0
- package/skills/sk:schema-migrate/orms/drizzle.md +546 -0
- package/skills/sk:schema-migrate/orms/laravel.md +367 -0
- package/skills/sk:schema-migrate/orms/prisma.md +357 -0
- package/skills/sk:schema-migrate/orms/rails.md +351 -0
- package/skills/sk:schema-migrate/orms/sqlalchemy.md +385 -0
- package/skills/sk:schema-migrate/references/detection.md +110 -0
- package/skills/sk:setup-claude/SKILL.md +365 -0
- package/skills/sk:setup-claude/references/detection.md +6 -0
- package/skills/sk:setup-claude/references/templates.md +11 -0
- package/skills/sk:setup-claude/scripts/apply_setup_claude.py +443 -0
- package/skills/sk:setup-claude/scripts/detect_arch_changes.py +437 -0
- package/skills/sk:setup-claude/templates/.claude/docs/arch-changelog-guide.md.template +6 -0
- package/skills/sk:setup-claude/templates/.claude/docs/changelog-guide.md.template +12 -0
- package/skills/sk:setup-claude/templates/CHANGELOG.md.template +21 -0
- package/skills/sk:setup-claude/templates/CLAUDE.md.template +299 -0
- package/skills/sk:setup-claude/templates/arch-changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/changelog-guide.md.template +3 -0
- package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +57 -0
- package/skills/sk:setup-claude/templates/commands/features.md.template +238 -0
- package/skills/sk:setup-claude/templates/commands/finish-feature.md.template +155 -0
- package/skills/sk:setup-claude/templates/commands/plan.md.template +30 -0
- package/skills/sk:setup-claude/templates/commands/re-setup.md.template +38 -0
- package/skills/sk:setup-claude/templates/commands/release.md.template +74 -0
- package/skills/sk:setup-claude/templates/commands/security-check.md.template +172 -0
- package/skills/sk:setup-claude/templates/commands/status.md.template +17 -0
- package/skills/sk:setup-claude/templates/commands/write-plan.md.template +34 -0
- package/skills/sk:setup-claude/templates/finish-feature.md.template +3 -0
- package/skills/sk:setup-claude/templates/plan.md.template +3 -0
- package/skills/sk:setup-claude/templates/status.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks/findings.md.template +19 -0
- package/skills/sk:setup-claude/templates/tasks/lessons.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/progress.md.template +20 -0
- package/skills/sk:setup-claude/templates/tasks/security-findings.md.template +5 -0
- package/skills/sk:setup-claude/templates/tasks/todo.md.template +26 -0
- package/skills/sk:setup-claude/templates/tasks/workflow-status.md.template +31 -0
- package/skills/sk:setup-claude/templates/tasks-findings.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-lessons.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-progress.md.template +3 -0
- package/skills/sk:setup-claude/templates/tasks-todo.md.template +3 -0
- package/skills/sk:setup-claude/tests/test_apply_setup_claude.py +193 -0
- package/skills/sk:setup-optimizer/SKILL.md +184 -0
- package/skills/sk:setup-optimizer/lib/__init__.py +24 -0
- package/skills/sk:setup-optimizer/lib/detect.py +205 -0
- package/skills/sk:setup-optimizer/lib/discover.py +221 -0
- package/skills/sk:setup-optimizer/lib/enrich.py +163 -0
- package/skills/sk:setup-optimizer/lib/merge.py +277 -0
- package/skills/sk:setup-optimizer/lib/sidecar.py +129 -0
- package/skills/sk:setup-optimizer/optimize_claude.py +174 -0
- package/skills/sk:setup-optimizer/templates/CLAUDE.md.template +105 -0
- package/skills/sk:skill-creator/LICENSE.txt +202 -0
- package/skills/sk:skill-creator/SKILL.md +479 -0
- package/skills/sk:skill-creator/agents/analyzer.md +274 -0
- package/skills/sk:skill-creator/agents/comparator.md +202 -0
- package/skills/sk:skill-creator/agents/grader.md +223 -0
- package/skills/sk:skill-creator/assets/eval_review.html +146 -0
- package/skills/sk:skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/sk:skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/sk:skill-creator/references/schemas.md +430 -0
- package/skills/sk:skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/sk:skill-creator/scripts/generate_report.py +326 -0
- package/skills/sk:skill-creator/scripts/improve_description.py +248 -0
- package/skills/sk:skill-creator/scripts/package_skill.py +136 -0
- package/skills/sk:skill-creator/scripts/quick_validate.py +103 -0
- package/skills/sk:skill-creator/scripts/run_eval.py +310 -0
- package/skills/sk:skill-creator/scripts/run_loop.py +332 -0
- package/skills/sk:skill-creator/scripts/utils.py +47 -0
- package/skills/sk:smart-commit/SKILL.md +175 -0
- package/skills/sk:test/SKILL.md +171 -0
- package/skills/sk:write-tests/SKILL.md +195 -0
- package/skills/sk:write-tests/references/patterns.md +209 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sk:debug
|
|
3
|
+
description: "Structured bug investigation: reproduce, isolate, hypothesize, verify, fix. Logs findings systematically."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured Debugging Workflow
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Systematic bug investigation that follows a disciplined process: reproduce, isolate, hypothesize, verify, fix. Every finding is logged to prevent repeated work and build project knowledge.
|
|
11
|
+
|
|
12
|
+
<HARD-GATE>
|
|
13
|
+
Do NOT jump to fixing code before you understand the bug. No code changes until a hypothesis is CONFIRMED through systematic investigation. Random fixes waste time and mask root causes.
|
|
14
|
+
</HARD-GATE>
|
|
15
|
+
|
|
16
|
+
## Anti-Patterns — Do NOT Do These
|
|
17
|
+
|
|
18
|
+
- **Changing code before understanding** — Read and analyze first
|
|
19
|
+
- **Trying random fixes** — "Maybe if I change this..." is not debugging
|
|
20
|
+
- **Ignoring stack traces** — They tell you exactly where to look
|
|
21
|
+
- **Fixing symptoms, not causes** — A try/catch around a crash is not a fix
|
|
22
|
+
- **Skipping reproduction** — If you can't reproduce it, you can't verify the fix
|
|
23
|
+
- **Debugging in production** — Reproduce locally first
|
|
24
|
+
|
|
25
|
+
## Allowed Tools
|
|
26
|
+
|
|
27
|
+
Bash, Read, Write, Edit, Glob, Grep, mcp__plugin_playwright_playwright__browser_navigate, mcp__plugin_playwright_playwright__browser_console_messages, mcp__plugin_playwright_playwright__browser_network_requests, mcp__plugin_playwright_playwright__browser_take_screenshot, mcp__plugin_playwright_playwright__browser_snapshot
|
|
28
|
+
|
|
29
|
+
## Steps
|
|
30
|
+
|
|
31
|
+
You MUST complete these steps in order:
|
|
32
|
+
|
|
33
|
+
### 1. Gather Information
|
|
34
|
+
|
|
35
|
+
Parse what the user tells you:
|
|
36
|
+
|
|
37
|
+
- **Error message**: Extract the exact error text
|
|
38
|
+
- **Stack trace**: Identify the file, line, and call chain
|
|
39
|
+
- **Expected vs actual behavior**: What should happen vs what does happen
|
|
40
|
+
- **Trigger conditions**: When does it happen? Always, sometimes, under specific conditions?
|
|
41
|
+
- **Recent changes**: Did it work before? What changed?
|
|
42
|
+
|
|
43
|
+
If the user provides insufficient information, ask specific questions — don't guess.
|
|
44
|
+
|
|
45
|
+
### 2. Read Project Context
|
|
46
|
+
|
|
47
|
+
Check for existing knowledge:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
CLAUDE.md — Project conventions, known issues
|
|
51
|
+
tasks/findings.md — Previous debugging sessions, known bugs
|
|
52
|
+
tasks/lessons.md — Patterns that caused issues before
|
|
53
|
+
tasks/progress.md — Recent work log and error log
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**If `tasks/lessons.md` exists, read it in full.** For each active lesson, apply its prevention rule to your investigation — treat lessons as standing constraints, not just history. For example: if a lesson says "always check env vars before checking application code", do that first.
|
|
57
|
+
|
|
58
|
+
**If `tasks/progress.md` exists**, scan the Error Log for failures near the bug's reported time — they often share a root cause with the current bug.
|
|
59
|
+
|
|
60
|
+
Check if this bug (or something similar) has been investigated before.
|
|
61
|
+
|
|
62
|
+
### 3. Check Recent Changes
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git log --oneline -10
|
|
66
|
+
git diff HEAD~3 --stat
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Correlate the bug timeline with recent changes. Did the bug start after a specific commit?
|
|
70
|
+
|
|
71
|
+
### 4. Reproduce the Bug
|
|
72
|
+
|
|
73
|
+
**Determine the bug surface first:**
|
|
74
|
+
|
|
75
|
+
#### A. Server / CLI / Non-Browser Bug
|
|
76
|
+
|
|
77
|
+
Run the specific command, test, or action that triggers the bug. Capture the full output.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Run the failing test/command
|
|
81
|
+
[specific command that triggers the bug]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### B. Browser / UI Bug
|
|
85
|
+
|
|
86
|
+
If the bug is visual, involves JavaScript errors, or requires a browser to reproduce, use the Playwright MCP plugin instead of Bash:
|
|
87
|
+
|
|
88
|
+
1. **Navigate to the page**:
|
|
89
|
+
```
|
|
90
|
+
mcp__plugin_playwright_playwright__browser_navigate({ url: "http://localhost:[PORT]/[path]" })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. **Capture JS errors** (most useful for runtime exceptions):
|
|
94
|
+
```
|
|
95
|
+
mcp__plugin_playwright_playwright__browser_console_messages({ level: "error" })
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
3. **Inspect failed network requests** (useful for API/fetch failures):
|
|
99
|
+
```
|
|
100
|
+
mcp__plugin_playwright_playwright__browser_network_requests({ includeStatic: false })
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
4. **Screenshot the visual state** to document what the bug looks like:
|
|
104
|
+
```
|
|
105
|
+
mcp__plugin_playwright_playwright__browser_take_screenshot({ type: "png" })
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
5. **Capture accessibility snapshot** for structural/DOM-level inspection:
|
|
109
|
+
```
|
|
110
|
+
mcp__plugin_playwright_playwright__browser_snapshot()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use the console errors and network failures as primary evidence in Step 6 (Hypotheses).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
If you cannot reproduce (either path):
|
|
118
|
+
- Check environment differences
|
|
119
|
+
- Check for race conditions or timing issues
|
|
120
|
+
- Ask the user for exact reproduction steps
|
|
121
|
+
- Do NOT proceed to fixing without reproduction
|
|
122
|
+
|
|
123
|
+
### 5. Isolate the Problem
|
|
124
|
+
|
|
125
|
+
Read the relevant code, tracing the execution path:
|
|
126
|
+
|
|
127
|
+
1. Start at the error location (from stack trace)
|
|
128
|
+
2. Trace backward through the call chain
|
|
129
|
+
3. Identify the inputs and state at each step
|
|
130
|
+
4. Find where actual behavior diverges from expected
|
|
131
|
+
|
|
132
|
+
Use targeted searches:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Find related code
|
|
136
|
+
grep -r "functionName" src/
|
|
137
|
+
grep -r "ERROR_CODE" .
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 6. Form Hypotheses
|
|
141
|
+
|
|
142
|
+
Generate 2-3 ranked hypotheses based on your investigation:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
## Hypotheses
|
|
146
|
+
|
|
147
|
+
### H1: [Most likely] Description
|
|
148
|
+
- Evidence: what supports this
|
|
149
|
+
- Test: how to confirm or reject
|
|
150
|
+
|
|
151
|
+
### H2: [Alternative] Description
|
|
152
|
+
- Evidence: what supports this
|
|
153
|
+
- Test: how to confirm or reject
|
|
154
|
+
|
|
155
|
+
### H3: [Less likely] Description
|
|
156
|
+
- Evidence: what supports this
|
|
157
|
+
- Test: how to confirm or reject
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Log these to `tasks/findings.md` under a dated heading.
|
|
161
|
+
|
|
162
|
+
### 7. Test Hypotheses Systematically
|
|
163
|
+
|
|
164
|
+
For each hypothesis, starting with the most likely:
|
|
165
|
+
|
|
166
|
+
1. Design a specific diagnostic step (not a fix)
|
|
167
|
+
2. Execute it and observe the result
|
|
168
|
+
3. Update the hypothesis status: **CONFIRMED** / **REJECTED** / **PARTIAL**
|
|
169
|
+
|
|
170
|
+
Diagnostic steps might include:
|
|
171
|
+
- Adding a temporary log statement to check a value
|
|
172
|
+
- Running with different inputs
|
|
173
|
+
- Checking database state
|
|
174
|
+
- Inspecting environment variables
|
|
175
|
+
- Running a minimal reproduction
|
|
176
|
+
|
|
177
|
+
**Do NOT change production code during this phase.** Diagnostic changes only.
|
|
178
|
+
|
|
179
|
+
### 8. Update Findings
|
|
180
|
+
|
|
181
|
+
Update `tasks/findings.md` with results:
|
|
182
|
+
|
|
183
|
+
```markdown
|
|
184
|
+
### [Date] Bug: [brief description]
|
|
185
|
+
|
|
186
|
+
**Symptom:** [what the user reported]
|
|
187
|
+
**Root cause:** [H1/H2/H3 — which was confirmed]
|
|
188
|
+
**Evidence:** [what confirmed it]
|
|
189
|
+
**Status:** CONFIRMED → fix proposed
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 9. Propose Minimal Fix
|
|
193
|
+
|
|
194
|
+
Once a hypothesis is confirmed, propose the smallest possible fix:
|
|
195
|
+
|
|
196
|
+
- Change as few lines as possible
|
|
197
|
+
- Don't refactor surrounding code
|
|
198
|
+
- Don't add "while I'm here" improvements
|
|
199
|
+
- Explain why this fix addresses the root cause
|
|
200
|
+
|
|
201
|
+
Present the fix and **wait for user approval** before applying.
|
|
202
|
+
|
|
203
|
+
### 10. Verify Fix + Regression Check
|
|
204
|
+
|
|
205
|
+
After the fix is applied:
|
|
206
|
+
|
|
207
|
+
1. Re-run the original reproduction steps — bug should be gone
|
|
208
|
+
2. Run the full test suite — no new failures
|
|
209
|
+
3. If there was a related test, confirm it passes
|
|
210
|
+
4. If there was no test, suggest writing one (reference `/sk:write-tests`)
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Verify the specific fix
|
|
214
|
+
[reproduction command]
|
|
215
|
+
|
|
216
|
+
# Regression check
|
|
217
|
+
[test suite command]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 11. Document
|
|
221
|
+
|
|
222
|
+
**Root cause in `tasks/findings.md`:**
|
|
223
|
+
Update the entry from step 8 with the final resolution.
|
|
224
|
+
|
|
225
|
+
**Lesson in `tasks/lessons.md`** (only if the pattern could recur):
|
|
226
|
+
|
|
227
|
+
```markdown
|
|
228
|
+
### [Date] Lesson: [brief title]
|
|
229
|
+
**Bug:** [what happened]
|
|
230
|
+
**Root cause:** [why it happened]
|
|
231
|
+
**Prevention:** [how to avoid it in the future]
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Skip the lesson entry if it was a simple typo or one-off mistake.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Model Routing
|
|
239
|
+
|
|
240
|
+
Read `.shipkit/sk:config.json` from the project root if it exists.
|
|
241
|
+
|
|
242
|
+
- If `model_overrides["sk:debug"]` is set, use that model — it takes precedence.
|
|
243
|
+
- Otherwise use the `profile` field. Default: `balanced`.
|
|
244
|
+
|
|
245
|
+
| Profile | Model |
|
|
246
|
+
|---------|-------|
|
|
247
|
+
| `full-sail` | opus (inherit) |
|
|
248
|
+
| `quality` | opus (inherit) |
|
|
249
|
+
| `balanced` | sonnet |
|
|
250
|
+
| `budget` | sonnet |
|
|
251
|
+
|
|
252
|
+
> `opus` = inherit (uses the current session model). When spawning sub-agents via the Agent tool, pass `model: "<resolved-model>"`.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Main conductor for /debug skill: 11-step structured debugging."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
# Add lib to path
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).parent / "lib"))
|
|
11
|
+
|
|
12
|
+
from context_reader import ContextReader
|
|
13
|
+
from bug_gatherer import BugGatherer
|
|
14
|
+
from step_runner import StepRunner
|
|
15
|
+
from findings_writer import FindingsWriter
|
|
16
|
+
from lessons_writer import LessonsWriter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DebugStep(Enum):
|
|
20
|
+
GATHER_INFO = 1
|
|
21
|
+
READ_CONTEXT = 2
|
|
22
|
+
CHECK_CHANGES = 3
|
|
23
|
+
REPRODUCE = 4
|
|
24
|
+
ISOLATE = 5
|
|
25
|
+
FORM_HYPOTHESES = 6
|
|
26
|
+
TEST_HYPOTHESES = 7
|
|
27
|
+
UPDATE_FINDINGS = 8
|
|
28
|
+
PROPOSE_FIX = 9
|
|
29
|
+
VERIFY_FIX = 10
|
|
30
|
+
DOCUMENT = 11
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DebugConductor:
|
|
34
|
+
"""Manages the 11-step debugging workflow with gating."""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.context_reader = ContextReader()
|
|
38
|
+
self.bug_gatherer = BugGatherer()
|
|
39
|
+
self.step_runner = StepRunner()
|
|
40
|
+
self.findings_writer = FindingsWriter()
|
|
41
|
+
self.lessons_writer = LessonsWriter()
|
|
42
|
+
self.current_step = DebugStep.GATHER_INFO
|
|
43
|
+
self.state = {} # Accumulate findings across steps
|
|
44
|
+
|
|
45
|
+
def run(self):
|
|
46
|
+
"""Execute debugging workflow from step 1 to 11."""
|
|
47
|
+
print("\n" + "="*70)
|
|
48
|
+
print("🐛 DEBUG: Structured Bug Investigation")
|
|
49
|
+
print("="*70)
|
|
50
|
+
|
|
51
|
+
# Step 1: Gather user input
|
|
52
|
+
print("\n[STEP 1] Gathering Information")
|
|
53
|
+
bug_info = self.bug_gatherer.gather_from_user()
|
|
54
|
+
self.state['bug_info'] = bug_info
|
|
55
|
+
|
|
56
|
+
# Step 2: Read context files
|
|
57
|
+
print("\n[STEP 2] Reading Project Context")
|
|
58
|
+
context = self.context_reader.read_all()
|
|
59
|
+
self.state['context'] = context
|
|
60
|
+
self.state['active_lessons'] = context['lessons']
|
|
61
|
+
|
|
62
|
+
# Apply lessons as constraints
|
|
63
|
+
self._apply_lessons_as_constraints(context['lessons'])
|
|
64
|
+
|
|
65
|
+
# Steps 3-11: Run sequentially with gating
|
|
66
|
+
for step_num in range(3, 12):
|
|
67
|
+
step = DebugStep(step_num)
|
|
68
|
+
self.current_step = step
|
|
69
|
+
|
|
70
|
+
print(f"\n{'='*70}")
|
|
71
|
+
print(f"[STEP {step_num}] {step.name.replace('_', ' ').title()}")
|
|
72
|
+
print(f"{'='*70}\n")
|
|
73
|
+
|
|
74
|
+
# Check if we can proceed
|
|
75
|
+
if not self._can_proceed(step):
|
|
76
|
+
print(f"\n⛔ BLOCKED: {self._gate_reason(step)}")
|
|
77
|
+
print("\nRun /debug again to continue from this step.")
|
|
78
|
+
self._save_progress()
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# Run step and update state
|
|
82
|
+
result = self.step_runner.execute(step, self.state)
|
|
83
|
+
self.state.update(result)
|
|
84
|
+
|
|
85
|
+
# Step 8: Write findings
|
|
86
|
+
if step_num == 8:
|
|
87
|
+
self.findings_writer.write_findings(self.state)
|
|
88
|
+
|
|
89
|
+
# Step 11: Write lesson (conditional)
|
|
90
|
+
if step_num == 11:
|
|
91
|
+
self.lessons_writer.write_lesson(self.state)
|
|
92
|
+
|
|
93
|
+
if step_num < 11:
|
|
94
|
+
proceed = input(f"\n✅ Step {step_num} complete. Continue to step {step_num + 1}? (y/n): ").strip().lower()
|
|
95
|
+
if proceed not in ['y', 'yes', '']:
|
|
96
|
+
print(f"Pausing after step {step_num}. Run /debug again to continue.")
|
|
97
|
+
self._save_progress()
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
print("\n" + "="*70)
|
|
101
|
+
print("🎉 DEBUGGING WORKFLOW COMPLETE")
|
|
102
|
+
print("="*70)
|
|
103
|
+
print(f"\n✅ Findings written to: tasks/findings.md")
|
|
104
|
+
if self.state.get('lesson_created'):
|
|
105
|
+
print(f"✅ New lesson written to: tasks/lessons.md")
|
|
106
|
+
print("\n📝 Next: Run /commit to create a fix commit")
|
|
107
|
+
print(" Or: Run /write-tests to add test coverage")
|
|
108
|
+
|
|
109
|
+
def _apply_lessons_as_constraints(self, lessons: list):
|
|
110
|
+
"""Apply all active lessons as standing constraints during investigation."""
|
|
111
|
+
if not lessons:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
print("\n📚 APPLYING STANDING LESSONS:")
|
|
115
|
+
for lesson in lessons:
|
|
116
|
+
print(f" - [{lesson.get('date', '?')}] {lesson.get('title', 'Unknown')}")
|
|
117
|
+
print(f" Prevention: {lesson.get('prevention', 'N/A')}")
|
|
118
|
+
print()
|
|
119
|
+
|
|
120
|
+
def _can_proceed(self, step: DebugStep) -> bool:
|
|
121
|
+
"""Check if we can proceed to/through this step."""
|
|
122
|
+
# Step 4: Must eventually reproduce to proceed
|
|
123
|
+
if step == DebugStep.REPRODUCE:
|
|
124
|
+
return True # Can always try to reproduce
|
|
125
|
+
|
|
126
|
+
# Step 5+: Must have reproduced
|
|
127
|
+
if step.value > 4:
|
|
128
|
+
return self.state.get('reproduced', False)
|
|
129
|
+
|
|
130
|
+
# Step 6: Can form hypotheses
|
|
131
|
+
if step == DebugStep.FORM_HYPOTHESES:
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
# Step 7+: Must have hypotheses
|
|
135
|
+
if step.value > 6:
|
|
136
|
+
return len(self.state.get('hypotheses', [])) > 0
|
|
137
|
+
|
|
138
|
+
# Step 8+: Must have tested at least one
|
|
139
|
+
if step.value > 7:
|
|
140
|
+
hypotheses = self.state.get('hypotheses', [])
|
|
141
|
+
return any(h.get('status') in ['CONFIRMED', 'REJECTED', 'PARTIAL']
|
|
142
|
+
for h in hypotheses)
|
|
143
|
+
|
|
144
|
+
# Step 9+: Must have confirmed hypothesis
|
|
145
|
+
if step.value > 8:
|
|
146
|
+
hypotheses = self.state.get('hypotheses', [])
|
|
147
|
+
return any(h.get('status') == 'CONFIRMED' for h in hypotheses)
|
|
148
|
+
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
def _gate_reason(self, step: DebugStep) -> str:
|
|
152
|
+
"""Explain why we can't proceed."""
|
|
153
|
+
if step.value > 4 and not self.state.get('reproduced'):
|
|
154
|
+
return "Cannot proceed without reproducing the bug first."
|
|
155
|
+
if step.value > 7 and not self.state.get('hypotheses'):
|
|
156
|
+
return "No hypotheses formed yet."
|
|
157
|
+
if step.value > 8 and not any(
|
|
158
|
+
h.get('status') == 'CONFIRMED'
|
|
159
|
+
for h in self.state.get('hypotheses', [])
|
|
160
|
+
):
|
|
161
|
+
return "No confirmed hypothesis yet—cannot propose fix."
|
|
162
|
+
return "Precondition not met for this step."
|
|
163
|
+
|
|
164
|
+
def _save_progress(self):
|
|
165
|
+
"""Save current state for resumption (optional)."""
|
|
166
|
+
# Could implement checkpoint system here
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main():
|
|
171
|
+
"""Entry point for /debug skill."""
|
|
172
|
+
conductor = DebugConductor()
|
|
173
|
+
conductor.run()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Debug skill library modules."""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Gather bug information from user input."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BugGatherer:
|
|
7
|
+
"""Interactive bug information gathering."""
|
|
8
|
+
|
|
9
|
+
def gather_from_user(self) -> Dict:
|
|
10
|
+
"""Prompt user for bug details."""
|
|
11
|
+
print("\nLet's gather information about the bug.\n")
|
|
12
|
+
|
|
13
|
+
bug_info = {
|
|
14
|
+
'description': self._prompt("Brief description of what's wrong"),
|
|
15
|
+
'error_message': self._prompt("Exact error message (if any)", optional=True),
|
|
16
|
+
'stack_trace': self._prompt_multiline("Stack trace or logs (if any)", optional=True),
|
|
17
|
+
'expected_behavior': self._prompt("What SHOULD happen"),
|
|
18
|
+
'actual_behavior': self._prompt("What ACTUALLY happens"),
|
|
19
|
+
'trigger_conditions': self._prompt("When/how does it happen? (Always, sometimes, specific conditions?)"),
|
|
20
|
+
'recent_changes': self._prompt("Did this work before? What changed?", optional=True),
|
|
21
|
+
'environment': self._prompt("Environment (local, staging, prod)?"),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return bug_info
|
|
25
|
+
|
|
26
|
+
def _prompt(self, question: str, optional: bool = False) -> str:
|
|
27
|
+
"""Prompt user for input."""
|
|
28
|
+
marker = " (optional)" if optional else " (required)"
|
|
29
|
+
print(f"❓ {question}{marker}:")
|
|
30
|
+
answer = input("> ").strip()
|
|
31
|
+
|
|
32
|
+
if not answer and not optional:
|
|
33
|
+
print(" ⚠️ This is required. Please try again.")
|
|
34
|
+
return self._prompt(question, optional)
|
|
35
|
+
|
|
36
|
+
return answer
|
|
37
|
+
|
|
38
|
+
def _prompt_multiline(self, question: str, optional: bool = False) -> str:
|
|
39
|
+
"""Prompt for multiline input (ends with empty line)."""
|
|
40
|
+
marker = " (optional, blank line to end)" if optional else " (blank line to end)"
|
|
41
|
+
print(f"❓ {question}{marker}:")
|
|
42
|
+
|
|
43
|
+
lines = []
|
|
44
|
+
while True:
|
|
45
|
+
line = input("> ")
|
|
46
|
+
if not line:
|
|
47
|
+
break
|
|
48
|
+
lines.append(line)
|
|
49
|
+
|
|
50
|
+
result = '\n'.join(lines)
|
|
51
|
+
if not result and not optional:
|
|
52
|
+
print(" ⚠️ This is required. Please try again.")
|
|
53
|
+
return self._prompt_multiline(question, optional)
|
|
54
|
+
|
|
55
|
+
return result
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Read and parse project context files (CLAUDE.md, lessons, findings, progress)."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContextReader:
|
|
9
|
+
"""Safe reader for CLAUDE.md, lessons.md, findings.md, progress.md."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, project_root: Path = None):
|
|
12
|
+
self.root = project_root or Path.cwd()
|
|
13
|
+
|
|
14
|
+
def read_all(self) -> Dict:
|
|
15
|
+
"""Read all context files and return structured data."""
|
|
16
|
+
return {
|
|
17
|
+
'claude_md': self._read_claude_md(),
|
|
18
|
+
'lessons': self._read_lessons(),
|
|
19
|
+
'findings': self._read_findings(),
|
|
20
|
+
'progress': self._read_progress(),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def _read_claude_md(self) -> Dict:
|
|
24
|
+
"""Parse CLAUDE.md for tech stack and conventions."""
|
|
25
|
+
claude_path = self.root / 'CLAUDE.md'
|
|
26
|
+
if not claude_path.exists():
|
|
27
|
+
return {'exists': False}
|
|
28
|
+
|
|
29
|
+
content = claude_path.read_text()
|
|
30
|
+
return {
|
|
31
|
+
'exists': True,
|
|
32
|
+
'has_content': len(content) > 100,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def _read_lessons(self) -> List[Dict]:
|
|
36
|
+
"""Parse tasks/lessons.md and extract active lessons."""
|
|
37
|
+
lessons_path = self.root / 'tasks' / 'lessons.md'
|
|
38
|
+
if not lessons_path.exists():
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
content = lessons_path.read_text()
|
|
42
|
+
lessons = []
|
|
43
|
+
|
|
44
|
+
# Parse markdown format:
|
|
45
|
+
# ### [YYYY-MM-DD] Brief title
|
|
46
|
+
# **Bug:** ...
|
|
47
|
+
# **Root cause:** ...
|
|
48
|
+
# **Prevention:** ...
|
|
49
|
+
|
|
50
|
+
# Split by ### headers
|
|
51
|
+
sections = re.split(r'### ', content)
|
|
52
|
+
|
|
53
|
+
for section in sections[1:]: # Skip header
|
|
54
|
+
lines = section.strip().split('\n')
|
|
55
|
+
if not lines:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# First line: [YYYY-MM-DD] Title
|
|
59
|
+
first = lines[0]
|
|
60
|
+
match = re.match(r'\[(\d{4}-\d{2}-\d{2})\]\s*(.*)', first)
|
|
61
|
+
if not match:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
date, title = match.groups()
|
|
65
|
+
|
|
66
|
+
# Extract fields
|
|
67
|
+
full_text = '\n'.join(lines)
|
|
68
|
+
bug = self._extract_field(full_text, 'Bug')
|
|
69
|
+
root_cause = self._extract_field(full_text, 'Root cause')
|
|
70
|
+
prevention = self._extract_field(full_text, 'Prevention')
|
|
71
|
+
|
|
72
|
+
lesson = {
|
|
73
|
+
'date': date,
|
|
74
|
+
'title': title,
|
|
75
|
+
'bug': bug,
|
|
76
|
+
'root_cause': root_cause,
|
|
77
|
+
'prevention': prevention,
|
|
78
|
+
}
|
|
79
|
+
lessons.append(lesson)
|
|
80
|
+
|
|
81
|
+
return lessons
|
|
82
|
+
|
|
83
|
+
def _read_findings(self) -> List[Dict]:
|
|
84
|
+
"""Parse tasks/findings.md and extract past investigations."""
|
|
85
|
+
findings_path = self.root / 'tasks' / 'findings.md'
|
|
86
|
+
if not findings_path.exists():
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
content = findings_path.read_text()
|
|
90
|
+
findings = []
|
|
91
|
+
|
|
92
|
+
# Parse markdown format:
|
|
93
|
+
# ## YYYY-MM-DD HH:MM — Bug: Description
|
|
94
|
+
# **Symptom:** ...
|
|
95
|
+
# **Root cause:** ...
|
|
96
|
+
|
|
97
|
+
sections = re.split(r'## (\d{4}-\d{2}-\d{2})', content)
|
|
98
|
+
|
|
99
|
+
for i in range(1, len(sections), 2):
|
|
100
|
+
date = sections[i]
|
|
101
|
+
text = sections[i + 1] if i + 1 < len(sections) else ''
|
|
102
|
+
|
|
103
|
+
# Extract description from "— Bug: ..."
|
|
104
|
+
desc_match = re.search(r'—\s*Bug:\s*(.+)', text)
|
|
105
|
+
description = desc_match.group(1).strip() if desc_match else ''
|
|
106
|
+
|
|
107
|
+
symptom = self._extract_field(text, 'Symptom')
|
|
108
|
+
root_cause = self._extract_field(text, 'Root cause')
|
|
109
|
+
status = self._extract_field(text, 'Status')
|
|
110
|
+
|
|
111
|
+
findings.append({
|
|
112
|
+
'date': date,
|
|
113
|
+
'description': description,
|
|
114
|
+
'symptom': symptom,
|
|
115
|
+
'root_cause': root_cause,
|
|
116
|
+
'status': status,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return findings
|
|
120
|
+
|
|
121
|
+
def _read_progress(self) -> Dict:
|
|
122
|
+
"""Parse tasks/progress.md for recent errors."""
|
|
123
|
+
progress_path = self.root / 'tasks' / 'progress.md'
|
|
124
|
+
if not progress_path.exists():
|
|
125
|
+
return {'exists': False}
|
|
126
|
+
|
|
127
|
+
content = progress_path.read_text()
|
|
128
|
+
return {
|
|
129
|
+
'exists': True,
|
|
130
|
+
'has_error_log': '## Error Log' in content or '### Error' in content,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def _extract_field(self, text: str, field_name: str) -> str:
|
|
134
|
+
"""Extract field from markdown (e.g., **Bug:** content)."""
|
|
135
|
+
pattern = rf'\*\*{field_name}:\*\*\s*(.+?)(?=\n\*\*|\n###|$)'
|
|
136
|
+
match = re.search(pattern, text, re.DOTALL)
|
|
137
|
+
if match:
|
|
138
|
+
return match.group(1).strip()
|
|
139
|
+
return ''
|