@kennethsolomon/shipkit 3.0.4 → 3.0.6
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/commands/sk/config +92 -0
- package/commands/sk/finish-feature.md +18 -13
- package/commands/sk/help.md +12 -3
- package/commands/sk/security-check.md +9 -7
- package/commands/sk/set-profile +113 -0
- package/package.json +1 -1
- package/skills/sk:test/SKILL.md +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manage ShipKit project configuration."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
def get_defaults():
|
|
10
|
+
return {
|
|
11
|
+
"profile": "balanced",
|
|
12
|
+
"auto_commit": True,
|
|
13
|
+
"skip_gates": [],
|
|
14
|
+
"coverage_threshold": 100,
|
|
15
|
+
"branch_pattern": "feature/{slug}",
|
|
16
|
+
"model_overrides": {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
def get_config():
|
|
20
|
+
"""Read current config or return defaults."""
|
|
21
|
+
config_file = Path(".shipkit/config.json")
|
|
22
|
+
if config_file.exists():
|
|
23
|
+
try:
|
|
24
|
+
with open(config_file) as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
except json.JSONDecodeError:
|
|
27
|
+
return get_defaults()
|
|
28
|
+
return get_defaults()
|
|
29
|
+
|
|
30
|
+
def get_models_for_profile(profile):
|
|
31
|
+
"""Get model assignments for a profile."""
|
|
32
|
+
models = {
|
|
33
|
+
"full-sail": {"planning": "opus", "implementation": "opus", "audits": "opus", "gates": "sonnet"},
|
|
34
|
+
"quality": {"planning": "opus", "implementation": "sonnet", "audits": "sonnet", "gates": "sonnet"},
|
|
35
|
+
"balanced": {"planning": "sonnet", "implementation": "sonnet", "audits": "sonnet", "gates": "haiku"},
|
|
36
|
+
"budget": {"planning": "sonnet", "implementation": "sonnet", "audits": "haiku", "gates": "haiku"}
|
|
37
|
+
}
|
|
38
|
+
return models.get(profile, models["balanced"])
|
|
39
|
+
|
|
40
|
+
def display_config(config):
|
|
41
|
+
"""Display current configuration in a table."""
|
|
42
|
+
print("\n## Current Configuration\n")
|
|
43
|
+
print("| Setting | Value | Description |")
|
|
44
|
+
print("|---------|-------|-------------|")
|
|
45
|
+
print(f"| `profile` | `{config['profile']}` | Model routing profile (full-sail / quality / balanced / budget) |")
|
|
46
|
+
print(f"| `auto_commit` | `{str(config['auto_commit']).lower()}` | Auto-run `/sk:smart-commit` after each gate passes |")
|
|
47
|
+
skip_gates_str = ", ".join(config['skip_gates']) if config['skip_gates'] else "(none)"
|
|
48
|
+
print(f"| `skip_gates` | `{skip_gates_str}` | Gates to skip |")
|
|
49
|
+
print(f"| `coverage_threshold` | `{config['coverage_threshold']}%` | Minimum test coverage on new code |")
|
|
50
|
+
print(f"| `branch_pattern` | `{config['branch_pattern']}` | Branch naming pattern |")
|
|
51
|
+
print(f"| `model_overrides` | `{json.dumps(config['model_overrides'])}` | Per-skill model overrides |")
|
|
52
|
+
|
|
53
|
+
profile = config['profile']
|
|
54
|
+
models = get_models_for_profile(profile)
|
|
55
|
+
print(f"\n## Model Assignments — `{profile}` profile\n")
|
|
56
|
+
print("| Skill group | Model |")
|
|
57
|
+
print("|-------------|-------|")
|
|
58
|
+
print(f"| brainstorm, write-plan, debug, execute-plan, review | `{models['planning']}` |")
|
|
59
|
+
print(f"| write-tests, frontend-design, api-design, security-check | `{models['implementation']}` |")
|
|
60
|
+
print(f"| perf, schema-migrate, accessibility | `{models['audits']}` |")
|
|
61
|
+
print(f"| lint, test | `{models['gates']}` |")
|
|
62
|
+
print(f"| smart-commit, branch, update-task | `haiku` |")
|
|
63
|
+
|
|
64
|
+
def save_config(config):
|
|
65
|
+
"""Save config to .shipkit/config.json."""
|
|
66
|
+
Path(".shipkit").mkdir(exist_ok=True)
|
|
67
|
+
config_file = Path(".shipkit/config.json")
|
|
68
|
+
|
|
69
|
+
with open(config_file, "w") as f:
|
|
70
|
+
json.dump(config, f, indent=2)
|
|
71
|
+
|
|
72
|
+
# Add to .gitignore
|
|
73
|
+
gitignore = Path(".gitignore")
|
|
74
|
+
if gitignore.exists():
|
|
75
|
+
content = gitignore.read_text()
|
|
76
|
+
if ".shipkit/config.json" not in content:
|
|
77
|
+
with open(gitignore, "a") as f:
|
|
78
|
+
f.write("\n.shipkit/config.json\n")
|
|
79
|
+
else:
|
|
80
|
+
gitignore.write_text(".shipkit/config.json\n")
|
|
81
|
+
|
|
82
|
+
print(f"\n✅ Config saved to `.shipkit/config.json`")
|
|
83
|
+
|
|
84
|
+
def main():
|
|
85
|
+
config = get_config()
|
|
86
|
+
display_config(config)
|
|
87
|
+
|
|
88
|
+
print("\n---\n")
|
|
89
|
+
print("To change a setting, run: `/sk:set-profile <profile>` or edit `.shipkit/config.json` directly")
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Finalize a feature/bug-fix: changelog, arch log, verification, and PR creation."
|
|
3
|
+
---
|
|
3
4
|
|
|
4
|
-
#
|
|
5
|
+
# /sk:finish-feature
|
|
6
|
+
|
|
7
|
+
Finalize a feature/bug-fix branch and create a pull request.
|
|
5
8
|
|
|
6
9
|
Finalize a feature/bug-fix branch: changelog, arch log, security gate, verification, and PR creation.
|
|
7
10
|
|
|
@@ -80,22 +83,24 @@ If unresolved Critical/High findings remain, warn the user before proceeding.
|
|
|
80
83
|
|
|
81
84
|
Tests should have been created during `/sk:execute-plan`. Verify:
|
|
82
85
|
|
|
86
|
+
Detect the project stack from `CLAUDE.md`, `package.json`, `composer.json`, etc. before running checks.
|
|
87
|
+
|
|
83
88
|
a) **Automated Tests**
|
|
84
|
-
- Execute
|
|
89
|
+
- Execute the detected test runner (e.g. `npm test`, `./vendor/bin/pest`, `python -m pytest`)
|
|
85
90
|
- Verify all tests pass with no failures
|
|
86
|
-
- Check test coverage (target: >80% for new code
|
|
91
|
+
- Check test coverage (target: >80% for new code)
|
|
87
92
|
- No skipped tests (`test.skip`, `it.skip`, `@skip`, etc.)
|
|
88
|
-
- Run other CI checks: lint
|
|
93
|
+
- Run other CI checks: lint and build using project-detected commands
|
|
89
94
|
|
|
90
|
-
b) **Manual Testing
|
|
91
|
-
- For frontend (
|
|
92
|
-
- For backend/API (
|
|
93
|
-
- For CLI/desktop (
|
|
94
|
-
-
|
|
95
|
+
b) **Manual Testing**
|
|
96
|
+
- For frontend (if detected): Render the component/page in browser, verify state updates work correctly, test all user interactions (clicks, form inputs, navigation), verify conditional rendering, check edge cases and error states
|
|
97
|
+
- For backend/API (if detected): Test HTTP status codes and responses, verify request/response bodies match spec, test error cases and input validation, check database transactions/state, verify authentication/authorization if applicable
|
|
98
|
+
- For CLI/desktop (if detected): Test command-line arguments and flags, verify output format and readability, test error messages and help text, check file I/O operations
|
|
99
|
+
- Verify test structure matches project conventions, assertions are clear and specific, setup/teardown is properly handled
|
|
95
100
|
|
|
96
101
|
c) **Regression Testing**
|
|
97
102
|
- Test related existing functionality to ensure no breakage
|
|
98
|
-
-
|
|
103
|
+
- Check related components/endpoints/commands work correctly
|
|
99
104
|
- Verify no new console errors, warnings, or debug statements
|
|
100
105
|
- Confirm existing tests still pass
|
|
101
106
|
|
|
@@ -104,7 +109,7 @@ If unresolved Critical/High findings remain, warn the user before proceeding.
|
|
|
104
109
|
- Proper error handling and validation
|
|
105
110
|
- No debugging code (`console.log`, `debugger`, `pdb`, `print` statements, etc.)
|
|
106
111
|
- Comments explain *why*, not *what*
|
|
107
|
-
- Follows
|
|
112
|
+
- Follows project conventions and style guide (see `CLAUDE.md`)
|
|
108
113
|
|
|
109
114
|
6. **Security Gate**
|
|
110
115
|
- If `/sk:security-check` has not been run on this branch, recommend: "Run `/sk:security-check` before creating a PR."
|
package/commands/sk/help.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
description: "Show all ShipKit commands and workflow overview."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
# Meta
|
|
6
|
+
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `/sk:help` | Show all commands and workflow overview |
|
|
10
|
+
| `/sk:status` | Show workflow and task status at a glance |
|
|
11
|
+
| `/sk:skill-creator` | Create or improve ShipKit skills |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
5
15
|
# /sk:help — ShipKit
|
|
6
16
|
|
|
7
17
|
A structured workflow toolkit for Claude Code.
|
|
@@ -19,7 +29,6 @@ Run these commands in order for a complete, quality-gated feature build.
|
|
|
19
29
|
| `/sk:schema-migrate` | Analyze schema changes *(skip if no DB changes)* |
|
|
20
30
|
| `/sk:write-tests` | TDD red: write failing tests first |
|
|
21
31
|
| `/sk:execute-plan` | TDD green: implement until tests pass |
|
|
22
|
-
| `/sk:change` | Requirements changed? Re-enter at the right step |
|
|
23
32
|
| `/sk:smart-commit` | Conventional commit with approval |
|
|
24
33
|
| `/sk:lint` | **GATE** — all linters must pass |
|
|
25
34
|
| `/sk:test` | **GATE** — 100% coverage on new code |
|
|
@@ -70,7 +79,7 @@ Requirements change mid-workflow? Run `/sk:change` — it classifies the scope a
|
|
|
70
79
|
| `/sk:lint` | Auto-detect and run all linters |
|
|
71
80
|
| `/sk:perf` | Performance audit |
|
|
72
81
|
| `/sk:plan` | Create/refresh task planning files |
|
|
73
|
-
| `/sk:release` |
|
|
82
|
+
| `/sk:release` | Automate releases: bump version, update CHANGELOG, create tag, push to GitHub. Use --android and/or --ios flags for App Store / Play Store readiness audit |
|
|
74
83
|
| `/sk:review` | Self-review of branch changes |
|
|
75
84
|
| `/sk:schema-migrate` | Multi-ORM schema change analysis |
|
|
76
85
|
| `/sk:security-check` | OWASP security audit |
|
|
@@ -113,4 +122,4 @@ Config lives in `.shipkit/config.json` — per project, gitignored by default.
|
|
|
113
122
|
|
|
114
123
|
---
|
|
115
124
|
|
|
116
|
-
**ShipKit** by Kenneth Solomon · `
|
|
125
|
+
**ShipKit** by Kenneth Solomon · `npx @kennethsolomon/shipkit` to install/update
|
|
@@ -53,36 +53,38 @@ Read each file in scope before auditing.
|
|
|
53
53
|
- **A09 Logging Failures** — Missing audit logs, PII in logs, no alerting on security events
|
|
54
54
|
- **A10 SSRF** — Unvalidated URLs, internal network access, DNS rebinding
|
|
55
55
|
|
|
56
|
-
### 2. Stack-Specific Checks
|
|
56
|
+
### 2. Stack-Specific Checks
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Detect the project stack from `CLAUDE.md`, `package.json`, `composer.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, etc. Apply the relevant checks below for every detected framework/language.
|
|
59
|
+
|
|
60
|
+
**If the project uses React/Next.js:**
|
|
59
61
|
- `dangerouslySetInnerHTML` usage without sanitization
|
|
60
62
|
- Client-side secrets (API keys in browser bundles)
|
|
61
63
|
- Missing CSP headers
|
|
62
64
|
- Server component data leaking to client
|
|
63
65
|
- `getServerSideProps`/Server Actions exposing internal data
|
|
64
66
|
|
|
65
|
-
**If
|
|
67
|
+
**If the project uses Express/Node.js:**
|
|
66
68
|
- Missing helmet/security headers
|
|
67
69
|
- Unsanitized user input in `req.params`, `req.query`, `req.body`
|
|
68
70
|
- Path traversal via `req.params` in file operations
|
|
69
71
|
- Missing rate limiting on auth endpoints
|
|
70
72
|
- Prototype pollution
|
|
71
73
|
|
|
72
|
-
**If
|
|
74
|
+
**If the project uses Python:**
|
|
73
75
|
- `eval()`, `exec()`, `pickle.loads()` with untrusted input
|
|
74
76
|
- SQL string formatting instead of parameterized queries
|
|
75
77
|
- `subprocess.shell=True` with user input
|
|
76
78
|
- Missing input validation on FastAPI/Django endpoints
|
|
77
79
|
- Jinja2 `| safe` filter misuse
|
|
78
80
|
|
|
79
|
-
**If
|
|
81
|
+
**If the project uses Go:**
|
|
80
82
|
- Unchecked error returns on security-critical operations
|
|
81
83
|
- `html/template` vs `text/template` confusion
|
|
82
84
|
- Missing context cancellation/timeouts
|
|
83
85
|
- Race conditions on shared state
|
|
84
86
|
|
|
85
|
-
**If
|
|
87
|
+
**If the project uses PHP/Laravel:**
|
|
86
88
|
- `include`/`require` with user-controlled paths
|
|
87
89
|
- `mysqli_query` without prepared statements
|
|
88
90
|
- Missing CSRF tokens
|
|
@@ -112,7 +114,7 @@ Write findings to `tasks/security-findings.md` using this format:
|
|
|
112
114
|
# Security Audit — YYYY-MM-DD
|
|
113
115
|
|
|
114
116
|
**Scope:** Changed files on branch `<branch-name>` | Full project scan
|
|
115
|
-
**Stack:**
|
|
117
|
+
**Stack:** `<detected stack — e.g. Laravel / React>`
|
|
116
118
|
**Files audited:** N
|
|
117
119
|
|
|
118
120
|
## Critical (must fix before deploy)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Switch ShipKit model routing profile."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
VALID_PROFILES = ["full-sail", "quality", "balanced", "budget"]
|
|
9
|
+
|
|
10
|
+
def get_defaults():
|
|
11
|
+
return {
|
|
12
|
+
"profile": "balanced",
|
|
13
|
+
"auto_commit": True,
|
|
14
|
+
"skip_gates": [],
|
|
15
|
+
"coverage_threshold": 100,
|
|
16
|
+
"branch_pattern": "feature/{slug}",
|
|
17
|
+
"model_overrides": {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def get_config():
|
|
21
|
+
"""Read current config or return defaults."""
|
|
22
|
+
config_file = Path(".shipkit/config.json")
|
|
23
|
+
if config_file.exists():
|
|
24
|
+
try:
|
|
25
|
+
with open(config_file) as f:
|
|
26
|
+
return json.load(f)
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
return get_defaults()
|
|
29
|
+
return get_defaults()
|
|
30
|
+
|
|
31
|
+
def get_models_for_profile(profile):
|
|
32
|
+
"""Get model assignments for a profile."""
|
|
33
|
+
models = {
|
|
34
|
+
"full-sail": {"planning": "opus", "implementation": "opus", "audits": "opus", "gates": "sonnet"},
|
|
35
|
+
"quality": {"planning": "opus", "implementation": "sonnet", "audits": "sonnet", "gates": "sonnet"},
|
|
36
|
+
"balanced": {"planning": "sonnet", "implementation": "sonnet", "audits": "sonnet", "gates": "haiku"},
|
|
37
|
+
"budget": {"planning": "sonnet", "implementation": "sonnet", "audits": "haiku", "gates": "haiku"}
|
|
38
|
+
}
|
|
39
|
+
return models.get(profile, models["balanced"])
|
|
40
|
+
|
|
41
|
+
def get_profile_description(profile):
|
|
42
|
+
"""Get profile philosophy and use case."""
|
|
43
|
+
descriptions = {
|
|
44
|
+
"full-sail": ("Opus on everything that matters", "High-stakes work, client projects, production features"),
|
|
45
|
+
"quality": ("Opus for planning + review, Sonnet for implementation", "Most professional projects"),
|
|
46
|
+
"balanced": ("Sonnet across the board", "Day-to-day development (default)"),
|
|
47
|
+
"budget": ("Haiku where possible, Sonnet for gates", "Side projects, exploration, prototyping")
|
|
48
|
+
}
|
|
49
|
+
return descriptions.get(profile, ("", ""))
|
|
50
|
+
|
|
51
|
+
def save_config(config):
|
|
52
|
+
"""Save config to .shipkit/config.json."""
|
|
53
|
+
Path(".shipkit").mkdir(exist_ok=True)
|
|
54
|
+
config_file = Path(".shipkit/config.json")
|
|
55
|
+
|
|
56
|
+
with open(config_file, "w") as f:
|
|
57
|
+
json.dump(config, f, indent=2)
|
|
58
|
+
|
|
59
|
+
# Add to .gitignore
|
|
60
|
+
gitignore = Path(".gitignore")
|
|
61
|
+
if gitignore.exists():
|
|
62
|
+
content = gitignore.read_text()
|
|
63
|
+
if ".shipkit/config.json" not in content:
|
|
64
|
+
with open(gitignore, "a") as f:
|
|
65
|
+
f.write("\n.shipkit/config.json\n")
|
|
66
|
+
else:
|
|
67
|
+
gitignore.write_text(".shipkit/config.json\n")
|
|
68
|
+
|
|
69
|
+
def main():
|
|
70
|
+
# Get profile argument
|
|
71
|
+
profile = None
|
|
72
|
+
if len(sys.argv) > 1:
|
|
73
|
+
profile = sys.argv[1].lower()
|
|
74
|
+
|
|
75
|
+
# Validate profile
|
|
76
|
+
if profile and profile not in VALID_PROFILES:
|
|
77
|
+
print(f"❌ Invalid profile: `{profile}`")
|
|
78
|
+
print(f"\nValid profiles: {' · '.join(VALID_PROFILES)}")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
# If no profile provided, show options
|
|
82
|
+
if not profile:
|
|
83
|
+
print("## Available Profiles\n")
|
|
84
|
+
print("| Profile | Philosophy | Best for |")
|
|
85
|
+
print("|---------|-----------|---------|")
|
|
86
|
+
for p in VALID_PROFILES:
|
|
87
|
+
philosophy, use_case = get_profile_description(p)
|
|
88
|
+
default = " *(default)*" if p == "balanced" else ""
|
|
89
|
+
print(f"| `{p}` | {philosophy}{default} | {use_case} |")
|
|
90
|
+
print("\nUsage: `/sk:set-profile <profile>`")
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
|
|
93
|
+
# Read current config
|
|
94
|
+
config = get_config()
|
|
95
|
+
old_profile = config['profile']
|
|
96
|
+
|
|
97
|
+
# Update profile
|
|
98
|
+
config['profile'] = profile
|
|
99
|
+
save_config(config)
|
|
100
|
+
|
|
101
|
+
# Confirm and display new assignments
|
|
102
|
+
models = get_models_for_profile(profile)
|
|
103
|
+
print(f"\n✅ Profile set to: `{profile}` (was `{old_profile}`)\n")
|
|
104
|
+
print("## Model assignments for this project:\n")
|
|
105
|
+
print(f" brainstorm, write-plan, debug, execute-plan, review → `{models['planning']}`")
|
|
106
|
+
print(f" write-tests, frontend-design, api-design, security-check → `{models['implementation']}`")
|
|
107
|
+
print(f" perf, schema-migrate, accessibility → `{models['audits']}`")
|
|
108
|
+
print(f" lint, test → `{models['gates']}`")
|
|
109
|
+
print(f" smart-commit, branch, update-task → `haiku`\n")
|
|
110
|
+
print("Run `/sk:config` to see all settings or make further changes.")
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
main()
|
package/package.json
CHANGED
package/skills/sk:test/SKILL.md
CHANGED
|
@@ -72,7 +72,7 @@ npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-
|
|
|
72
72
|
|
|
73
73
|
Create `vitest.config.ts`:
|
|
74
74
|
```ts
|
|
75
|
-
import { defineConfig } from 'vitest/
|
|
75
|
+
import { defineConfig } from 'vitest/config';
|
|
76
76
|
import react from '@vitejs/plugin-react';
|
|
77
77
|
|
|
78
78
|
export default defineConfig({
|