@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,76 @@
1
+ """Safe writer for tasks/findings.md - appends findings without overwriting."""
2
+
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+ from typing import Dict
6
+
7
+
8
+ class FindingsWriter:
9
+ """Append findings to tasks/findings.md without overwriting."""
10
+
11
+ def __init__(self, root: Path = None):
12
+ self.root = root or Path.cwd()
13
+ self.findings_path = self.root / 'tasks' / 'findings.md'
14
+
15
+ def write_findings(self, state: Dict) -> bool:
16
+ """Write findings entry from debug state."""
17
+ bug_info = state.get('bug_info', {})
18
+ hypotheses = state.get('hypotheses', [])
19
+ root_cause = state.get('root_cause_confirmed', '')
20
+
21
+ now = datetime.now().strftime("%Y-%m-%d %H:%M")
22
+ bug_summary = bug_info.get('description', 'Unknown')
23
+
24
+ # Build the entry
25
+ entry = f"\n## {now} — Bug: {bug_summary}\n\n"
26
+ entry += f"**Symptom:** {bug_info.get('actual_behavior', 'Unknown')}\n\n"
27
+ entry += f"**Root cause:** {root_cause}\n\n"
28
+
29
+ # Add hypotheses tested
30
+ if hypotheses:
31
+ entry += "**Hypotheses tested:**\n"
32
+ for h in hypotheses:
33
+ status = h.get('status', 'UNKNOWN')
34
+ entry += f"- H{h['rank']}: {h['description']} → **{status}**\n"
35
+ if h.get('test_results'):
36
+ entry += f" Result: {h['test_results']}\n"
37
+ entry += "\n"
38
+
39
+ entry += f"**Status:** Investigation complete\n"
40
+
41
+ # Write to file
42
+ try:
43
+ self._ensure_file_exists()
44
+ self._append_entry(entry)
45
+ print(f"\n✅ Findings written to {self.findings_path}")
46
+ return True
47
+ except Exception as e:
48
+ print(f"\n❌ Error writing findings: {e}")
49
+ return False
50
+
51
+ def _ensure_file_exists(self):
52
+ """Create tasks/findings.md if missing with template."""
53
+ if not self.findings_path.exists():
54
+ self.findings_path.parent.mkdir(parents=True, exist_ok=True)
55
+ template = """# Bug Findings
56
+
57
+ Investigations of bugs found during development.
58
+
59
+ ## Entry Format
60
+
61
+ Each entry includes:
62
+ - **Date and summary** — When and what
63
+ - **Symptom** — What the user reported
64
+ - **Root cause** — What was actually wrong
65
+ - **Hypotheses tested** — What we checked
66
+ - **Status** — Investigation stage
67
+
68
+ ---
69
+
70
+ """
71
+ self.findings_path.write_text(template)
72
+
73
+ def _append_entry(self, entry: str):
74
+ """Append entry to findings.md without overwriting."""
75
+ current = self.findings_path.read_text()
76
+ self.findings_path.write_text(current + entry + "\n")
@@ -0,0 +1,165 @@
1
+ """Safe writer for tasks/lessons.md - appends lessons only when pattern detected."""
2
+
3
+ from pathlib import Path
4
+ from datetime import datetime
5
+ from typing import Dict
6
+
7
+
8
+ class LessonsWriter:
9
+ """Append lessons to tasks/lessons.md only when recurrence risk detected."""
10
+
11
+ def __init__(self, root: Path = None):
12
+ self.root = root or Path.cwd()
13
+ self.lessons_path = self.root / 'tasks' / 'lessons.md'
14
+
15
+ def should_create_lesson(self, state: Dict) -> bool:
16
+ """Determine if this bug warrants a lesson (recurrence risk)."""
17
+ root_cause = state.get('root_cause_confirmed', '')
18
+
19
+ if not root_cause:
20
+ return False
21
+
22
+ # Skip lessons for one-off issues:
23
+ skip_patterns = [
24
+ 'typo', 'spelling', 'copy-paste',
25
+ 'environment variable not set',
26
+ 'hardware failure', 'network timeout',
27
+ 'race condition (timing)',
28
+ 'one-off',
29
+ ]
30
+
31
+ for pattern in skip_patterns:
32
+ if pattern.lower() in root_cause.lower():
33
+ return False
34
+
35
+ # Create lesson for systematic issues that could recur:
36
+ # - Logic errors
37
+ # - Missing validation
38
+ # - Architectural oversights
39
+ # - Performance issues
40
+ # - Missing error handling
41
+
42
+ return True
43
+
44
+ def write_lesson(self, state: Dict) -> bool:
45
+ """Write lesson entry from debug state."""
46
+ if not self.should_create_lesson(state):
47
+ print("\n⏭️ Skipping lesson (one-off issue, pattern unlikely to recur)")
48
+ return False
49
+
50
+ bug_info = state.get('bug_info', {})
51
+ root_cause = state.get('root_cause_confirmed', '')
52
+
53
+ date = datetime.now().strftime("%Y-%m-%d")
54
+ bug_symptom = bug_info.get('description', 'Unknown')
55
+ prevention = self._derive_prevention(state)
56
+
57
+ # Build the lesson entry
58
+ entry = f"\n### [{date}] {self._generate_title(root_cause)}\n\n"
59
+ entry += f"**Bug:** {bug_symptom}\n\n"
60
+ entry += f"**Root cause:** {root_cause}\n\n"
61
+ entry += f"**Prevention:** {prevention}\n"
62
+
63
+ # Write to file
64
+ try:
65
+ self._ensure_file_exists()
66
+ self._append_lesson(entry)
67
+ print(f"\n✅ New lesson added to {self.lessons_path}")
68
+ return True
69
+ except Exception as e:
70
+ print(f"\n❌ Error writing lesson: {e}")
71
+ return False
72
+
73
+ def _ensure_file_exists(self):
74
+ """Create tasks/lessons.md if missing with template."""
75
+ if not self.lessons_path.exists():
76
+ self.lessons_path.parent.mkdir(parents=True, exist_ok=True)
77
+ template = """# Lessons Learned
78
+
79
+ Patterns that caused bugs and prevention rules to avoid them in the future.
80
+
81
+ ## Entry Format
82
+
83
+ Each lesson includes:
84
+ - **[Date] Title** — When and what we learned
85
+ - **Bug** — What went wrong
86
+ - **Root cause** — Why it happened
87
+ - **Prevention** — How to avoid it next time
88
+
89
+ Lessons are applied as standing constraints by:
90
+ - `/brainstorm` — Applies to requirement exploration
91
+ - `/write-plan` — Applies to plan creation
92
+ - `/execute-plan` — Applies as implementation constraints
93
+ - `/write-tests` — Applies to test patterns
94
+ - `/review` — Applies to code review checks
95
+ - `/finish-feature` — Applies as merge gate checks
96
+
97
+ ---
98
+
99
+ ## Active Lessons
100
+
101
+ """
102
+ self.lessons_path.write_text(template)
103
+
104
+ def _append_lesson(self, entry: str):
105
+ """Append lesson to lessons.md in the Active Lessons section."""
106
+ content = self.lessons_path.read_text()
107
+
108
+ # Find "## Active Lessons" marker
109
+ marker = "## Active Lessons\n"
110
+ if marker in content:
111
+ # Insert after the marker
112
+ parts = content.split(marker)
113
+ self.lessons_path.write_text(
114
+ parts[0] + marker + "\n" + entry + "\n" + parts[1]
115
+ )
116
+ else:
117
+ # Fallback: append at end
118
+ self.lessons_path.write_text(content + entry + "\n")
119
+
120
+ def _derive_prevention(self, state: Dict) -> str:
121
+ """Suggest prevention rule from root cause."""
122
+ root_cause = state.get('root_cause_confirmed', '').lower()
123
+
124
+ # Simple heuristics to suggest prevention
125
+ if 'validation' in root_cause:
126
+ return "Always validate input at system boundaries (user input, API calls, file uploads) before processing. Check type, range, format, and size."
127
+
128
+ elif 'race condition' in root_cause:
129
+ return "Use locks or atomic operations for concurrent access to shared state. Add tests for concurrent scenarios."
130
+
131
+ elif 'type' in root_cause or 'coercion' in root_cause:
132
+ return "Enable and enforce strict type checking. Avoid implicit type coercion. Use type hints/annotations."
133
+
134
+ elif 'error handling' in root_cause or 'exception' in root_cause:
135
+ return "Always handle exceptions explicitly. Don't silently catch and ignore errors. Log unexpected conditions."
136
+
137
+ elif 'null' in root_cause or 'undefined' in root_cause:
138
+ return "Check for null/undefined values before accessing properties. Use optional chaining or default values."
139
+
140
+ elif 'memory' in root_cause or 'resource' in root_cause:
141
+ return "Always clean up resources (close files, connections, listeners). Use try-finally or context managers."
142
+
143
+ elif 'performance' in root_cause or 'slow' in root_cause:
144
+ return "Profile before optimizing. Add monitoring and alerts for performance regressions. Include performance tests."
145
+
146
+ elif 'security' in root_cause or 'injection' in root_cause:
147
+ return "Apply input validation and output encoding. Use parameterized queries. Never trust user input. Review OWASP top 10."
148
+
149
+ else:
150
+ # Generic prevention
151
+ return f"Pattern identified: {root_cause[:60]}... Review root cause and implement safeguard in code review checklist."
152
+
153
+ def _generate_title(self, root_cause: str) -> str:
154
+ """Generate lesson title from root cause."""
155
+ # Try to create a concise title
156
+ title = root_cause
157
+
158
+ # Limit length
159
+ if len(title) > 60:
160
+ title = title[:57] + "..."
161
+
162
+ # Capitalize first letter
163
+ title = title[0].upper() + title[1:] if title else "Bug Lesson"
164
+
165
+ return title
@@ -0,0 +1,326 @@
1
+ """Execute individual debug steps (3-11)."""
2
+
3
+ import subprocess
4
+ from typing import Dict
5
+ from pathlib import Path
6
+ import sys
7
+
8
+
9
+ class StepRunner:
10
+ """Execute each debugging step."""
11
+
12
+ def execute(self, step, state: Dict) -> Dict:
13
+ """Execute a specific step and return results."""
14
+ step_num = step.value
15
+
16
+ if step_num == 3:
17
+ return self._step_3_check_changes()
18
+ elif step_num == 4:
19
+ return self._step_4_reproduce(state)
20
+ elif step_num == 5:
21
+ return self._step_5_isolate(state)
22
+ elif step_num == 6:
23
+ return self._step_6_form_hypotheses(state)
24
+ elif step_num == 7:
25
+ return self._step_7_test_hypotheses(state)
26
+ elif step_num == 8:
27
+ return self._step_8_update_findings(state)
28
+ elif step_num == 9:
29
+ return self._step_9_propose_fix(state)
30
+ elif step_num == 10:
31
+ return self._step_10_verify_fix(state)
32
+ elif step_num == 11:
33
+ return self._step_11_document(state)
34
+
35
+ return {}
36
+
37
+ def _step_3_check_changes(self) -> Dict:
38
+ """Check recent git changes."""
39
+ print("Checking recent git commits and diffs...\n")
40
+
41
+ try:
42
+ # Recent commits
43
+ print("📋 Recent commits:")
44
+ result = subprocess.run(
45
+ "git log --oneline -10",
46
+ shell=True,
47
+ capture_output=True,
48
+ text=True
49
+ )
50
+ print(result.stdout)
51
+
52
+ # Recent diffs
53
+ print("\n📋 Recent file changes:")
54
+ result = subprocess.run(
55
+ "git diff HEAD~3 --stat",
56
+ shell=True,
57
+ capture_output=True,
58
+ text=True
59
+ )
60
+ print(result.stdout)
61
+
62
+ return {'changes_reviewed': True}
63
+
64
+ except Exception as e:
65
+ print(f"⚠️ Could not check git history: {e}")
66
+ return {'changes_reviewed': False}
67
+
68
+ def _step_4_reproduce(self, state: Dict) -> Dict:
69
+ """Reproduce the bug (CLI or browser)."""
70
+ bug_info = state['bug_info']
71
+
72
+ print("Now let's reproduce the bug to confirm it exists.\n")
73
+
74
+ # Detect if likely browser bug
75
+ is_browser_bug = self._detect_browser_bug(bug_info)
76
+
77
+ if is_browser_bug:
78
+ return self._reproduce_browser_bug(state)
79
+ else:
80
+ return self._reproduce_cli_bug(state)
81
+
82
+ def _detect_browser_bug(self, bug_info: Dict) -> bool:
83
+ """Heuristic: is this a browser/UI bug?"""
84
+ keywords = [
85
+ 'visual', 'browser', 'javascript', 'console', 'render',
86
+ 'DOM', 'click', 'button', 'form', 'UI', 'page', 'screen'
87
+ ]
88
+ text = (
89
+ bug_info.get('description', '') + ' ' +
90
+ bug_info.get('error_message', '')
91
+ ).lower()
92
+ return any(kw in text for kw in keywords)
93
+
94
+ def _reproduce_cli_bug(self, state: Dict) -> Dict:
95
+ """Reproduce CLI/server bug via bash command."""
96
+ print("📝 Provide the bash command that triggers the bug:")
97
+ print(" (Leave blank if you'd rather describe manually)\n")
98
+
99
+ command = input("> ").strip()
100
+
101
+ if not command:
102
+ print("\n⚠️ Cannot reproduce without a command.")
103
+ print(" Please provide the exact command/steps to trigger the bug.")
104
+ return {'reproduced': False}
105
+
106
+ print(f"\n🔧 Running: {command}\n")
107
+
108
+ try:
109
+ result = subprocess.run(
110
+ command,
111
+ shell=True,
112
+ capture_output=True,
113
+ text=True,
114
+ timeout=30
115
+ )
116
+
117
+ output = result.stdout + result.stderr
118
+ print(output)
119
+
120
+ print("\n✅ Bug reproduced successfully!")
121
+
122
+ return {
123
+ 'reproduced': True,
124
+ 'bug_type': 'cli',
125
+ 'reproduction_command': command,
126
+ 'reproduction_output': output[:500], # First 500 chars
127
+ 'evidence_collected': [f"Reproduced with: {command}"]
128
+ }
129
+
130
+ except subprocess.TimeoutExpired:
131
+ print("\n⚠️ Command timed out (30s)")
132
+ return {'reproduced': False}
133
+ except Exception as e:
134
+ print(f"\n❌ Error: {e}")
135
+ return {'reproduced': False}
136
+
137
+ def _reproduce_browser_bug(self, state: Dict) -> Dict:
138
+ """Reproduce browser/UI bug using Playwright (if available)."""
139
+ print("🌐 Browser bug detected. Provide URL where bug occurs:")
140
+ print(" (e.g., http://localhost:3000/page)\n")
141
+
142
+ url = input("> ").strip()
143
+
144
+ if not url:
145
+ print("\n⚠️ Cannot reproduce without URL.")
146
+ return {'reproduced': False}
147
+
148
+ print(f"\n🔧 Navigating to {url}...\n")
149
+
150
+ # TODO: Integrate Playwright MCP if available
151
+ # For now, mark as reproduced with manual confirmation
152
+
153
+ confirmed = input("Is the bug visible? (y/n): ").strip().lower()
154
+
155
+ if confirmed in ['y', 'yes']:
156
+ return {
157
+ 'reproduced': True,
158
+ 'bug_type': 'browser',
159
+ 'url': url,
160
+ 'evidence_collected': [f"Reproduced at {url}"]
161
+ }
162
+
163
+ return {'reproduced': False}
164
+
165
+ def _step_5_isolate(self, state: Dict) -> Dict:
166
+ """Isolate the problem by analyzing code."""
167
+ print("Now let's isolate the problem.\n")
168
+
169
+ error = state['bug_info'].get('error_message', '')
170
+ stack_trace = state['bug_info'].get('stack_trace', '')
171
+
172
+ print("📊 Code path to investigate:")
173
+ if error:
174
+ print(f" Error: {error}")
175
+ if stack_trace:
176
+ print(f" Stack trace: {stack_trace}")
177
+
178
+ isolation_notes = input("\n> Describe what code you think is involved: ").strip()
179
+
180
+ return {
181
+ 'isolated_problem': isolation_notes,
182
+ 'evidence_collected': [f"Isolated problem: {isolation_notes}"]
183
+ }
184
+
185
+ def _step_6_form_hypotheses(self, state: Dict) -> Dict:
186
+ """Form 2-3 hypotheses about root cause."""
187
+ print("\nForm hypotheses about what's causing the bug.\n")
188
+ print("For each hypothesis, provide:")
189
+ print(" 1. Your theory")
190
+ print(" 2. Evidence that supports it")
191
+ print(" 3. How you'd test it\n")
192
+
193
+ hypotheses = []
194
+ for rank in range(1, 4):
195
+ print(f"Hypothesis {rank}:")
196
+ description = input(" > Theory: ").strip()
197
+ if not description:
198
+ continue
199
+
200
+ evidence = input(" > Evidence supporting this: ").strip()
201
+ test_plan = input(" > How to test it: ").strip()
202
+
203
+ hypotheses.append({
204
+ 'rank': rank,
205
+ 'description': description,
206
+ 'evidence': evidence,
207
+ 'test_plan': test_plan,
208
+ 'status': 'UNKNOWN',
209
+ 'test_results': '',
210
+ })
211
+
212
+ return {'hypotheses': hypotheses}
213
+
214
+ def _step_7_test_hypotheses(self, state: Dict) -> Dict:
215
+ """Test each hypothesis systematically."""
216
+ hypotheses = state.get('hypotheses', [])
217
+
218
+ print(f"\nTesting {len(hypotheses)} hypothesis/hypotheses...\n")
219
+
220
+ for h in hypotheses:
221
+ print(f"Testing H{h['rank']}: {h['description']}")
222
+ print(f" Plan: {h['test_plan']}\n")
223
+
224
+ result = input(" > Test result (CONFIRMED/REJECTED/PARTIAL): ").strip().upper()
225
+ details = input(" > Details: ").strip()
226
+
227
+ h['status'] = result if result in ['CONFIRMED', 'REJECTED', 'PARTIAL'] else 'UNKNOWN'
228
+ h['test_results'] = details
229
+
230
+ if h['status'] == 'CONFIRMED':
231
+ print(f" ✅ H{h['rank']} CONFIRMED!\n")
232
+ elif h['status'] == 'REJECTED':
233
+ print(f" ❌ H{h['rank']} rejected.\n")
234
+ else:
235
+ print(f" ⚠️ H{h['rank']} inconclusive.\n")
236
+
237
+ return {'hypotheses': hypotheses}
238
+
239
+ def _step_8_update_findings(self, state: Dict) -> Dict:
240
+ """Update findings.md with investigation results."""
241
+ print("\n📝 Findings will be written to tasks/findings.md")
242
+ print(" (This happens automatically in the next step)")
243
+ return {}
244
+
245
+ def _step_9_propose_fix(self, state: Dict) -> Dict:
246
+ """Propose minimal fix."""
247
+ hypotheses = state.get('hypotheses', [])
248
+ confirmed = next((h for h in hypotheses if h['status'] == 'CONFIRMED'), None)
249
+
250
+ if not confirmed:
251
+ print("\n❌ No confirmed hypothesis—cannot propose fix.")
252
+ return {}
253
+
254
+ print(f"\nConfirmed root cause: {confirmed['description']}\n")
255
+ print("Describe the minimal fix:")
256
+ print(" - What file(s) to change")
257
+ print(" - What to change")
258
+ print(" - Why it fixes the problem\n")
259
+
260
+ fix_description = input("> Fix proposal: ").strip()
261
+
262
+ return {
263
+ 'confirmed_hypothesis': confirmed['description'],
264
+ 'proposed_fix': fix_description,
265
+ 'root_cause_confirmed': confirmed['description']
266
+ }
267
+
268
+ def _step_10_verify_fix(self, state: Dict) -> Dict:
269
+ """Verify the fix works."""
270
+ print("\n✅ Apply the fix to your code, then return here.\n")
271
+ input("Press Enter when fix is applied: ")
272
+
273
+ print("\nVerifying fix works...\n")
274
+
275
+ command = input("Run your reproduction command again: ").strip()
276
+
277
+ if command:
278
+ try:
279
+ result = subprocess.run(
280
+ command,
281
+ shell=True,
282
+ capture_output=True,
283
+ text=True,
284
+ timeout=30
285
+ )
286
+ output = result.stdout + result.stderr
287
+ print(output)
288
+
289
+ fixed = input("\nIs the bug fixed? (y/n): ").strip().lower()
290
+ if fixed in ['y', 'yes']:
291
+ print("✅ Fix verified!")
292
+ return {'fix_verified': True}
293
+
294
+ except Exception as e:
295
+ print(f"Error: {e}")
296
+
297
+ return {'fix_verified': False}
298
+
299
+ def _step_11_document(self, state: Dict) -> Dict:
300
+ """Document the findings and create lesson if needed."""
301
+ print("\n📚 Documentation will be written:")
302
+ print(" ✅ tasks/findings.md (bug details)")
303
+
304
+ # Determine if a lesson should be created
305
+ root_cause = state.get('root_cause_confirmed', '')
306
+
307
+ skip_patterns = [
308
+ 'typo', 'spelling', 'copy-paste',
309
+ 'environment variable',
310
+ 'one-off', 'race condition'
311
+ ]
312
+
313
+ skip_lesson = any(p.lower() in root_cause.lower() for p in skip_patterns)
314
+
315
+ if not skip_lesson and root_cause:
316
+ print(" ✅ tasks/lessons.md (prevention rule)")
317
+ return {
318
+ 'lesson_created': True,
319
+ 'root_cause_confirmed': root_cause
320
+ }
321
+ else:
322
+ print(" ⏭️ Skipping lesson (one-off issue)")
323
+ return {
324
+ 'lesson_created': False,
325
+ 'root_cause_confirmed': root_cause
326
+ }