@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.
Files changed (117) hide show
  1. package/README.md +321 -0
  2. package/bin/shipkit.js +146 -0
  3. package/commands/sk/brainstorm.md +63 -0
  4. package/commands/sk/branch.md +35 -0
  5. package/commands/sk/config.md +96 -0
  6. package/commands/sk/execute-plan.md +85 -0
  7. package/commands/sk/features.md +238 -0
  8. package/commands/sk/finish-feature.md +154 -0
  9. package/commands/sk/help.md +103 -0
  10. package/commands/sk/hotfix.md +61 -0
  11. package/commands/sk/plan.md +30 -0
  12. package/commands/sk/release.md +72 -0
  13. package/commands/sk/security-check.md +188 -0
  14. package/commands/sk/set-profile.md +71 -0
  15. package/commands/sk/status.md +25 -0
  16. package/commands/sk/update-task.md +35 -0
  17. package/commands/sk/write-plan.md +72 -0
  18. package/package.json +23 -0
  19. package/skills/sk:accessibility/LICENSE.txt +177 -0
  20. package/skills/sk:accessibility/SKILL.md +150 -0
  21. package/skills/sk:api-design/LICENSE.txt +177 -0
  22. package/skills/sk:api-design/SKILL.md +158 -0
  23. package/skills/sk:brainstorming/SKILL.md +124 -0
  24. package/skills/sk:debug/SKILL.md +252 -0
  25. package/skills/sk:debug/debug_conductor.py +177 -0
  26. package/skills/sk:debug/lib/__init__.py +1 -0
  27. package/skills/sk:debug/lib/bug_gatherer.py +55 -0
  28. package/skills/sk:debug/lib/context_reader.py +139 -0
  29. package/skills/sk:debug/lib/findings_writer.py +76 -0
  30. package/skills/sk:debug/lib/lessons_writer.py +165 -0
  31. package/skills/sk:debug/lib/step_runner.py +326 -0
  32. package/skills/sk:features/SKILL.md +238 -0
  33. package/skills/sk:frontend-design/LICENSE.txt +177 -0
  34. package/skills/sk:frontend-design/SKILL.md +191 -0
  35. package/skills/sk:laravel-init/SKILL.md +37 -0
  36. package/skills/sk:laravel-new/SKILL.md +68 -0
  37. package/skills/sk:lint/SKILL.md +113 -0
  38. package/skills/sk:perf/LICENSE.txt +177 -0
  39. package/skills/sk:perf/SKILL.md +188 -0
  40. package/skills/sk:release/SKILL.md +113 -0
  41. package/skills/sk:release/references/android-checklist.md +269 -0
  42. package/skills/sk:release/references/ios-checklist.md +339 -0
  43. package/skills/sk:release/release.sh +378 -0
  44. package/skills/sk:review/SKILL.md +346 -0
  45. package/skills/sk:review/references/security-checklist.md +223 -0
  46. package/skills/sk:schema-migrate/SKILL.md +125 -0
  47. package/skills/sk:schema-migrate/orms/drizzle.md +546 -0
  48. package/skills/sk:schema-migrate/orms/laravel.md +367 -0
  49. package/skills/sk:schema-migrate/orms/prisma.md +357 -0
  50. package/skills/sk:schema-migrate/orms/rails.md +351 -0
  51. package/skills/sk:schema-migrate/orms/sqlalchemy.md +385 -0
  52. package/skills/sk:schema-migrate/references/detection.md +110 -0
  53. package/skills/sk:setup-claude/SKILL.md +365 -0
  54. package/skills/sk:setup-claude/references/detection.md +6 -0
  55. package/skills/sk:setup-claude/references/templates.md +11 -0
  56. package/skills/sk:setup-claude/scripts/apply_setup_claude.py +443 -0
  57. package/skills/sk:setup-claude/scripts/detect_arch_changes.py +437 -0
  58. package/skills/sk:setup-claude/templates/.claude/docs/arch-changelog-guide.md.template +6 -0
  59. package/skills/sk:setup-claude/templates/.claude/docs/changelog-guide.md.template +12 -0
  60. package/skills/sk:setup-claude/templates/CHANGELOG.md.template +21 -0
  61. package/skills/sk:setup-claude/templates/CLAUDE.md.template +299 -0
  62. package/skills/sk:setup-claude/templates/arch-changelog-guide.md.template +3 -0
  63. package/skills/sk:setup-claude/templates/changelog-guide.md.template +3 -0
  64. package/skills/sk:setup-claude/templates/commands/brainstorm.md.template +74 -0
  65. package/skills/sk:setup-claude/templates/commands/execute-plan.md.template +57 -0
  66. package/skills/sk:setup-claude/templates/commands/features.md.template +238 -0
  67. package/skills/sk:setup-claude/templates/commands/finish-feature.md.template +155 -0
  68. package/skills/sk:setup-claude/templates/commands/plan.md.template +30 -0
  69. package/skills/sk:setup-claude/templates/commands/re-setup.md.template +38 -0
  70. package/skills/sk:setup-claude/templates/commands/release.md.template +74 -0
  71. package/skills/sk:setup-claude/templates/commands/security-check.md.template +172 -0
  72. package/skills/sk:setup-claude/templates/commands/status.md.template +17 -0
  73. package/skills/sk:setup-claude/templates/commands/write-plan.md.template +34 -0
  74. package/skills/sk:setup-claude/templates/finish-feature.md.template +3 -0
  75. package/skills/sk:setup-claude/templates/plan.md.template +3 -0
  76. package/skills/sk:setup-claude/templates/status.md.template +3 -0
  77. package/skills/sk:setup-claude/templates/tasks/findings.md.template +19 -0
  78. package/skills/sk:setup-claude/templates/tasks/lessons.md.template +26 -0
  79. package/skills/sk:setup-claude/templates/tasks/progress.md.template +20 -0
  80. package/skills/sk:setup-claude/templates/tasks/security-findings.md.template +5 -0
  81. package/skills/sk:setup-claude/templates/tasks/todo.md.template +26 -0
  82. package/skills/sk:setup-claude/templates/tasks/workflow-status.md.template +31 -0
  83. package/skills/sk:setup-claude/templates/tasks-findings.md.template +3 -0
  84. package/skills/sk:setup-claude/templates/tasks-lessons.md.template +3 -0
  85. package/skills/sk:setup-claude/templates/tasks-progress.md.template +3 -0
  86. package/skills/sk:setup-claude/templates/tasks-todo.md.template +3 -0
  87. package/skills/sk:setup-claude/tests/test_apply_setup_claude.py +193 -0
  88. package/skills/sk:setup-optimizer/SKILL.md +184 -0
  89. package/skills/sk:setup-optimizer/lib/__init__.py +24 -0
  90. package/skills/sk:setup-optimizer/lib/detect.py +205 -0
  91. package/skills/sk:setup-optimizer/lib/discover.py +221 -0
  92. package/skills/sk:setup-optimizer/lib/enrich.py +163 -0
  93. package/skills/sk:setup-optimizer/lib/merge.py +277 -0
  94. package/skills/sk:setup-optimizer/lib/sidecar.py +129 -0
  95. package/skills/sk:setup-optimizer/optimize_claude.py +174 -0
  96. package/skills/sk:setup-optimizer/templates/CLAUDE.md.template +105 -0
  97. package/skills/sk:skill-creator/LICENSE.txt +202 -0
  98. package/skills/sk:skill-creator/SKILL.md +479 -0
  99. package/skills/sk:skill-creator/agents/analyzer.md +274 -0
  100. package/skills/sk:skill-creator/agents/comparator.md +202 -0
  101. package/skills/sk:skill-creator/agents/grader.md +223 -0
  102. package/skills/sk:skill-creator/assets/eval_review.html +146 -0
  103. package/skills/sk:skill-creator/eval-viewer/generate_review.py +471 -0
  104. package/skills/sk:skill-creator/eval-viewer/viewer.html +1325 -0
  105. package/skills/sk:skill-creator/references/schemas.md +430 -0
  106. package/skills/sk:skill-creator/scripts/aggregate_benchmark.py +401 -0
  107. package/skills/sk:skill-creator/scripts/generate_report.py +326 -0
  108. package/skills/sk:skill-creator/scripts/improve_description.py +248 -0
  109. package/skills/sk:skill-creator/scripts/package_skill.py +136 -0
  110. package/skills/sk:skill-creator/scripts/quick_validate.py +103 -0
  111. package/skills/sk:skill-creator/scripts/run_eval.py +310 -0
  112. package/skills/sk:skill-creator/scripts/run_loop.py +332 -0
  113. package/skills/sk:skill-creator/scripts/utils.py +47 -0
  114. package/skills/sk:smart-commit/SKILL.md +175 -0
  115. package/skills/sk:test/SKILL.md +171 -0
  116. package/skills/sk:write-tests/SKILL.md +195 -0
  117. 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 ''