@kennethsolomon/shipkit 3.10.0 → 3.10.1

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 (29) hide show
  1. package/package.json +1 -1
  2. package/skills/.claude/settings.local.json +20 -1
  3. package/skills/sk:brainstorming/SKILL.md +6 -6
  4. package/skills/sk:debug/__pycache__/debug_conductor.cpython-314.pyc +0 -0
  5. package/skills/sk:debug/debug_conductor.py +0 -8
  6. package/skills/sk:debug/lib/__pycache__/findings_writer.cpython-314.pyc +0 -0
  7. package/skills/sk:debug/lib/__pycache__/lessons_writer.cpython-314.pyc +0 -0
  8. package/skills/sk:debug/lib/__pycache__/step_runner.cpython-314.pyc +0 -0
  9. package/skills/sk:debug/lib/findings_writer.py +2 -2
  10. package/skills/sk:debug/lib/lessons_writer.py +2 -2
  11. package/skills/sk:debug/lib/step_runner.py +8 -10
  12. package/skills/sk:lint/SKILL.md +2 -2
  13. package/skills/sk:setup-claude/scripts/__pycache__/apply_setup_claude.cpython-314.pyc +0 -0
  14. package/skills/sk:setup-claude/scripts/apply_setup_claude.py +12 -3
  15. package/skills/sk:setup-optimizer/lib/__pycache__/discover.cpython-314.pyc +0 -0
  16. package/skills/sk:setup-optimizer/lib/discover.py +9 -10
  17. package/skills/sk:skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
  18. package/skills/sk:skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
  19. package/skills/sk:skill-creator/scripts/__pycache__/package_skill.cpython-314.pyc +0 -0
  20. package/skills/sk:skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
  21. package/skills/sk:skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
  22. package/skills/sk:skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
  23. package/skills/sk:skill-creator/scripts/generate_report.py +2 -0
  24. package/skills/sk:skill-creator/scripts/improve_description.py +2 -0
  25. package/skills/sk:skill-creator/scripts/package_skill.py +4 -0
  26. package/skills/sk:skill-creator/scripts/run_eval.py +3 -1
  27. package/skills/sk:skill-creator/scripts/run_loop.py +2 -0
  28. package/skills/sk:skill-creator/scripts/utils.py +2 -0
  29. package/skills/sk:test/SKILL.md +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kennethsolomon/shipkit",
3
- "version": "3.10.0",
3
+ "version": "3.10.1",
4
4
  "description": "A structured workflow toolkit for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",
@@ -12,7 +12,26 @@
12
12
  "Bash(done)",
13
13
  "Bash(tasks/security-findings.md:*)",
14
14
  "Bash(git push:*)",
15
- "Bash(git tag:*)"
15
+ "Bash(git tag:*)",
16
+ "Bash(find /Users/kennethsolomon/Herd/shipit -type f -name *.md -path */node_modules/*commands/sk/*)",
17
+ "Bash(grep -r \"sk:\" /Users/kennethsolomon/Herd/shipit/commands/sk/*.md)",
18
+ "Bash(find /Users/kennethsolomon/Herd/shipit/.claude/docs -name *.md)",
19
+ "Bash(grep -v \"^Command$\")",
20
+ "Bash(find /Users/kennethsolomon/Herd/shipit -path */test* -o -path */__tests__/* -o -path */spec/*)",
21
+ "Bash(find /Users/kennethsolomon/Herd/shipit/commands/sk -type f -name *.md)",
22
+ "Bash(find /Users/kennethsolomon/Herd/shipit/skills -maxdepth 1 -type d -name sk:*)",
23
+ "Bash(python3 -m py_compile skills/sk:debug/lib/step_runner.py)",
24
+ "Bash(python3 -m py_compile skills/sk:debug/debug_conductor.py)",
25
+ "Bash(python3 -m py_compile skills/sk:setup-optimizer/lib/discover.py)",
26
+ "Bash(python3 -m py_compile skills/sk:debug/lib/findings_writer.py)",
27
+ "Bash(python3 -m py_compile skills/sk:debug/lib/lessons_writer.py)",
28
+ "Bash(python3 -m py_compile skills/sk:skill-creator/scripts/run_eval.py)",
29
+ "Bash(python3 -m py_compile skills/sk:skill-creator/scripts/run_loop.py)",
30
+ "Bash(python3 -m py_compile skills/sk:skill-creator/scripts/improve_description.py)",
31
+ "Bash(python3 -m py_compile skills/sk:skill-creator/scripts/generate_report.py)",
32
+ "Bash(python3 -m py_compile skills/sk:skill-creator/scripts/utils.py)",
33
+ "Bash(git:*)",
34
+ "Bash(npm publish:*)"
16
35
  ]
