@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,193 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import importlib.util
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import unittest
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_apply_module():
|
|
12
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
13
|
+
script_path = skill_root / "scripts" / "apply_setup_claude.py"
|
|
14
|
+
spec = importlib.util.spec_from_file_location("apply_setup_claude", script_path)
|
|
15
|
+
assert spec and spec.loader
|
|
16
|
+
module = importlib.util.module_from_spec(spec)
|
|
17
|
+
sys.modules[spec.name] = module
|
|
18
|
+
spec.loader.exec_module(module)
|
|
19
|
+
return module
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestApplySetupClaude(unittest.TestCase):
|
|
23
|
+
def test_print_detection_exits_without_writes(self):
|
|
24
|
+
mod = _load_apply_module()
|
|
25
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
26
|
+
|
|
27
|
+
with tempfile.TemporaryDirectory() as td:
|
|
28
|
+
repo_root = Path(td)
|
|
29
|
+
(repo_root / "package.json").write_text(
|
|
30
|
+
json.dumps(
|
|
31
|
+
{
|
|
32
|
+
"name": "demo",
|
|
33
|
+
"description": "Demo repo",
|
|
34
|
+
"scripts": {"dev": "next dev", "test": "vitest"},
|
|
35
|
+
"dependencies": {"next": "1.0.0", "react": "1.0.0"},
|
|
36
|
+
"devDependencies": {"vitest": "1.0.0"},
|
|
37
|
+
}
|
|
38
|
+
),
|
|
39
|
+
encoding="utf-8",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
buf = io.StringIO()
|
|
43
|
+
with contextlib.redirect_stdout(buf):
|
|
44
|
+
rc = mod.main(["apply_setup_claude.py", str(repo_root), "--print-detection"])
|
|
45
|
+
self.assertEqual(rc, 0)
|
|
46
|
+
data = json.loads(buf.getvalue().strip())
|
|
47
|
+
self.assertEqual(data["project_name"], "demo")
|
|
48
|
+
self.assertEqual(data["framework"], "Next.js (App Router)")
|
|
49
|
+
|
|
50
|
+
self.assertFalse((repo_root / "tasks").exists())
|
|
51
|
+
self.assertFalse((repo_root / ".claude").exists())
|
|
52
|
+
|
|
53
|
+
def test_dry_run_does_not_write_files_or_dirs(self):
|
|
54
|
+
mod = _load_apply_module()
|
|
55
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
56
|
+
|
|
57
|
+
with tempfile.TemporaryDirectory() as td:
|
|
58
|
+
repo_root = Path(td)
|
|
59
|
+
(repo_root / "package.json").write_text(json.dumps({"name": "demo"}), encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
buf = io.StringIO()
|
|
62
|
+
with contextlib.redirect_stdout(buf):
|
|
63
|
+
rc = mod.apply(
|
|
64
|
+
repo_root,
|
|
65
|
+
skill_root,
|
|
66
|
+
update_generated=False,
|
|
67
|
+
dry_run=True,
|
|
68
|
+
detection=mod.detect(repo_root),
|
|
69
|
+
)
|
|
70
|
+
self.assertEqual(rc, 0)
|
|
71
|
+
|
|
72
|
+
self.assertFalse((repo_root / "tasks").exists())
|
|
73
|
+
self.assertFalse((repo_root / ".claude").exists())
|
|
74
|
+
|
|
75
|
+
def test_marker_guarded_updates(self):
|
|
76
|
+
mod = _load_apply_module()
|
|
77
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
78
|
+
|
|
79
|
+
with tempfile.TemporaryDirectory() as td:
|
|
80
|
+
repo_root = Path(td)
|
|
81
|
+
(repo_root / "package.json").write_text(
|
|
82
|
+
json.dumps({"name": "demo", "scripts": {"dev": "node dev"}}),
|
|
83
|
+
encoding="utf-8",
|
|
84
|
+
)
|
|
85
|
+
detection = mod.detect(repo_root)
|
|
86
|
+
|
|
87
|
+
# Create an agent-modified file (no marker): should NOT be overwritten.
|
|
88
|
+
no_marker_path = repo_root / ".claude" / "commands" / "status.md"
|
|
89
|
+
no_marker_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
no_marker_path.write_text("# custom status\n", encoding="utf-8")
|
|
91
|
+
|
|
92
|
+
# Create a generated file with marker but wrong content: should be updated.
|
|
93
|
+
marker_path = repo_root / ".claude" / "commands" / "plan.md"
|
|
94
|
+
marker_path.write_text(f"{mod.GENERATED_MARKER}\n# old\n", encoding="utf-8")
|
|
95
|
+
|
|
96
|
+
buf = io.StringIO()
|
|
97
|
+
with contextlib.redirect_stdout(buf):
|
|
98
|
+
rc = mod.apply(
|
|
99
|
+
repo_root,
|
|
100
|
+
skill_root,
|
|
101
|
+
update_generated=True,
|
|
102
|
+
dry_run=False,
|
|
103
|
+
detection=detection,
|
|
104
|
+
)
|
|
105
|
+
self.assertEqual(rc, 0)
|
|
106
|
+
|
|
107
|
+
self.assertEqual(no_marker_path.read_text(encoding="utf-8"), "# custom status\n")
|
|
108
|
+
|
|
109
|
+
tpl = (skill_root / "templates" / "commands" / "plan.md.template").read_text(encoding="utf-8")
|
|
110
|
+
expected = mod.render_template(tpl, detection)
|
|
111
|
+
self.assertEqual(marker_path.read_text(encoding="utf-8"), expected)
|
|
112
|
+
|
|
113
|
+
def test_workflow_status_created_on_fresh_setup(self):
|
|
114
|
+
mod = _load_apply_module()
|
|
115
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
116
|
+
|
|
117
|
+
with tempfile.TemporaryDirectory() as td:
|
|
118
|
+
repo_root = Path(td)
|
|
119
|
+
(repo_root / "package.json").write_text(json.dumps({"name": "demo"}), encoding="utf-8")
|
|
120
|
+
|
|
121
|
+
buf = io.StringIO()
|
|
122
|
+
with contextlib.redirect_stdout(buf):
|
|
123
|
+
rc = mod.apply(
|
|
124
|
+
repo_root,
|
|
125
|
+
skill_root,
|
|
126
|
+
update_generated=False,
|
|
127
|
+
dry_run=False,
|
|
128
|
+
detection=mod.detect(repo_root),
|
|
129
|
+
)
|
|
130
|
+
self.assertEqual(rc, 0)
|
|
131
|
+
|
|
132
|
+
wf_path = repo_root / "tasks" / "workflow-status.md"
|
|
133
|
+
self.assertTrue(wf_path.exists())
|
|
134
|
+
content = wf_path.read_text(encoding="utf-8")
|
|
135
|
+
self.assertIn(">> next <<", content)
|
|
136
|
+
self.assertIn("/brainstorm", content)
|
|
137
|
+
self.assertIn("/finish-feature", content)
|
|
138
|
+
self.assertIn("/release", content)
|
|
139
|
+
# All 14 steps present
|
|
140
|
+
self.assertIn("| 14 |", content)
|
|
141
|
+
|
|
142
|
+
def test_workflow_status_not_overwritten_on_rerun(self):
|
|
143
|
+
mod = _load_apply_module()
|
|
144
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
145
|
+
|
|
146
|
+
with tempfile.TemporaryDirectory() as td:
|
|
147
|
+
repo_root = Path(td)
|
|
148
|
+
(repo_root / "package.json").write_text(json.dumps({"name": "demo"}), encoding="utf-8")
|
|
149
|
+
|
|
150
|
+
# First run creates the file
|
|
151
|
+
buf = io.StringIO()
|
|
152
|
+
with contextlib.redirect_stdout(buf):
|
|
153
|
+
mod.apply(repo_root, skill_root, update_generated=False, dry_run=False, detection=mod.detect(repo_root))
|
|
154
|
+
|
|
155
|
+
# Simulate user progress by modifying the file
|
|
156
|
+
wf_path = repo_root / "tasks" / "workflow-status.md"
|
|
157
|
+
custom_content = wf_path.read_text(encoding="utf-8").replace(">> next <<", "done")
|
|
158
|
+
wf_path.write_text(custom_content, encoding="utf-8")
|
|
159
|
+
|
|
160
|
+
# Second run should NOT overwrite
|
|
161
|
+
buf = io.StringIO()
|
|
162
|
+
with contextlib.redirect_stdout(buf):
|
|
163
|
+
mod.apply(repo_root, skill_root, update_generated=False, dry_run=False, detection=mod.detect(repo_root))
|
|
164
|
+
|
|
165
|
+
self.assertEqual(wf_path.read_text(encoding="utf-8"), custom_content)
|
|
166
|
+
|
|
167
|
+
def test_existing_custom_claude_md_writes_sidecar(self):
|
|
168
|
+
mod = _load_apply_module()
|
|
169
|
+
skill_root = Path(__file__).resolve().parents[1]
|
|
170
|
+
|
|
171
|
+
with tempfile.TemporaryDirectory() as td:
|
|
172
|
+
repo_root = Path(td)
|
|
173
|
+
(repo_root / "package.json").write_text(json.dumps({"name": "demo"}), encoding="utf-8")
|
|
174
|
+
(repo_root / "CLAUDE.md").write_text("# Custom\n", encoding="utf-8")
|
|
175
|
+
|
|
176
|
+
buf = io.StringIO()
|
|
177
|
+
with contextlib.redirect_stdout(buf):
|
|
178
|
+
rc = mod.apply(
|
|
179
|
+
repo_root,
|
|
180
|
+
skill_root,
|
|
181
|
+
update_generated=False,
|
|
182
|
+
dry_run=False,
|
|
183
|
+
detection=mod.detect(repo_root),
|
|
184
|
+
)
|
|
185
|
+
self.assertEqual(rc, 0)
|
|
186
|
+
|
|
187
|
+
self.assertEqual((repo_root / "CLAUDE.md").read_text(encoding="utf-8"), "# Custom\n")
|
|
188
|
+
self.assertTrue((repo_root / "CLAUDE.setup-claude.md").exists())
|
|
189
|
+
self.assertIn("Notes:", buf.getvalue())
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
unittest.main()
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sk:setup-optimizer
|
|
3
|
+
description: "Diagnose, update workflow, enrich, and maintain CLAUDE.md. The single command to keep any CLAUDE.md current."
|
|
4
|
+
triggers:
|
|
5
|
+
- optimize claude
|
|
6
|
+
- optimize setup
|
|
7
|
+
- enrich claude
|
|
8
|
+
- maintain claude
|
|
9
|
+
- doctor claude
|
|
10
|
+
- check claude
|
|
11
|
+
- diagnose claude
|
|
12
|
+
- refresh claude
|
|
13
|
+
- update claude
|
|
14
|
+
- re-setup
|
|
15
|
+
allowed-tools:
|
|
16
|
+
- Bash
|
|
17
|
+
- Read
|
|
18
|
+
- Write
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
The single command to keep your CLAUDE.md current. Diagnoses problems, updates the workflow to the latest version, scans your codebase, and enriches with project context — all while preserving your customizations.
|
|
24
|
+
|
|
25
|
+
### What It Does
|
|
26
|
+
|
|
27
|
+
1. **Diagnoses** — finds missing sections, stale info, inconsistencies, and gaps
|
|
28
|
+
2. **Updates workflow** — refreshes the workflow section to the latest template version
|
|
29
|
+
3. **Discovers** — scans project structure, docs, and workflows
|
|
30
|
+
4. **Enriches** — merges discoveries into CLAUDE.md while preserving your edits
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
/sk:setup-optimizer
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Step 0: Diagnose
|
|
39
|
+
|
|
40
|
+
Before making any changes, runs a diagnostic pass on the existing CLAUDE.md:
|
|
41
|
+
|
|
42
|
+
- **Missing sections** — checks for essential sections (Workflow, Sub-Agent Patterns, Project Memory, Lessons Capture, Testing, Commands, etc.)
|
|
43
|
+
- **Stale content** — detects outdated info (stale model/route counts, removed dependencies, old command names like `/laravel-lint` instead of `/sk:lint`)
|
|
44
|
+
- **Inconsistencies** — compares documented vs actual project state (directories, scripts, workflows)
|
|
45
|
+
- **Section completeness** — flags sections that exist but are empty or have only placeholder text
|
|
46
|
+
- **Outdated workflow** — checks if the workflow matches the current 24-step TDD flow with hard gates
|
|
47
|
+
|
|
48
|
+
Reports findings before proceeding. If issues are found, they inform subsequent steps.
|
|
49
|
+
|
|
50
|
+
### Step 1: Update Workflow
|
|
51
|
+
|
|
52
|
+
If the workflow section is outdated or missing, replace it with the latest version:
|
|
53
|
+
|
|
54
|
+
**Current workflow (24 steps, TDD with hard gates):**
|
|
55
|
+
```
|
|
56
|
+
Read → Explore → Design → Accessibility → Plan → Branch → Migrate → Write Tests → Implement → Lint → Verify Tests → Security → Performance → Review → Finish
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**What gets updated:**
|
|
60
|
+
- Workflow table (24 steps with correct commands: `/sk:write-tests`, `/sk:lint`, `/sk:test`, `/sk:accessibility`, `/sk:perf`)
|
|
61
|
+
- Step details (TDD red/green/verify descriptions)
|
|
62
|
+
- Tracker rules (hard gates at 12, 14, 16, 20; optional steps 4, 5, 7, 18, 24)
|
|
63
|
+
- Step completion summary rule (NON-NEGOTIABLE)
|
|
64
|
+
- Bug fix flow section
|
|
65
|
+
- Sub-Agent Patterns section (if missing)
|
|
66
|
+
- Project Memory section (if missing)
|
|
67
|
+
- Lessons Capture section (if missing)
|
|
68
|
+
- Testing TDD section (if missing)
|
|
69
|
+
- 3-Strike Protocol (if missing)
|
|
70
|
+
|
|
71
|
+
**What gets preserved:**
|
|
72
|
+
- Everything marked with `<!-- LOCK -->` is never touched
|
|
73
|
+
- Project-specific content below the workflow (conventions, models, routes, architecture)
|
|
74
|
+
- Stack section, Build & Run section
|
|
75
|
+
- Any section with `<!-- EDITED -->` marker
|
|
76
|
+
|
|
77
|
+
**How it works:**
|
|
78
|
+
1. Read the latest workflow template from `~/.claude/skills/sk:setup-claude/templates/CLAUDE.md.template`
|
|
79
|
+
2. Compare with the current CLAUDE.md workflow section
|
|
80
|
+
3. If different, replace the workflow section (between `## Workflow` and the next `##` that isn't a workflow subsection)
|
|
81
|
+
4. Insert missing sections (Sub-Agent Patterns, Project Memory, etc.) in their correct positions
|
|
82
|
+
5. Preserve all `<!-- LOCK -->` and project-specific sections
|
|
83
|
+
|
|
84
|
+
### Step 2: Scan & Enrich
|
|
85
|
+
|
|
86
|
+
After workflow update, proceeds with codebase discovery and enrichment:
|
|
87
|
+
|
|
88
|
+
1. Scans project for directories, docs, and workflows
|
|
89
|
+
2. Reads your existing CLAUDE.md
|
|
90
|
+
3. Intelligently merges discoveries with your content (prioritizing diagnosed gaps)
|
|
91
|
+
4. Preserves any user customizations
|
|
92
|
+
5. Updates CLAUDE.md with comprehensive context
|
|
93
|
+
|
|
94
|
+
## What Gets Discovered
|
|
95
|
+
|
|
96
|
+
### Directories
|
|
97
|
+
Auto-documents: src/, tests/, docs/, public/, scripts/, config/, migrations/, and more (intelligently excludes node_modules/, vendor/, etc.)
|
|
98
|
+
|
|
99
|
+
### Documentation
|
|
100
|
+
Finds and links: README.md, CONTRIBUTING.md, CHANGELOG.md, docs/*.md, .github/CONTRIBUTING.md, and more
|
|
101
|
+
|
|
102
|
+
### Workflows
|
|
103
|
+
Detects: Makefile targets, npm/yarn scripts, GitHub Actions workflows
|
|
104
|
+
|
|
105
|
+
## Smart Features
|
|
106
|
+
|
|
107
|
+
### 1. User Customization Preservation
|
|
108
|
+
|
|
109
|
+
**Dual Detection:**
|
|
110
|
+
- Compares content to detect user edits
|
|
111
|
+
- Looks for `<!-- EDITED -->` markers
|
|
112
|
+
- Automatically preserves customized sections
|
|
113
|
+
|
|
114
|
+
**Auto-Locking:**
|
|
115
|
+
- `Important Context` section - auto-locked if has content
|
|
116
|
+
- `Known Issues` section - auto-locked
|
|
117
|
+
- Any section with `<!-- LOCK -->` comment - permanently locked
|
|
118
|
+
|
|
119
|
+
**Result:** Run multiple times during development without losing work!
|
|
120
|
+
|
|
121
|
+
### 2. Intelligent Merging
|
|
122
|
+
|
|
123
|
+
When updating CLAUDE.md:
|
|
124
|
+
- ✅ Keeps user customizations intact
|
|
125
|
+
- ✅ Updates auto-generated discovery sections
|
|
126
|
+
- ✅ Adds newly discovered items
|
|
127
|
+
- ✅ Reports what was preserved
|
|
128
|
+
|
|
129
|
+
### 3. Flexible Line Count
|
|
130
|
+
|
|
131
|
+
- **Target:** Stay under 200 lines when possible
|
|
132
|
+
- **Philosophy:** Comprehensive context > artificial line limit
|
|
133
|
+
- **Strategy:** Link to docs instead of inlining if extensive
|
|
134
|
+
|
|
135
|
+
## Maintenance Workflow
|
|
136
|
+
|
|
137
|
+
### During Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# After adding new test directory
|
|
141
|
+
mkdir tests
|
|
142
|
+
|
|
143
|
+
# Run optimizer to discover it
|
|
144
|
+
/optimize-claude
|
|
145
|
+
|
|
146
|
+
# CLAUDE.md now documents tests/ automatically!
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### When Customizing
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Edit Important Context with custom notes
|
|
153
|
+
vim CLAUDE.md
|
|
154
|
+
|
|
155
|
+
# The edit is automatically preserved on next run
|
|
156
|
+
/optimize-claude
|
|
157
|
+
|
|
158
|
+
# ✅ Your notes stay intact!
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Configuration
|
|
162
|
+
|
|
163
|
+
### Smart Defaults (Auto-Locked)
|
|
164
|
+
- **Important Context** - Your project decisions
|
|
165
|
+
- **Known Issues** - Problems/limitations
|
|
166
|
+
- Any section with `<!-- LOCK -->` comment
|
|
167
|
+
|
|
168
|
+
### Explicit Locking
|
|
169
|
+
|
|
170
|
+
```markdown
|
|
171
|
+
## My Custom Section
|
|
172
|
+
<!-- LOCK -->
|
|
173
|
+
This content will never be regenerated.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## When to Use
|
|
177
|
+
|
|
178
|
+
✅ **Use `/optimize-claude` when:**
|
|
179
|
+
- You've added new directories to your project
|
|
180
|
+
- You've created documentation files
|
|
181
|
+
- You want to refresh project context
|
|
182
|
+
- Monthly maintenance of CLAUDE.md
|
|
183
|
+
|
|
184
|
+
✅ **Safe to run multiple times during development** - Your customizations are always preserved!
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Claude setup tools library."""
|
|
2
|
+
|
|
3
|
+
from .detect import ProjectConfig, detect_project
|
|
4
|
+
from .sidecar import (
|
|
5
|
+
has_marker,
|
|
6
|
+
is_custom_file,
|
|
7
|
+
get_target_file,
|
|
8
|
+
add_marker,
|
|
9
|
+
format_output,
|
|
10
|
+
count_lines,
|
|
11
|
+
extract_sections,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ProjectConfig",
|
|
16
|
+
"detect_project",
|
|
17
|
+
"has_marker",
|
|
18
|
+
"is_custom_file",
|
|
19
|
+
"get_target_file",
|
|
20
|
+
"add_marker",
|
|
21
|
+
"format_output",
|
|
22
|
+
"count_lines",
|
|
23
|
+
"extract_sections",
|
|
24
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Auto-detection logic for project configuration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ProjectConfig:
|
|
11
|
+
"""Detected project configuration."""
|
|
12
|
+
|
|
13
|
+
language: str = "Unknown"
|
|
14
|
+
framework: str = "None"
|
|
15
|
+
database: str = "None"
|
|
16
|
+
ui: str = "None"
|
|
17
|
+
testing: str = "None"
|
|
18
|
+
description: str = ""
|
|
19
|
+
dev_command: str = ""
|
|
20
|
+
build_command: str = ""
|
|
21
|
+
test_command: str = ""
|
|
22
|
+
lint_command: str = ""
|
|
23
|
+
project_name: str = ""
|
|
24
|
+
project_dir: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_project(root_path: Path) -> ProjectConfig:
|
|
28
|
+
"""Auto-detect project configuration from files.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
root_path: Root directory of the project
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
ProjectConfig with detected values
|
|
35
|
+
"""
|
|
36
|
+
config = ProjectConfig()
|
|
37
|
+
config.project_dir = root_path.name
|
|
38
|
+
config.project_name = root_path.name
|
|
39
|
+
|
|
40
|
+
# Check package.json (Node.js)
|
|
41
|
+
package_json = root_path / "package.json"
|
|
42
|
+
if package_json.exists():
|
|
43
|
+
return _detect_nodejs(root_path, config)
|
|
44
|
+
|
|
45
|
+
# Check pyproject.toml or setup.py (Python)
|
|
46
|
+
if (root_path / "pyproject.toml").exists() or (root_path / "setup.py").exists():
|
|
47
|
+
return _detect_python(root_path, config)
|
|
48
|
+
|
|
49
|
+
# Check go.mod (Go)
|
|
50
|
+
if (root_path / "go.mod").exists():
|
|
51
|
+
return _detect_go(root_path, config)
|
|
52
|
+
|
|
53
|
+
# Check Cargo.toml (Rust)
|
|
54
|
+
if (root_path / "Cargo.toml").exists():
|
|
55
|
+
return _detect_rust(root_path, config)
|
|
56
|
+
|
|
57
|
+
# Default: Generic project
|
|
58
|
+
config.language = "JavaScript/TypeScript"
|
|
59
|
+
config.dev_command = "npm run dev"
|
|
60
|
+
config.build_command = "npm run build"
|
|
61
|
+
config.test_command = "npm test"
|
|
62
|
+
config.lint_command = "npm run lint"
|
|
63
|
+
|
|
64
|
+
return config
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _detect_nodejs(root_path: Path, config: ProjectConfig) -> ProjectConfig:
|
|
68
|
+
"""Detect Node.js/JavaScript project details."""
|
|
69
|
+
config.language = "JavaScript/TypeScript"
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
with open(root_path / "package.json") as f:
|
|
73
|
+
pkg = json.load(f)
|
|
74
|
+
|
|
75
|
+
# Extract project info
|
|
76
|
+
config.project_name = pkg.get("name", config.project_name)
|
|
77
|
+
config.description = pkg.get("description", "")
|
|
78
|
+
|
|
79
|
+
# Detect framework
|
|
80
|
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
81
|
+
|
|
82
|
+
if "react" in deps or "next" in deps:
|
|
83
|
+
config.framework = "React" if "react" in deps else "Next.js"
|
|
84
|
+
config.ui = "React"
|
|
85
|
+
elif "vue" in deps or "nuxt" in deps:
|
|
86
|
+
config.framework = "Vue" if "vue" in deps else "Nuxt"
|
|
87
|
+
config.ui = "Vue"
|
|
88
|
+
elif "angular" in deps:
|
|
89
|
+
config.framework = "Angular"
|
|
90
|
+
config.ui = "Angular"
|
|
91
|
+
elif "svelte" in deps:
|
|
92
|
+
config.framework = "Svelte"
|
|
93
|
+
config.ui = "Svelte"
|
|
94
|
+
|
|
95
|
+
# Detect UI framework
|
|
96
|
+
if "tailwindcss" in deps:
|
|
97
|
+
config.ui = "Tailwind CSS"
|
|
98
|
+
elif "styled-components" in deps:
|
|
99
|
+
config.ui = "styled-components"
|
|
100
|
+
elif "sass" in deps or "node-sass" in deps:
|
|
101
|
+
config.ui = "Sass"
|
|
102
|
+
|
|
103
|
+
# Detect database
|
|
104
|
+
if "prisma" in deps:
|
|
105
|
+
config.database = "Prisma"
|
|
106
|
+
elif "drizzle-orm" in deps:
|
|
107
|
+
config.database = "Drizzle"
|
|
108
|
+
elif "mongoose" in deps:
|
|
109
|
+
config.database = "MongoDB (Mongoose)"
|
|
110
|
+
elif "sequelize" in deps:
|
|
111
|
+
config.database = "PostgreSQL (Sequelize)"
|
|
112
|
+
elif "typeorm" in deps:
|
|
113
|
+
config.database = "TypeORM"
|
|
114
|
+
|
|
115
|
+
# Detect testing
|
|
116
|
+
if "vitest" in deps:
|
|
117
|
+
config.testing = "Vitest"
|
|
118
|
+
elif "jest" in deps:
|
|
119
|
+
config.testing = "Jest"
|
|
120
|
+
elif "mocha" in deps:
|
|
121
|
+
config.testing = "Mocha"
|
|
122
|
+
|
|
123
|
+
# Extract commands from package.json scripts
|
|
124
|
+
scripts = pkg.get("scripts", {})
|
|
125
|
+
config.dev_command = f"npm run {_find_script(scripts, ['dev', 'start'])}" if _find_script(scripts, ['dev', 'start']) else "npm run dev"
|
|
126
|
+
config.build_command = f"npm run {_find_script(scripts, ['build'])}" if _find_script(scripts, ['build']) else "npm run build"
|
|
127
|
+
config.test_command = f"npm test"
|
|
128
|
+
config.lint_command = f"npm run {_find_script(scripts, ['lint'])}" if _find_script(scripts, ['lint']) else "npm run lint"
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f"Warning: Could not fully parse package.json: {e}")
|
|
132
|
+
|
|
133
|
+
return config
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _detect_python(root_path: Path, config: ProjectConfig) -> ProjectConfig:
|
|
137
|
+
"""Detect Python project details."""
|
|
138
|
+
config.language = "Python"
|
|
139
|
+
|
|
140
|
+
# Try pyproject.toml first
|
|
141
|
+
pyproject = root_path / "pyproject.toml"
|
|
142
|
+
if pyproject.exists():
|
|
143
|
+
try:
|
|
144
|
+
content = pyproject.read_text()
|
|
145
|
+
|
|
146
|
+
# Simple detection without external dependency
|
|
147
|
+
if "django" in content:
|
|
148
|
+
config.framework = "Django"
|
|
149
|
+
elif "fastapi" in content:
|
|
150
|
+
config.framework = "FastAPI"
|
|
151
|
+
elif "flask" in content:
|
|
152
|
+
config.framework = "Flask"
|
|
153
|
+
elif "starlette" in content:
|
|
154
|
+
config.framework = "Starlette"
|
|
155
|
+
|
|
156
|
+
if "sqlalchemy" in content or "alembic" in content:
|
|
157
|
+
config.database = "SQLAlchemy"
|
|
158
|
+
elif "django" in content:
|
|
159
|
+
config.database = "Django ORM"
|
|
160
|
+
|
|
161
|
+
if "pytest" in content:
|
|
162
|
+
config.testing = "pytest"
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"Warning: Could not parse pyproject.toml: {e}")
|
|
165
|
+
|
|
166
|
+
# Default Python commands
|
|
167
|
+
config.dev_command = "python -m uvicorn main:app --reload"
|
|
168
|
+
config.build_command = "pip install -e ."
|
|
169
|
+
config.test_command = "pytest"
|
|
170
|
+
config.lint_command = "pylint ."
|
|
171
|
+
config.description = "Python project"
|
|
172
|
+
|
|
173
|
+
return config
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _detect_go(root_path: Path, config: ProjectConfig) -> ProjectConfig:
|
|
177
|
+
"""Detect Go project details."""
|
|
178
|
+
config.language = "Go"
|
|
179
|
+
config.dev_command = "go run ."
|
|
180
|
+
config.build_command = "go build -o bin/app ."
|
|
181
|
+
config.test_command = "go test ./..."
|
|
182
|
+
config.lint_command = "golangci-lint run"
|
|
183
|
+
config.description = "Go project"
|
|
184
|
+
|
|
185
|
+
return config
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _detect_rust(root_path: Path, config: ProjectConfig) -> ProjectConfig:
|
|
189
|
+
"""Detect Rust project details."""
|
|
190
|
+
config.language = "Rust"
|
|
191
|
+
config.dev_command = "cargo run"
|
|
192
|
+
config.build_command = "cargo build --release"
|
|
193
|
+
config.test_command = "cargo test"
|
|
194
|
+
config.lint_command = "cargo clippy"
|
|
195
|
+
config.description = "Rust project"
|
|
196
|
+
|
|
197
|
+
return config
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _find_script(scripts: dict, candidates: list) -> Optional[str]:
|
|
201
|
+
"""Find first matching script from candidates."""
|
|
202
|
+
for candidate in candidates:
|
|
203
|
+
if candidate in scripts:
|
|
204
|
+
return candidate
|
|
205
|
+
return None
|