17
36
  }
18
37
  }
@@ -34,7 +34,7 @@ You MUST create a task for each of these items and complete them in order:
34
34
  - Open questions (if any remain)
35
35
  Additionally, **append** an ADR entry to `docs/decisions.md` (see "Decisions Log" section below).
36
36
  (Optionally also write a full design doc to docs/plans/YYYY-MM-DD-<topic>-design.md)
37
- 6. **Transition to implementation** — invoke writing-plans skill to create implementation plan
37
+ 6. **Transition to implementation** — invoke `/sk:write-plan` skill to create implementation plan
38
38
 
39
39
  ## Process Flow
40
40
 
@@ -46,7 +46,7 @@ digraph brainstorming {
46
46
  "Present design sections" [shape=box];
47
47
  "User approves design?" [shape=diamond];
48
48
  "Write design doc" [shape=box];
49
- "Invoke writing-plans skill" [shape=doublecircle];
49
+ "Invoke `/sk:write-plan` skill" [shape=doublecircle];
50
50
 
51
51
  "Explore project context" -> "Ask clarifying questions";
52
52
  "Ask clarifying questions" -> "Propose 2-3 approaches";
@@ -54,11 +54,11 @@ digraph brainstorming {
54
54
  "Present design sections" -> "User approves design?";
55
55
  "User approves design?" -> "Present design sections" [label="no, revise"];
56
56
  "User approves design?" -> "Write design doc" [label="yes"];
57
- "Write design doc" -> "Invoke writing-plans skill";
57
+ "Write design doc" -> "Invoke `/sk:write-plan` skill";
58
58
  }
59
59
  ```
60
60
 
61
- **The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans.
61
+ **The terminal state is invoking `/sk:write-plan`.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is `/sk:write-plan`.
62
62
 
63
63
  ## The Process
64
64
 
@@ -95,8 +95,8 @@ digraph brainstorming {
95
95
  - Commit the findings, decisions log entry, and any design document to git
96
96
 
97
97
  **Implementation:**
98
- - Invoke the writing-plans skill to create a detailed implementation plan
99
- - Do NOT invoke any other skill. writing-plans is the next step.
98
+ - Invoke the `/sk:write-plan` skill to create a detailed implementation plan
99
+ - Do NOT invoke any other skill. `/sk:write-plan` is the next step.
100
100
 
101
101
  ## Decisions Log
102
102
 
@@ -75,7 +75,6 @@ class DebugConductor:
75
75
  if not self._can_proceed(step):
76
76
  print(f"\n⛔ BLOCKED: {self._gate_reason(step)}")
77
77
  print("\nRun /debug again to continue from this step.")
78
- self._save_progress()
79
78
  return
80
79
 
81
80
  # Run step and update state
@@ -94,7 +93,6 @@ class DebugConductor:
94
93
  proceed = input(f"\n✅ Step {step_num} complete. Continue to step {step_num + 1}? (y/n): ").strip().lower()
95
94
  if proceed not in ['y', 'yes', '']:
96
95
  print(f"Pausing after step {step_num}. Run /debug again to continue.")
97
- self._save_progress()
98
96
  return
99
97
 
100
98
  print("\n" + "="*70)
@@ -161,12 +159,6 @@ class DebugConductor:
161
159
  return "No confirmed hypothesis yet—cannot propose fix."
162
160
  return "Precondition not met for this step."
163
161
 
164
- def _save_progress(self):
165
- """Save current state for resumption (optional)."""
166
- # Could implement checkpoint system here
167
- pass
168
-
169
-
170
162
  def main():
171
163
  """Entry point for /debug skill."""
172
164
  conductor = DebugConductor()
@@ -44,8 +44,8 @@ class FindingsWriter:
44
44
  self._append_entry(entry)
45
45
  print(f"\n✅ Findings written to {self.findings_path}")
46
46
  return True
47
- except Exception as e:
48
- print(f"\n❌ Error writing findings: {e}")
47
+ except OSError as e:
48
+ print(f"\n❌ Error writing findings to {self.findings_path}: {e}")
49
49
  return False
50
50
 
51
51
  def _ensure_file_exists(self):
@@ -66,8 +66,8 @@ class LessonsWriter:
66
66
  self._append_lesson(entry)
67
67
  print(f"\n✅ New lesson added to {self.lessons_path}")
68
68
  return True
69
- except Exception as e:
70
- print(f"\n❌ Error writing lesson: {e}")
69
+ except OSError as e:
70
+ print(f"\n❌ Error writing lesson to {self.lessons_path}: {e}")
71
71
  return False
72
72
 
73
73
  def _ensure_file_exists(self):
@@ -1,9 +1,11 @@
1
1
  """Execute individual debug steps (3-11)."""
2
2
 
3
+ from __future__ import annotations
4
+
5
+ import shlex
3
6
  import subprocess
4
- from typing import Dict
5
7
  from pathlib import Path
6
- import sys
8
+ from typing import Dict
7
9
 
8
10
 
9
11
  class StepRunner:
@@ -42,8 +44,7 @@ class StepRunner:
42
44
  # Recent commits
43
45
  print("📋 Recent commits:")
44
46
  result = subprocess.run(
45
- "git log --oneline -10",
46
- shell=True,
47
+ ["git", "log", "--oneline", "-10"],
47
48
  capture_output=True,
48
49
  text=True
49
50
  )
@@ -52,8 +53,7 @@ class StepRunner:
52
53
  # Recent diffs
53
54
  print("\n📋 Recent file changes:")
54
55
  result = subprocess.run(
55
- "git diff HEAD~3 --stat",
56
- shell=True,
56
+ ["git", "diff", "HEAD~3", "--stat"],
57
57
  capture_output=True,
58
58
  text=True
59
59
  )
@@ -107,8 +107,7 @@ class StepRunner:
107
107
 
108
108
  try:
109
109
  result = subprocess.run(
110
- command,
111
- shell=True,
110
+ shlex.split(command),
112
111
  capture_output=True,
113
112
  text=True,
114
113
  timeout=30
@@ -277,8 +276,7 @@ class StepRunner:
277
276
  if command:
278
277
  try:
279
278
  result = subprocess.run(
280
- command,
281
- shell=True,
279
+ shlex.split(command),
282
280
  capture_output=True,
283
281
  text=True,
284
282
  timeout=30
@@ -171,8 +171,8 @@ Read `.shipkit/config.json` from the project root if it exists.
171
171
 
172
172
  | Profile | Model |
173
173
  |---------|-------|
174
- | `full-sail` | sonnet |
175
- | `quality` | sonnet |
174
+ | `full-sail` | opus (inherit) |
175
+ | `quality` | opus (inherit) |
176
176
  | `balanced` | haiku |
177
177
  | `budget` | haiku |
178
178
 
@@ -271,7 +271,16 @@ def _write_file_if_missing(dest: Path, content: str) -> Tuple[str, Path]:
271
271
  if dest.exists():
272
272
  return ("skipped", dest)
273
273
  dest.parent.mkdir(parents=True, exist_ok=True)
274
- dest.write_text(content, encoding="utf-8")
274
+ # Atomic write: write to temp file then rename to avoid TOCTOU race
275
+ import tempfile
276
+ fd, tmp_path = tempfile.mkstemp(dir=dest.parent, suffix=".tmp")
277
+ try:
278
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
279
+ f.write(content)
280
+ Path(tmp_path).replace(dest)
281
+ except BaseException:
282
+ Path(tmp_path).unlink(missing_ok=True)
283
+ raise
275
284
  return ("created", dest)
276
285
 
277
286
 
@@ -282,7 +291,7 @@ def _write_file_if_generated(dest: Path, content: str, template_path: Optional[P
282
291
  return ("created", dest)
283
292
  try:
284
293
  existing = dest.read_text(encoding="utf-8")
285
- except Exception:
294
+ except (OSError, UnicodeDecodeError):
286
295
  return ("skipped", dest)
287
296
  if GENERATED_MARKER in existing:
288
297
  # Check if content is identical (fast path)
@@ -311,7 +320,7 @@ def _plan_file_if_generated(dest: Path, content: str) -> str:
311
320
  return "created"
312
321
  try:
313
322
  existing = dest.read_text(encoding="utf-8")
314
- except Exception:
323
+ except (OSError, UnicodeDecodeError):
315
324
  return "skipped"
316
325
  if GENERATED_MARKER not in existing:
317
326
  return "skipped"
@@ -1,7 +1,8 @@
1
1
  """Intelligent project structure, documentation, and workflow discovery."""
2
2
 
3
+ import json
3
4
  from pathlib import Path
4
- from typing import Dict, List, Tuple
5
+ from typing import Dict, List
5
6
 
6
7
 
7
8
  # Directories to exclude from discovery
@@ -174,16 +175,14 @@ def _extract_makefile_targets(root_path: Path) -> List[str]:
174
175
  target = line.split(':')[0].strip()
175
176
  if target and not target.startswith('.'):
176
177
  targets.append(target)
177
- except Exception:
178
- pass
178
+ except (OSError, UnicodeDecodeError) as e:
179
+ print(f"Warning: Could not parse Makefile: {e}")
179
180
 
180
181
  return list(set(targets))[:10] # Limit to 10 targets
181
182
 
182
183
 
183
184
  def _extract_npm_scripts(root_path: Path) -> List[str]:
184
185
  """Extract scripts from package.json."""
185
- import json
186
-
187
186
  package_json = root_path / 'package.json'
188
187
  if not package_json.exists():
189
188
  return []
@@ -191,14 +190,14 @@ def _extract_npm_scripts(root_path: Path) -> List[str]:
191
190
  scripts = []
192
191
  try:
193
192
  content = json.loads(package_json.read_text())
194
- if 'scripts' in content:
193
+ if isinstance(content, dict) and 'scripts' in content:
195
194
  # Include common scripts, exclude default ones
196
195
  exclude = {'test', 'start', 'dev', 'build'}
197
196
  for script_name in content['scripts'].keys():
198
197
  if script_name not in exclude:
199
198
  scripts.append(script_name)
200
- except Exception:
201
- pass
199
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
200
+ print(f"Warning: Could not parse package.json scripts: {e}")
202
201
 
203
202
  return scripts[:8] # Limit to 8 scripts
204
203
 
@@ -215,7 +214,7 @@ def _find_github_workflows(root_path: Path) -> List[str]:
215
214
  workflows.append(workflow_file.stem)
216
215
  for workflow_file in workflows_dir.glob('*.yaml'):
217
216
  workflows.append(workflow_file.stem)
218
- except Exception:
219
- pass
217
+ except OSError as e:
218
+ print(f"Warning: Could not read GitHub workflows: {e}")
220
219
 
221
220
  return workflows[:5] # Limit to 5 workflows
@@ -6,6 +6,8 @@ showing each description attempt with check/x for each test case.
6
6
  Distinguishes between train and test queries.
7
7
  """
8
8
 
9
+ from __future__ import annotations
10
+
9
11
  import argparse
10
12
  import html
11
13
  import json
@@ -5,6 +5,8 @@ Takes eval results (from run_eval.py) and generates an improved description
5
5
  using Claude with extended thinking.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import argparse
9
11
  import json
10
12
  import re
@@ -93,6 +93,10 @@ def package_skill(skill_path, output_dir=None):
93
93
  for file_path in skill_path.rglob('*'):
94
94
  if not file_path.is_file():
95
95
  continue
96
+ # Skip symlinks to prevent path traversal
97
+ if file_path.is_symlink():
98
+ print(f" Skipped (symlink): {file_path}")
99
+ continue
96
100
  arcname = file_path.relative_to(skill_path.parent)
97
101
  if should_exclude(arcname):
98
102
  print(f" Skipped: {arcname}")
@@ -5,6 +5,8 @@ Tests whether a skill's description causes Claude to trigger (read the skill)
5
5
  for a set of queries. Outputs results as JSON.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import argparse
9
11
  import json
10
12
  import os
@@ -221,7 +223,7 @@ def run_eval(
221
223
  try:
222
224
  query_triggers[query].append(future.result())
223
225
  except Exception as e:
224
- print(f"Warning: query failed: {e}", file=sys.stderr)
226
+ print(f"Warning: query failed for '{query[:60]}': {type(e).__name__}: {e}", file=sys.stderr)
225
227
  query_triggers[query].append(False)
226
228
 
227
229
  for query, triggers in query_triggers.items():
@@ -6,6 +6,8 @@ and returning the best description found. Supports train/test split to prevent
6
6
  overfitting.
7
7
  """
8
8
 
9
+ from __future__ import annotations
10
+
9
11
  import argparse
10
12
  import json
11
13
  import random
@@ -1,5 +1,7 @@
1
1
  """Shared utilities for skill-creator scripts."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from pathlib import Path
4
6
 
5
7
 
@@ -184,8 +184,8 @@ Read `.shipkit/config.json` from the project root if it exists.
184
184
 
185
185
  | Profile | Model |
186
186
  |---------|-------|
187
- | `full-sail` | sonnet |
188
- | `quality` | sonnet |
187
+ | `full-sail` | opus (inherit) |
188
+ | `quality` | opus (inherit) |
189
189
  | `balanced` | haiku |
190
190
  | `budget` | haiku |
191
191