shipwright-cli 1.7.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +926 -0
  3. package/claude-code/CLAUDE.md.shipwright +125 -0
  4. package/claude-code/hooks/notify-idle.sh +35 -0
  5. package/claude-code/hooks/pre-compact-save.sh +57 -0
  6. package/claude-code/hooks/task-completed.sh +170 -0
  7. package/claude-code/hooks/teammate-idle.sh +68 -0
  8. package/claude-code/settings.json.template +184 -0
  9. package/completions/_shipwright +140 -0
  10. package/completions/shipwright.bash +89 -0
  11. package/completions/shipwright.fish +107 -0
  12. package/docs/KNOWN-ISSUES.md +199 -0
  13. package/docs/TIPS.md +331 -0
  14. package/docs/definition-of-done.example.md +16 -0
  15. package/docs/patterns/README.md +139 -0
  16. package/docs/patterns/audit-loop.md +149 -0
  17. package/docs/patterns/bug-hunt.md +183 -0
  18. package/docs/patterns/feature-implementation.md +159 -0
  19. package/docs/patterns/refactoring.md +183 -0
  20. package/docs/patterns/research-exploration.md +144 -0
  21. package/docs/patterns/test-generation.md +173 -0
  22. package/package.json +49 -0
  23. package/scripts/adapters/docker-deploy.sh +50 -0
  24. package/scripts/adapters/fly-deploy.sh +41 -0
  25. package/scripts/adapters/iterm2-adapter.sh +122 -0
  26. package/scripts/adapters/railway-deploy.sh +34 -0
  27. package/scripts/adapters/tmux-adapter.sh +87 -0
  28. package/scripts/adapters/vercel-deploy.sh +35 -0
  29. package/scripts/adapters/wezterm-adapter.sh +103 -0
  30. package/scripts/cct +242 -0
  31. package/scripts/cct-cleanup.sh +172 -0
  32. package/scripts/cct-cost.sh +590 -0
  33. package/scripts/cct-daemon.sh +3189 -0
  34. package/scripts/cct-doctor.sh +328 -0
  35. package/scripts/cct-fix.sh +478 -0
  36. package/scripts/cct-fleet.sh +904 -0
  37. package/scripts/cct-init.sh +282 -0
  38. package/scripts/cct-logs.sh +273 -0
  39. package/scripts/cct-loop.sh +1332 -0
  40. package/scripts/cct-memory.sh +1148 -0
  41. package/scripts/cct-pipeline.sh +3844 -0
  42. package/scripts/cct-prep.sh +1352 -0
  43. package/scripts/cct-ps.sh +168 -0
  44. package/scripts/cct-reaper.sh +390 -0
  45. package/scripts/cct-session.sh +284 -0
  46. package/scripts/cct-status.sh +169 -0
  47. package/scripts/cct-templates.sh +242 -0
  48. package/scripts/cct-upgrade.sh +422 -0
  49. package/scripts/cct-worktree.sh +405 -0
  50. package/scripts/postinstall.mjs +96 -0
  51. package/templates/pipelines/autonomous.json +71 -0
  52. package/templates/pipelines/cost-aware.json +95 -0
  53. package/templates/pipelines/deployed.json +79 -0
  54. package/templates/pipelines/enterprise.json +114 -0
  55. package/templates/pipelines/fast.json +63 -0
  56. package/templates/pipelines/full.json +104 -0
  57. package/templates/pipelines/hotfix.json +63 -0
  58. package/templates/pipelines/standard.json +91 -0
  59. package/tmux/claude-teams-overlay.conf +109 -0
  60. package/tmux/templates/architecture.json +19 -0
  61. package/tmux/templates/bug-fix.json +24 -0
  62. package/tmux/templates/code-review.json +24 -0
  63. package/tmux/templates/devops.json +19 -0
  64. package/tmux/templates/documentation.json +19 -0
  65. package/tmux/templates/exploration.json +19 -0
  66. package/tmux/templates/feature-dev.json +24 -0
  67. package/tmux/templates/full-stack.json +24 -0
  68. package/tmux/templates/migration.json +24 -0
  69. package/tmux/templates/refactor.json +19 -0
  70. package/tmux/templates/security-audit.json +24 -0
  71. package/tmux/templates/testing.json +24 -0
  72. package/tmux/tmux.conf +167 -0
@@ -0,0 +1,1352 @@
1
+ #!/usr/bin/env bash
2
+ # ╔═══════════════════════════════════════════════════════════════════════════╗
3
+ # ║ shipwright prep — Repository Preparation for Agent Teams ║
4
+ # ║ Analyze repos · Generate configs · Equip autonomous agents ║
5
+ # ╚═══════════════════════════════════════════════════════════════════════════╝
6
+ set -euo pipefail
7
+
8
+ VERSION="1.7.0"
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+
11
+ # ─── Handle subcommands ───────────────────────────────────────────────────────
12
+ if [[ "${1:-}" == "test" ]]; then
13
+ shift
14
+ exec "$SCRIPT_DIR/cct-prep-test.sh" "$@"
15
+ fi
16
+
17
+ # ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
18
+ CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
19
+ PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
20
+ BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
21
+ GREEN='\033[38;2;74;222;128m' # success
22
+ YELLOW='\033[38;2;250;204;21m' # warning
23
+ RED='\033[38;2;248;113;113m' # error
24
+ DIM='\033[2m'
25
+ BOLD='\033[1m'
26
+ RESET='\033[0m'
27
+
28
+ # ─── Output Helpers ─────────────────────────────────────────────────────────
29
+ info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
30
+ success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
31
+ warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
32
+ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
33
+
34
+ now_iso() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
35
+
36
+ # ─── Defaults ───────────────────────────────────────────────────────────────
37
+ FORCE=false
38
+ CHECK_ONLY=false
39
+ UPDATE_MODE=false
40
+ WITH_CLAUDE=false
41
+ PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
42
+
43
+ # Detection results
44
+ LANG_DETECTED=""
45
+ FRAMEWORK=""
46
+ PACKAGE_MANAGER=""
47
+ TEST_CMD=""
48
+ BUILD_CMD=""
49
+ LINT_CMD=""
50
+ FORMAT_CMD=""
51
+ DEV_CMD=""
52
+ TEST_FRAMEWORK=""
53
+ HAS_DOCKER=false
54
+ HAS_COMPOSE=false
55
+ HAS_CI=false
56
+ HAS_MAKEFILE=false
57
+ PROJECT_NAME=""
58
+
59
+ # Structure scan results
60
+ SRC_DIRS=""
61
+ TEST_DIRS=""
62
+ DOC_DIRS=""
63
+ CONFIG_FILES=""
64
+ ENTRY_POINTS=""
65
+ SRC_FILE_COUNT=0
66
+ TEST_FILE_COUNT=0
67
+ TOTAL_LINES=0
68
+
69
+ # Pattern extraction results
70
+ IMPORT_STYLE=""
71
+ NAMING_CONVENTION=""
72
+ HAS_ROUTES=false
73
+ HAS_DB=false
74
+ HAS_MIDDLEWARE=false
75
+ ROUTE_PATTERNS=""
76
+ DB_PATTERNS=""
77
+
78
+ # Tracking generated files
79
+ GENERATED_FILES=()
80
+
81
+ # ─── Help ───────────────────────────────────────────────────────────────────
82
+
83
+ show_help() {
84
+ echo -e "${CYAN}${BOLD}shipwright prep${RESET} ${DIM}v${VERSION}${RESET} — Prepare a repository for autonomous agent development"
85
+ echo ""
86
+ echo -e "${BOLD}USAGE${RESET}"
87
+ echo -e " ${CYAN}shipwright prep${RESET} [options]"
88
+ echo ""
89
+ echo -e "${BOLD}OPTIONS${RESET}"
90
+ echo -e " ${CYAN}--force${RESET} Overwrite existing files"
91
+ echo -e " ${CYAN}--check${RESET} Audit existing prep (dry run)"
92
+ echo -e " ${CYAN}--update${RESET} Refresh auto-generated sections only"
93
+ echo -e " ${CYAN}--with-claude${RESET} Deep analysis using Claude Code (slower, richer)"
94
+ echo -e " ${CYAN}--help, -h${RESET} Show this help message"
95
+ echo ""
96
+ echo -e "${BOLD}EXAMPLES${RESET}"
97
+ echo -e " ${DIM}shipwright prep${RESET} # Full analysis + generation"
98
+ echo -e " ${DIM}shipwright prep --check${RESET} # Audit quality"
99
+ echo -e " ${DIM}shipwright prep --update${RESET} # Refresh without overwriting user edits"
100
+ echo -e " ${DIM}shipwright prep --force${RESET} # Regenerate everything"
101
+ echo -e " ${DIM}shipwright prep --with-claude${RESET} # Deep analysis with Claude"
102
+ echo ""
103
+ echo -e "${BOLD}GENERATED FILES${RESET}"
104
+ echo -e " ${DIM}.claude/CLAUDE.md${RESET} Project context for Claude Code"
105
+ echo -e " ${DIM}.claude/settings.json${RESET} Permission allowlists"
106
+ echo -e " ${DIM}.claude/ARCHITECTURE.md${RESET} System architecture overview"
107
+ echo -e " ${DIM}.claude/CODING-STANDARDS.md${RESET} Coding conventions"
108
+ echo -e " ${DIM}.claude/DEFINITION-OF-DONE.md${RESET} Completion checklist"
109
+ echo -e " ${DIM}.claude/agents/*.md${RESET} Agent role definitions"
110
+ echo -e " ${DIM}.claude/hooks/*.sh${RESET} Pre/post action hooks"
111
+ echo -e " ${DIM}.github/ISSUE_TEMPLATE/agent-task.md${RESET} Agent task template"
112
+ echo ""
113
+ echo -e "${DIM}Docs: https://sethdford.github.io/shipwright | GitHub: https://github.com/sethdford/shipwright${RESET}"
114
+ }
115
+
116
+ # ─── CLI Argument Parsing ───────────────────────────────────────────────────
117
+
118
+ for arg in "$@"; do
119
+ case "$arg" in
120
+ --force) FORCE=true ;;
121
+ --check) CHECK_ONLY=true ;;
122
+ --update) UPDATE_MODE=true ;;
123
+ --with-claude) WITH_CLAUDE=true ;;
124
+ --help|-h) show_help; exit 0 ;;
125
+ *)
126
+ error "Unknown option: $arg"
127
+ echo ""
128
+ show_help
129
+ exit 1
130
+ ;;
131
+ esac
132
+ done
133
+
134
+ # ─── prep_init ──────────────────────────────────────────────────────────────
135
+
136
+ prep_init() {
137
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
138
+ error "Not inside a git repository"
139
+ exit 1
140
+ fi
141
+ PROJECT_ROOT="$(git rev-parse --show-toplevel)"
142
+ PROJECT_NAME="$(basename "$PROJECT_ROOT")"
143
+ mkdir -p "$PROJECT_ROOT/.claude"
144
+ mkdir -p "$PROJECT_ROOT/.claude/hooks"
145
+ mkdir -p "$PROJECT_ROOT/.claude/agents"
146
+ mkdir -p "$PROJECT_ROOT/.github/ISSUE_TEMPLATE"
147
+ }
148
+
149
+ # ─── should_write — Idempotency gating ─────────────────────────────────────
150
+
151
+ # Returns 0 if we should write, 1 if we should skip
152
+ should_write() {
153
+ local filepath="$1"
154
+ if [[ ! -f "$filepath" ]]; then
155
+ return 0 # File doesn't exist — write it
156
+ fi
157
+ if $FORCE; then
158
+ return 0 # Force mode — overwrite
159
+ fi
160
+ if $UPDATE_MODE; then
161
+ # In update mode, only write if file has auto markers
162
+ if grep -q "<!-- cct:auto-start -->" "$filepath" 2>/dev/null; then
163
+ return 0
164
+ fi
165
+ info "Skipping ${filepath##"$PROJECT_ROOT"/} (no auto markers, user-customized)"
166
+ return 1
167
+ fi
168
+ info "Skipping ${filepath##"$PROJECT_ROOT"/} (exists, use --force to overwrite)"
169
+ return 1
170
+ }
171
+
172
+ # ─── update_auto_section — Replace content between markers ──────────────────
173
+
174
+ update_auto_section() {
175
+ local filepath="$1"
176
+ local new_content="$2"
177
+
178
+ if $UPDATE_MODE && [[ -f "$filepath" ]] && grep -q "<!-- cct:auto-start -->" "$filepath"; then
179
+ # Replace content between markers, preserve everything else
180
+ local before after
181
+ before=$(sed '/<!-- cct:auto-start -->/,$d' "$filepath")
182
+ after=$(sed '1,/<!-- cct:auto-end -->/d' "$filepath")
183
+ {
184
+ echo "$before"
185
+ echo "$new_content"
186
+ echo "$after"
187
+ } > "$filepath"
188
+ else
189
+ echo "$new_content" > "$filepath"
190
+ fi
191
+ }
192
+
193
+ # ─── track_file — Track a generated file ────────────────────────────────────
194
+
195
+ track_file() {
196
+ local filepath="$1"
197
+ local lines
198
+ lines=$(wc -l < "$filepath" | tr -d ' ')
199
+ GENERATED_FILES+=("${filepath##"$PROJECT_ROOT"/}|${lines}")
200
+ }
201
+
202
+ # ─── prep_detect_stack ──────────────────────────────────────────────────────
203
+
204
+ prep_detect_stack() {
205
+ local root="$PROJECT_ROOT"
206
+ info "Detecting project stack..."
207
+
208
+ # ── Language & Framework ──
209
+
210
+ if [[ -f "$root/package.json" ]]; then
211
+ LANG_DETECTED="nodejs"
212
+ local deps
213
+ deps=$(cat "$root/package.json")
214
+
215
+ # Detect framework from dependencies
216
+ if echo "$deps" | grep -q '"next"'; then
217
+ FRAMEWORK="next.js"
218
+ LANG_DETECTED="typescript"
219
+ elif echo "$deps" | grep -q '"nuxt"'; then
220
+ FRAMEWORK="nuxt"
221
+ LANG_DETECTED="typescript"
222
+ elif echo "$deps" | grep -q '"@angular/core"'; then
223
+ FRAMEWORK="angular"
224
+ LANG_DETECTED="typescript"
225
+ elif echo "$deps" | grep -q '"vue"'; then
226
+ FRAMEWORK="vue"
227
+ elif echo "$deps" | grep -q '"react"'; then
228
+ FRAMEWORK="react"
229
+ elif echo "$deps" | grep -q '"@nestjs/core"'; then
230
+ FRAMEWORK="nestjs"
231
+ LANG_DETECTED="typescript"
232
+ elif echo "$deps" | grep -q '"express"'; then
233
+ FRAMEWORK="express"
234
+ elif echo "$deps" | grep -q '"fastify"'; then
235
+ FRAMEWORK="fastify"
236
+ elif echo "$deps" | grep -q '"hono"'; then
237
+ FRAMEWORK="hono"
238
+ fi
239
+
240
+ # TypeScript override
241
+ if echo "$deps" | grep -q '"typescript"'; then
242
+ LANG_DETECTED="typescript"
243
+ fi
244
+
245
+ # Detect test framework
246
+ if echo "$deps" | grep -q '"vitest"'; then
247
+ TEST_FRAMEWORK="vitest"
248
+ elif echo "$deps" | grep -q '"jest"'; then
249
+ TEST_FRAMEWORK="jest"
250
+ elif echo "$deps" | grep -q '"mocha"'; then
251
+ TEST_FRAMEWORK="mocha"
252
+ elif echo "$deps" | grep -q '"ava"'; then
253
+ TEST_FRAMEWORK="ava"
254
+ fi
255
+
256
+ # Detect package manager
257
+ if [[ -f "$root/pnpm-lock.yaml" ]]; then
258
+ PACKAGE_MANAGER="pnpm"
259
+ elif [[ -f "$root/yarn.lock" ]]; then
260
+ PACKAGE_MANAGER="yarn"
261
+ elif [[ -f "$root/bun.lockb" ]]; then
262
+ PACKAGE_MANAGER="bun"
263
+ else
264
+ PACKAGE_MANAGER="npm"
265
+ fi
266
+
267
+ # Detect commands from package.json scripts
268
+ local scripts_json
269
+ scripts_json=$(jq -r '.scripts // {}' "$root/package.json" 2>/dev/null || echo "{}")
270
+
271
+ if [[ -z "$TEST_CMD" ]]; then
272
+ local has_test
273
+ has_test=$(echo "$scripts_json" | jq -r '.test // ""' 2>/dev/null)
274
+ if [[ -n "$has_test" && "$has_test" != "null" && "$has_test" != *"no test specified"* ]]; then
275
+ TEST_CMD="$PACKAGE_MANAGER test"
276
+ fi
277
+ fi
278
+
279
+ local has_build
280
+ has_build=$(echo "$scripts_json" | jq -r '.build // ""' 2>/dev/null)
281
+ if [[ -n "$has_build" && "$has_build" != "null" ]]; then
282
+ BUILD_CMD="$PACKAGE_MANAGER run build"
283
+ fi
284
+
285
+ local has_lint
286
+ has_lint=$(echo "$scripts_json" | jq -r '.lint // ""' 2>/dev/null)
287
+ if [[ -n "$has_lint" && "$has_lint" != "null" ]]; then
288
+ LINT_CMD="$PACKAGE_MANAGER run lint"
289
+ fi
290
+
291
+ local has_format
292
+ has_format=$(echo "$scripts_json" | jq -r '.format // ""' 2>/dev/null)
293
+ if [[ -n "$has_format" && "$has_format" != "null" ]]; then
294
+ FORMAT_CMD="$PACKAGE_MANAGER run format"
295
+ fi
296
+
297
+ local has_dev
298
+ has_dev=$(echo "$scripts_json" | jq -r '.dev // ""' 2>/dev/null)
299
+ if [[ -n "$has_dev" && "$has_dev" != "null" ]]; then
300
+ DEV_CMD="$PACKAGE_MANAGER run dev"
301
+ fi
302
+
303
+ elif [[ -f "$root/go.mod" ]]; then
304
+ LANG_DETECTED="go"
305
+ PACKAGE_MANAGER="go modules"
306
+ TEST_CMD="go test ./..."
307
+ BUILD_CMD="go build ./..."
308
+ LINT_CMD="golangci-lint run"
309
+ # Detect framework
310
+ if grep -q "gin-gonic" "$root/go.mod" 2>/dev/null; then
311
+ FRAMEWORK="gin"
312
+ elif grep -q "labstack/echo" "$root/go.mod" 2>/dev/null; then
313
+ FRAMEWORK="echo"
314
+ elif grep -q "go-chi/chi" "$root/go.mod" 2>/dev/null; then
315
+ FRAMEWORK="chi"
316
+ elif grep -q "gofiber/fiber" "$root/go.mod" 2>/dev/null; then
317
+ FRAMEWORK="fiber"
318
+ fi
319
+
320
+ elif [[ -f "$root/Cargo.toml" ]]; then
321
+ LANG_DETECTED="rust"
322
+ PACKAGE_MANAGER="cargo"
323
+ TEST_CMD="cargo test"
324
+ BUILD_CMD="cargo build"
325
+ LINT_CMD="cargo clippy"
326
+ FORMAT_CMD="cargo fmt"
327
+ if grep -q "actix-web" "$root/Cargo.toml" 2>/dev/null; then
328
+ FRAMEWORK="actix-web"
329
+ elif grep -q "axum" "$root/Cargo.toml" 2>/dev/null; then
330
+ FRAMEWORK="axum"
331
+ elif grep -q "rocket" "$root/Cargo.toml" 2>/dev/null; then
332
+ FRAMEWORK="rocket"
333
+ fi
334
+
335
+ elif [[ -f "$root/pyproject.toml" || -f "$root/setup.py" || -f "$root/requirements.txt" ]]; then
336
+ LANG_DETECTED="python"
337
+ if [[ -f "$root/pyproject.toml" ]]; then
338
+ if grep -q "poetry" "$root/pyproject.toml" 2>/dev/null; then
339
+ PACKAGE_MANAGER="poetry"
340
+ elif grep -q "pdm" "$root/pyproject.toml" 2>/dev/null; then
341
+ PACKAGE_MANAGER="pdm"
342
+ else
343
+ PACKAGE_MANAGER="pip"
344
+ fi
345
+ else
346
+ PACKAGE_MANAGER="pip"
347
+ fi
348
+
349
+ # Detect framework
350
+ local py_deps=""
351
+ [[ -f "$root/requirements.txt" ]] && py_deps=$(cat "$root/requirements.txt")
352
+ [[ -f "$root/pyproject.toml" ]] && py_deps="$py_deps$(cat "$root/pyproject.toml")"
353
+ if echo "$py_deps" | grep -qi "django"; then
354
+ FRAMEWORK="django"
355
+ elif echo "$py_deps" | grep -qi "fastapi"; then
356
+ FRAMEWORK="fastapi"
357
+ elif echo "$py_deps" | grep -qi "flask"; then
358
+ FRAMEWORK="flask"
359
+ fi
360
+
361
+ # Detect test command
362
+ if [[ -f "$root/pyproject.toml" ]] && grep -q "pytest" "$root/pyproject.toml" 2>/dev/null; then
363
+ TEST_CMD="pytest"
364
+ TEST_FRAMEWORK="pytest"
365
+ elif [[ -d "$root/tests" ]]; then
366
+ TEST_CMD="pytest"
367
+ TEST_FRAMEWORK="pytest"
368
+ fi
369
+ LINT_CMD="ruff check ."
370
+ FORMAT_CMD="ruff format ."
371
+
372
+ elif [[ -f "$root/Gemfile" ]]; then
373
+ LANG_DETECTED="ruby"
374
+ PACKAGE_MANAGER="bundler"
375
+ if grep -q "rails" "$root/Gemfile" 2>/dev/null; then
376
+ FRAMEWORK="rails"
377
+ TEST_CMD="bundle exec rails test"
378
+ fi
379
+ if grep -q "rspec" "$root/Gemfile" 2>/dev/null; then
380
+ TEST_CMD="bundle exec rspec"
381
+ TEST_FRAMEWORK="rspec"
382
+ else
383
+ TEST_FRAMEWORK="minitest"
384
+ fi
385
+ LINT_CMD="bundle exec rubocop"
386
+
387
+ elif [[ -f "$root/pom.xml" ]]; then
388
+ LANG_DETECTED="java"
389
+ PACKAGE_MANAGER="maven"
390
+ TEST_CMD="mvn test"
391
+ BUILD_CMD="mvn package"
392
+ if grep -q "spring-boot" "$root/pom.xml" 2>/dev/null; then
393
+ FRAMEWORK="spring-boot"
394
+ fi
395
+
396
+ elif [[ -f "$root/build.gradle" || -f "$root/build.gradle.kts" ]]; then
397
+ LANG_DETECTED="java"
398
+ PACKAGE_MANAGER="gradle"
399
+ TEST_CMD="./gradlew test"
400
+ BUILD_CMD="./gradlew build"
401
+ if grep -q "spring-boot" "$root/build.gradle" 2>/dev/null || \
402
+ grep -q "spring-boot" "$root/build.gradle.kts" 2>/dev/null; then
403
+ FRAMEWORK="spring-boot"
404
+ fi
405
+ fi
406
+
407
+ # ── Infra detection ──
408
+
409
+ [[ -f "$root/Dockerfile" ]] && HAS_DOCKER=true
410
+ [[ -f "$root/docker-compose.yml" || -f "$root/docker-compose.yaml" || -f "$root/compose.yml" ]] && HAS_COMPOSE=true
411
+ [[ -d "$root/.github/workflows" ]] && HAS_CI=true
412
+ [[ -f "$root/Makefile" ]] && HAS_MAKEFILE=true
413
+
414
+ # Makefile fallbacks
415
+ if $HAS_MAKEFILE; then
416
+ [[ -z "$TEST_CMD" ]] && grep -q "^test:" "$root/Makefile" 2>/dev/null && TEST_CMD="make test"
417
+ [[ -z "$BUILD_CMD" ]] && grep -q "^build:" "$root/Makefile" 2>/dev/null && BUILD_CMD="make build"
418
+ [[ -z "$LINT_CMD" ]] && grep -q "^lint:" "$root/Makefile" 2>/dev/null && LINT_CMD="make lint"
419
+ fi
420
+
421
+ # Summary
422
+ success "Stack: ${BOLD}${LANG_DETECTED:-unknown}${RESET}" \
423
+ "${FRAMEWORK:+/ ${BOLD}${FRAMEWORK}${RESET}}" \
424
+ "${PACKAGE_MANAGER:+(${DIM}${PACKAGE_MANAGER}${RESET})}"
425
+ }
426
+
427
+ # ─── prep_scan_structure ────────────────────────────────────────────────────
428
+
429
+ prep_scan_structure() {
430
+ local root="$PROJECT_ROOT"
431
+ info "Scanning project structure..."
432
+
433
+ # Identify key directories
434
+ local dirs=()
435
+ for d in src lib app pkg cmd internal api routes controllers models views \
436
+ components pages services utils helpers middleware schemas types; do
437
+ [[ -d "$root/$d" ]] && dirs+=("$d")
438
+ done
439
+ SRC_DIRS="${dirs[*]:-}"
440
+
441
+ # Test directories
442
+ local tdirs=()
443
+ for d in tests test spec __tests__ test_* *_test; do
444
+ [[ -d "$root/$d" ]] && tdirs+=("$d")
445
+ done
446
+ TEST_DIRS="${tdirs[*]:-}"
447
+
448
+ # Doc directories
449
+ local ddirs=()
450
+ for d in docs doc documentation wiki; do
451
+ [[ -d "$root/$d" ]] && ddirs+=("$d")
452
+ done
453
+ DOC_DIRS="${ddirs[*]:-}"
454
+
455
+ # Config files
456
+ local configs=()
457
+ for f in .env.example .env.sample .eslintrc.js .eslintrc.json .eslintrc.yml \
458
+ .prettierrc .prettierrc.json tsconfig.json jest.config.js jest.config.ts \
459
+ vitest.config.ts webpack.config.js vite.config.ts next.config.js \
460
+ .babelrc babel.config.js tailwind.config.js postcss.config.js \
461
+ pyproject.toml setup.cfg tox.ini .flake8 .pylintrc \
462
+ .rubocop.yml .rspec Cargo.toml go.mod; do
463
+ [[ -f "$root/$f" ]] && configs+=("$f")
464
+ done
465
+ CONFIG_FILES="${configs[*]:-}"
466
+
467
+ # Entry points
468
+ local entries=()
469
+ for f in src/index.ts src/index.js src/main.ts src/main.js src/app.ts src/app.js \
470
+ index.ts index.js app.ts app.js server.ts server.js \
471
+ main.go cmd/main.go src/main.rs src/lib.rs \
472
+ app.py main.py manage.py wsgi.py asgi.py \
473
+ config.ru app.rb; do
474
+ [[ -f "$root/$f" ]] && entries+=("$f")
475
+ done
476
+ ENTRY_POINTS="${entries[*]:-}"
477
+
478
+ # File counts
479
+ local src_ext="ts tsx js jsx py rb go rs java kt"
480
+ SRC_FILE_COUNT=0
481
+ TEST_FILE_COUNT=0
482
+ for ext in $src_ext; do
483
+ local count
484
+ count=$(find "$root" -name "*.${ext}" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/vendor/*" -not -path "*/target/*" 2>/dev/null | wc -l | tr -d ' ')
485
+ SRC_FILE_COUNT=$((SRC_FILE_COUNT + count))
486
+ done
487
+
488
+ # Count test files
489
+ TEST_FILE_COUNT=$(find "$root" \( -name "*.test.*" -o -name "*.spec.*" -o -name "*_test.*" -o -name "test_*" \) \
490
+ -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | wc -l | tr -d ' ')
491
+
492
+ # Total lines (approximation from source files)
493
+ TOTAL_LINES=$(find "$root" \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \
494
+ -o -name "*.py" -o -name "*.rb" -o -name "*.go" -o -name "*.rs" -o -name "*.java" \) \
495
+ -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/vendor/*" \
496
+ -not -path "*/target/*" 2>/dev/null -exec cat {} + 2>/dev/null | wc -l | tr -d ' ')
497
+
498
+ success "Found ${BOLD}${SRC_FILE_COUNT}${RESET} source files, ${BOLD}${TEST_FILE_COUNT}${RESET} test files (${DIM}~${TOTAL_LINES} lines${RESET})"
499
+ }
500
+
501
+ # ─── prep_extract_patterns ──────────────────────────────────────────────────
502
+
503
+ prep_extract_patterns() {
504
+ local root="$PROJECT_ROOT"
505
+ info "Extracting code patterns..."
506
+
507
+ # ── Import style ──
508
+ if [[ "$LANG_DETECTED" == "nodejs" || "$LANG_DETECTED" == "typescript" ]]; then
509
+ local es_count cjs_count
510
+ es_count=$(grep -rl "^import " "$root/src" "$root/app" "$root/lib" 2>/dev/null | wc -l | tr -d ' ')
511
+ cjs_count=$(grep -rl "require(" "$root/src" "$root/app" "$root/lib" 2>/dev/null | wc -l | tr -d ' ')
512
+ if [[ "$es_count" -gt "$cjs_count" ]]; then
513
+ IMPORT_STYLE="ES modules (import/export)"
514
+ elif [[ "$cjs_count" -gt 0 ]]; then
515
+ IMPORT_STYLE="CommonJS (require/module.exports)"
516
+ else
517
+ IMPORT_STYLE="ES modules (import/export)"
518
+ fi
519
+ fi
520
+
521
+ # ── Naming convention ──
522
+ local camel_count snake_count
523
+ camel_count=$(grep -roh '[a-z][a-zA-Z]*(' "$root/src" "$root/app" "$root/lib" 2>/dev/null | grep -c '[a-z][A-Z]' 2>/dev/null || true)
524
+ camel_count="${camel_count:-0}"
525
+ snake_count=$(grep -roh '[a-z_]*_[a-z]*(' "$root/src" "$root/app" "$root/lib" 2>/dev/null | wc -l 2>/dev/null | tr -d ' ')
526
+ snake_count="${snake_count:-0}"
527
+ if [[ "$camel_count" -gt "$snake_count" ]]; then
528
+ NAMING_CONVENTION="camelCase"
529
+ elif [[ "$snake_count" -gt "$camel_count" ]]; then
530
+ NAMING_CONVENTION="snake_case"
531
+ else
532
+ NAMING_CONVENTION="mixed"
533
+ fi
534
+
535
+ # ── Route patterns ──
536
+ if grep -rq "app\.\(get\|post\|put\|delete\|patch\|use\)" "$root/src" "$root/app" "$root/routes" "$root/lib" 2>/dev/null; then
537
+ HAS_ROUTES=true
538
+ ROUTE_PATTERNS="Express-style (app.get/post/put/delete)"
539
+ elif grep -rq "@app\.route\|@router\.\(get\|post\)" "$root/src" "$root/app" 2>/dev/null; then
540
+ HAS_ROUTES=true
541
+ ROUTE_PATTERNS="Decorator-style (@app.route / @router)"
542
+ elif grep -rq "router\.\(GET\|POST\|PUT\|DELETE\)" "$root" 2>/dev/null; then
543
+ HAS_ROUTES=true
544
+ ROUTE_PATTERNS="Go-style router methods"
545
+ fi
546
+
547
+ # ── DB patterns ──
548
+ if grep -rq "prisma\|PrismaClient" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
549
+ HAS_DB=true; DB_PATTERNS="Prisma ORM"
550
+ elif grep -rq "sequelize\|Sequelize" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
551
+ HAS_DB=true; DB_PATTERNS="Sequelize ORM"
552
+ elif grep -rq "mongoose\|Schema(" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
553
+ HAS_DB=true; DB_PATTERNS="Mongoose (MongoDB)"
554
+ elif grep -rq "typeorm\|TypeORM\|@Entity" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
555
+ HAS_DB=true; DB_PATTERNS="TypeORM"
556
+ elif grep -rq "drizzle\|drizzle-orm" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
557
+ HAS_DB=true; DB_PATTERNS="Drizzle ORM"
558
+ elif grep -rq "sqlalchemy\|SQLAlchemy" "$root/src" "$root/app" 2>/dev/null; then
559
+ HAS_DB=true; DB_PATTERNS="SQLAlchemy"
560
+ elif grep -rq "pg\|Pool(" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
561
+ HAS_DB=true; DB_PATTERNS="pg (raw PostgreSQL)"
562
+ fi
563
+
564
+ # ── Middleware ──
565
+ if grep -rq "app\.use(" "$root/src" "$root/app" "$root/lib" 2>/dev/null; then
566
+ HAS_MIDDLEWARE=true
567
+ fi
568
+
569
+ success "Patterns: ${NAMING_CONVENTION} naming${IMPORT_STYLE:+, ${IMPORT_STYLE}}${ROUTE_PATTERNS:+, ${ROUTE_PATTERNS}}"
570
+ }
571
+
572
+ # ─── prep_generate_claude_md ────────────────────────────────────────────────
573
+
574
+ prep_generate_claude_md() {
575
+ local filepath="$PROJECT_ROOT/.claude/CLAUDE.md"
576
+ if ! should_write "$filepath"; then return; fi
577
+
578
+ info "Generating .claude/CLAUDE.md..."
579
+
580
+ # Build structure summary
581
+ local structure=""
582
+ if [[ -n "$SRC_DIRS" ]]; then
583
+ structure+="### Source Directories\n"
584
+ for d in $SRC_DIRS; do
585
+ structure+="- \`${d}/\`\n"
586
+ done
587
+ fi
588
+ if [[ -n "$TEST_DIRS" ]]; then
589
+ structure+="\n### Test Directories\n"
590
+ for d in $TEST_DIRS; do
591
+ structure+="- \`${d}/\`\n"
592
+ done
593
+ fi
594
+
595
+ # Build conventions
596
+ local conventions=""
597
+ [[ -n "$NAMING_CONVENTION" ]] && conventions+="- Naming: ${NAMING_CONVENTION}\n"
598
+ [[ -n "$IMPORT_STYLE" ]] && conventions+="- Imports: ${IMPORT_STYLE}\n"
599
+ [[ -n "$ROUTE_PATTERNS" ]] && conventions+="- Routes: ${ROUTE_PATTERNS}\n"
600
+ [[ -n "$DB_PATTERNS" ]] && conventions+="- Database: ${DB_PATTERNS}\n"
601
+
602
+ # Build important files
603
+ local important=""
604
+ if [[ -n "$ENTRY_POINTS" ]]; then
605
+ for f in $ENTRY_POINTS; do
606
+ important+="- \`${f}\`\n"
607
+ done
608
+ fi
609
+ if [[ -n "$CONFIG_FILES" ]]; then
610
+ for f in $CONFIG_FILES; do
611
+ important+="- \`${f}\`\n"
612
+ done
613
+ fi
614
+
615
+ local content
616
+ content=$(cat <<HEREDOC
617
+ <!-- cct:auto-start -->
618
+ # Project: ${PROJECT_NAME}
619
+
620
+ ## Stack
621
+ - Language: ${LANG_DETECTED:-unknown}
622
+ - Framework: ${FRAMEWORK:-none detected}
623
+ - Package Manager: ${PACKAGE_MANAGER:-unknown}
624
+ - Test Framework: ${TEST_FRAMEWORK:-unknown}
625
+
626
+ ## Commands
627
+ - Build: \`${BUILD_CMD:-N/A}\`
628
+ - Test: \`${TEST_CMD:-N/A}\`
629
+ - Lint: \`${LINT_CMD:-N/A}\`
630
+ - Format: \`${FORMAT_CMD:-N/A}\`
631
+ - Dev: \`${DEV_CMD:-N/A}\`
632
+
633
+ ## Structure
634
+ $(echo -e "$structure")
635
+
636
+ ## Conventions
637
+ $(echo -e "$conventions")
638
+
639
+ ## Important Files
640
+ $(echo -e "$important")
641
+ <!-- cct:auto-end -->
642
+ HEREDOC
643
+ )
644
+
645
+ update_auto_section "$filepath" "$content"
646
+ track_file "$filepath"
647
+ success "Generated .claude/CLAUDE.md"
648
+ }
649
+
650
+ # ─── prep_generate_settings ─────────────────────────────────────────────────
651
+
652
+ prep_generate_settings() {
653
+ local filepath="$PROJECT_ROOT/.claude/settings.json"
654
+ if ! should_write "$filepath"; then return; fi
655
+
656
+ info "Generating .claude/settings.json..."
657
+
658
+ # Build allow list using jq for proper JSON escaping
659
+ local allow_json='[]'
660
+ allow_json=$(echo "$allow_json" | jq '. + ["Read(***)","Edit(***)","Write(***)"]')
661
+ [[ -n "$TEST_CMD" ]] && allow_json=$(echo "$allow_json" | jq --arg cmd "Bash($TEST_CMD)" '. + [$cmd]')
662
+ [[ -n "$LINT_CMD" ]] && allow_json=$(echo "$allow_json" | jq --arg cmd "Bash($LINT_CMD)" '. + [$cmd]')
663
+ [[ -n "$BUILD_CMD" ]] && allow_json=$(echo "$allow_json" | jq --arg cmd "Bash($BUILD_CMD)" '. + [$cmd]')
664
+ [[ -n "$FORMAT_CMD" ]] && allow_json=$(echo "$allow_json" | jq --arg cmd "Bash($FORMAT_CMD)" '. + [$cmd]')
665
+ allow_json=$(echo "$allow_json" | jq '. + ["Bash(git *)"]')
666
+
667
+ jq -n --argjson allow "$allow_json" '{ permissions: { allow: $allow } }' > "$filepath"
668
+
669
+ # Validate JSON
670
+ if ! jq empty "$filepath" 2>/dev/null; then
671
+ warn "settings.json has invalid JSON — check manually"
672
+ fi
673
+
674
+ track_file "$filepath"
675
+ success "Generated .claude/settings.json"
676
+ }
677
+
678
+ # ─── prep_generate_hooks ────────────────────────────────────────────────────
679
+
680
+ prep_generate_hooks() {
681
+ # Pre-build hook
682
+ local pre_build="$PROJECT_ROOT/.claude/hooks/pre-build.sh"
683
+ if should_write "$pre_build"; then
684
+ info "Generating hooks..."
685
+
686
+ cat > "$pre_build" <<'HOOKEOF'
687
+ #!/usr/bin/env bash
688
+ # Pre-build hook: run linter before building
689
+ set -euo pipefail
690
+
691
+ HOOKEOF
692
+
693
+ if [[ -n "$LINT_CMD" ]]; then
694
+ cat >> "$pre_build" <<HOOKEOF
695
+ echo "Running lint check..."
696
+ ${LINT_CMD} || {
697
+ echo "Lint failed — fix issues before building"
698
+ exit 1
699
+ }
700
+ echo "Lint passed"
701
+ HOOKEOF
702
+ else
703
+ cat >> "$pre_build" <<'HOOKEOF'
704
+ echo "No lint command configured — skipping pre-build check"
705
+ HOOKEOF
706
+ fi
707
+
708
+ chmod +x "$pre_build"
709
+ track_file "$pre_build"
710
+ fi
711
+
712
+ # Post-test hook
713
+ local post_test="$PROJECT_ROOT/.claude/hooks/post-test.sh"
714
+ if should_write "$post_test"; then
715
+ cat > "$post_test" <<'HOOKEOF'
716
+ #!/usr/bin/env bash
717
+ # Post-test hook: check for common issues after test runs
718
+ set -euo pipefail
719
+
720
+ # Check for leftover console.log / print debugging
721
+ DEBUGGING_STMTS=$(grep -rn "console\.log\|debugger\|print(" src/ app/ lib/ 2>/dev/null | grep -v node_modules | grep -v ".test." | grep -v ".spec." || true)
722
+ if [[ -n "$DEBUGGING_STMTS" ]]; then
723
+ echo "Warning: Found debugging statements:"
724
+ echo "$DEBUGGING_STMTS" | head -10
725
+ fi
726
+
727
+ echo "Post-test checks complete"
728
+ HOOKEOF
729
+
730
+ chmod +x "$post_test"
731
+ track_file "$post_test"
732
+ fi
733
+
734
+ success "Generated hook scripts"
735
+ }
736
+
737
+ # ─── prep_generate_agents ───────────────────────────────────────────────────
738
+
739
+ prep_generate_agents() {
740
+ info "Generating agent definitions..."
741
+
742
+ # Backend agent
743
+ local backend="$PROJECT_ROOT/.claude/agents/backend.md"
744
+ if should_write "$backend"; then
745
+ cat > "$backend" <<AGENTEOF
746
+ # Backend Agent
747
+
748
+ ## Role
749
+ You are a backend specialist working on **${PROJECT_NAME}**.
750
+
751
+ ## Stack
752
+ - Language: ${LANG_DETECTED:-unknown}
753
+ - Framework: ${FRAMEWORK:-none}
754
+ - Database: ${DB_PATTERNS:-none detected}
755
+
756
+ ## Focus Areas
757
+ - API endpoints and route handlers
758
+ - Business logic and data processing
759
+ - Database queries and migrations
760
+ - Authentication and authorization
761
+ - Error handling and validation
762
+
763
+ ## Constraints
764
+ - Always write tests for new endpoints
765
+ - Follow existing patterns in the codebase
766
+ - Do not modify frontend files
767
+ - Use existing error handling patterns
768
+ - Run \`${TEST_CMD:-echo "no test cmd"}\` before marking work complete
769
+
770
+ ## Key Directories
771
+ $(for d in $SRC_DIRS; do echo "- \`${d}/\`"; done)
772
+ AGENTEOF
773
+ track_file "$backend"
774
+ fi
775
+
776
+ # Frontend agent (only if frontend-ish framework detected)
777
+ if [[ "$FRAMEWORK" == "react" || "$FRAMEWORK" == "vue" || "$FRAMEWORK" == "angular" || \
778
+ "$FRAMEWORK" == "next.js" || "$FRAMEWORK" == "nuxt" ]]; then
779
+ local frontend="$PROJECT_ROOT/.claude/agents/frontend.md"
780
+ if should_write "$frontend"; then
781
+ cat > "$frontend" <<AGENTEOF
782
+ # Frontend Agent
783
+
784
+ ## Role
785
+ You are a frontend specialist working on **${PROJECT_NAME}**.
786
+
787
+ ## Stack
788
+ - Framework: ${FRAMEWORK}
789
+ - Language: ${LANG_DETECTED}
790
+
791
+ ## Focus Areas
792
+ - UI components and layouts
793
+ - State management
794
+ - Client-side routing
795
+ - Form handling and validation
796
+ - Styling and responsiveness
797
+ - Accessibility (a11y)
798
+
799
+ ## Constraints
800
+ - Follow existing component patterns
801
+ - Do not modify backend/API files
802
+ - Write unit tests for components
803
+ - Ensure responsive design
804
+ - Run \`${TEST_CMD:-echo "no test cmd"}\` before marking work complete
805
+
806
+ ## Key Directories
807
+ $(for d in components pages views app src/components src/pages; do
808
+ [[ -d "$PROJECT_ROOT/$d" ]] && echo "- \`${d}/\`"
809
+ done)
810
+ AGENTEOF
811
+ track_file "$frontend"
812
+ fi
813
+ fi
814
+
815
+ # Tester agent
816
+ local tester="$PROJECT_ROOT/.claude/agents/tester.md"
817
+ if should_write "$tester"; then
818
+ cat > "$tester" <<AGENTEOF
819
+ # Tester Agent
820
+
821
+ ## Role
822
+ You are a testing specialist working on **${PROJECT_NAME}**.
823
+
824
+ ## Stack
825
+ - Test Framework: ${TEST_FRAMEWORK:-unknown}
826
+ - Test Command: \`${TEST_CMD:-N/A}\`
827
+
828
+ ## Focus Areas
829
+ - Unit tests for all new functions and methods
830
+ - Integration tests for API endpoints
831
+ - Edge case coverage
832
+ - Error path testing
833
+ - Test data fixtures and factories
834
+
835
+ ## Constraints
836
+ - Write real tests, not mocked pass-throughs
837
+ - Test both happy paths and error paths
838
+ - Follow existing test patterns and naming conventions
839
+ - Maintain or improve code coverage
840
+ - Do not modify source code — only test files
841
+
842
+ ## Test Directories
843
+ $(for d in $TEST_DIRS; do echo "- \`${d}/\`"; done)
844
+ $(if [[ -z "$TEST_DIRS" ]]; then echo "- Tests colocated with source files"; fi)
845
+
846
+ ## Running Tests
847
+ \`\`\`bash
848
+ ${TEST_CMD:-# No test command detected}
849
+ \`\`\`
850
+ AGENTEOF
851
+ track_file "$tester"
852
+ fi
853
+
854
+ success "Generated agent definitions"
855
+ }
856
+
857
+ # ─── prep_generate_architecture ─────────────────────────────────────────────
858
+
859
+ prep_generate_architecture() {
860
+ local filepath="$PROJECT_ROOT/.claude/ARCHITECTURE.md"
861
+ if ! should_write "$filepath"; then return; fi
862
+
863
+ info "Generating .claude/ARCHITECTURE.md..."
864
+
865
+ # Build module map
866
+ local module_map=""
867
+ for d in $SRC_DIRS; do
868
+ local file_count
869
+ file_count=$(find "$PROJECT_ROOT/$d" -type f -not -path "*/node_modules/*" 2>/dev/null | wc -l | tr -d ' ')
870
+ module_map+="- \`${d}/\` — ${file_count} files\n"
871
+ done
872
+
873
+ # Key dependencies
874
+ local deps_section=""
875
+ if [[ -f "$PROJECT_ROOT/package.json" ]]; then
876
+ local dep_names
877
+ dep_names=$(jq -r '.dependencies // {} | keys[]' "$PROJECT_ROOT/package.json" 2>/dev/null | head -15)
878
+ if [[ -n "$dep_names" ]]; then
879
+ deps_section="## Dependencies\n"
880
+ while IFS= read -r dep; do
881
+ deps_section+="- \`${dep}\`\n"
882
+ done <<< "$dep_names"
883
+ fi
884
+ elif [[ -f "$PROJECT_ROOT/go.mod" ]]; then
885
+ local go_deps
886
+ go_deps=$(grep "^\t" "$PROJECT_ROOT/go.mod" 2>/dev/null | awk '{print $1}' | head -15)
887
+ if [[ -n "$go_deps" ]]; then
888
+ deps_section="## Dependencies\n"
889
+ while IFS= read -r dep; do
890
+ deps_section+="- \`${dep}\`\n"
891
+ done <<< "$go_deps"
892
+ fi
893
+ fi
894
+
895
+ # Data flow
896
+ local data_flow=""
897
+ if $HAS_ROUTES && $HAS_DB; then
898
+ data_flow="## Data Flow\n"
899
+ data_flow+="Request → ${ROUTE_PATTERNS:-Router} → Handler → ${DB_PATTERNS:-Database} → Response\n"
900
+ elif $HAS_ROUTES; then
901
+ data_flow="## Data Flow\n"
902
+ data_flow+="Request → ${ROUTE_PATTERNS:-Router} → Handler → Response\n"
903
+ fi
904
+
905
+ local content
906
+ content=$(cat <<HEREDOC
907
+ <!-- cct:auto-start -->
908
+ # Architecture
909
+
910
+ ## Overview
911
+ **${PROJECT_NAME}** is a ${LANG_DETECTED:-unknown}${FRAMEWORK:+ / ${FRAMEWORK}} project with ${SRC_FILE_COUNT} source files and ~${TOTAL_LINES} lines of code.
912
+
913
+ ## Entry Points
914
+ $(for f in $ENTRY_POINTS; do echo "- \`${f}\`"; done)
915
+ $(if [[ -z "$ENTRY_POINTS" ]]; then echo "- No standard entry points detected"; fi)
916
+
917
+ ## Module Map
918
+ $(echo -e "$module_map")
919
+ $(if [[ -z "$module_map" ]]; then echo "No standard module directories detected."; fi)
920
+
921
+ $(echo -e "${deps_section}")
922
+
923
+ $(echo -e "${data_flow}")
924
+
925
+ ## Infrastructure
926
+ $(if $HAS_DOCKER; then echo "- Docker: \`Dockerfile\` present"; fi)
927
+ $(if $HAS_COMPOSE; then echo "- Docker Compose: multi-service setup"; fi)
928
+ $(if $HAS_CI; then echo "- CI/CD: GitHub Actions workflows"; fi)
929
+ $(if $HAS_MAKEFILE; then echo "- Makefile: build automation"; fi)
930
+ $(if ! $HAS_DOCKER && ! $HAS_COMPOSE && ! $HAS_CI && ! $HAS_MAKEFILE; then echo "- No infrastructure files detected"; fi)
931
+ <!-- cct:auto-end -->
932
+ HEREDOC
933
+ )
934
+
935
+ update_auto_section "$filepath" "$content"
936
+ track_file "$filepath"
937
+ success "Generated .claude/ARCHITECTURE.md"
938
+ }
939
+
940
+ # ─── prep_generate_standards ────────────────────────────────────────────────
941
+
942
+ prep_generate_standards() {
943
+ local filepath="$PROJECT_ROOT/.claude/CODING-STANDARDS.md"
944
+ if ! should_write "$filepath"; then return; fi
945
+
946
+ info "Generating .claude/CODING-STANDARDS.md..."
947
+
948
+ local content
949
+ content=$(cat <<HEREDOC
950
+ <!-- cct:auto-start -->
951
+ # Coding Standards
952
+
953
+ ## Naming
954
+ - Convention: **${NAMING_CONVENTION:-not detected}**
955
+ - Files: follow existing file naming patterns in the project
956
+ - Variables/functions: use ${NAMING_CONVENTION:-the project's established} style consistently
957
+
958
+ ## Imports
959
+ - Style: **${IMPORT_STYLE:-follow existing patterns}**
960
+ - Keep imports organized: stdlib → external deps → internal modules
961
+
962
+ ## Error Handling
963
+ - Use the project's existing error handling patterns
964
+ - Always handle promise rejections / async errors
965
+ - Provide meaningful error messages
966
+ - Don't swallow errors silently
967
+
968
+ ## Testing
969
+ - Framework: **${TEST_FRAMEWORK:-unknown}**
970
+ - Write tests for all new functionality
971
+ - Test both success and error paths
972
+ - Use descriptive test names: \`describe("module") → it("should do X when Y")\`
973
+ - Keep tests focused — one assertion per test where practical
974
+
975
+ ## File Organization
976
+ $(if [[ -n "$SRC_DIRS" ]]; then
977
+ echo "- Source code: \`${SRC_DIRS}\`"
978
+ fi)
979
+ $(if [[ -n "$TEST_DIRS" ]]; then
980
+ echo "- Tests: \`${TEST_DIRS}\`"
981
+ fi)
982
+ - Follow the existing directory structure — don't create new top-level dirs without discussion
983
+ <!-- cct:auto-end -->
984
+ HEREDOC
985
+ )
986
+
987
+ update_auto_section "$filepath" "$content"
988
+ track_file "$filepath"
989
+ success "Generated .claude/CODING-STANDARDS.md"
990
+ }
991
+
992
+ # ─── prep_generate_dod ──────────────────────────────────────────────────────
993
+
994
+ prep_generate_dod() {
995
+ local filepath="$PROJECT_ROOT/.claude/DEFINITION-OF-DONE.md"
996
+ if ! should_write "$filepath"; then return; fi
997
+
998
+ info "Generating .claude/DEFINITION-OF-DONE.md..."
999
+
1000
+ cat > "$filepath" <<HEREDOC
1001
+ # Definition of Done
1002
+
1003
+ ## Code Quality
1004
+ - [ ] All tests pass (\`${TEST_CMD:-N/A}\`)
1005
+ $(if [[ -n "$LINT_CMD" ]]; then echo "- [ ] Lint passes (\`${LINT_CMD}\`)"; fi)
1006
+ $(if [[ -n "$BUILD_CMD" ]]; then echo "- [ ] Build succeeds (\`${BUILD_CMD}\`)"; fi)
1007
+ - [ ] No console.log/print debugging left in code
1008
+ - [ ] Error handling for edge cases
1009
+
1010
+ ## Testing
1011
+ - [ ] Unit tests for new functions
1012
+ - [ ] Integration tests for new endpoints/features
1013
+ - [ ] Error paths tested
1014
+ - [ ] Edge cases covered
1015
+
1016
+ ## Documentation
1017
+ - [ ] Code comments for complex logic
1018
+ - [ ] README updated if API changes
1019
+
1020
+ ## Git
1021
+ - [ ] Clean commit history
1022
+ - [ ] Branch naming follows convention
1023
+ - [ ] No unresolved merge conflicts
1024
+ - [ ] PR description explains the "why"
1025
+ HEREDOC
1026
+
1027
+ track_file "$filepath"
1028
+ success "Generated .claude/DEFINITION-OF-DONE.md"
1029
+ }
1030
+
1031
+ # ─── prep_generate_issue_templates ──────────────────────────────────────────
1032
+
1033
+ prep_generate_issue_templates() {
1034
+ local filepath="$PROJECT_ROOT/.github/ISSUE_TEMPLATE/agent-task.md"
1035
+ if ! should_write "$filepath"; then return; fi
1036
+
1037
+ info "Generating issue template..."
1038
+
1039
+ cat > "$filepath" <<'HEREDOC'
1040
+ ---
1041
+ name: Agent Task
1042
+ about: Structured task for autonomous agent execution
1043
+ labels: ready-to-build
1044
+ ---
1045
+
1046
+ ## Goal
1047
+ <!-- One clear sentence describing what needs to happen -->
1048
+
1049
+ ## Context
1050
+ <!-- Background information, related issues, user requirements -->
1051
+
1052
+ ## Acceptance Criteria
1053
+ - [ ] Criterion 1
1054
+ - [ ] Criterion 2
1055
+
1056
+ ## Technical Notes
1057
+ <!-- Implementation hints, files to modify, constraints -->
1058
+
1059
+ ## Definition of Done
1060
+ - [ ] All existing tests pass
1061
+ - [ ] New tests added for new functionality
1062
+ - [ ] Code reviewed (adversarial + negative prompting)
1063
+ - [ ] No lint errors
1064
+ HEREDOC
1065
+
1066
+ track_file "$filepath"
1067
+ success "Generated .github/ISSUE_TEMPLATE/agent-task.md"
1068
+ }
1069
+
1070
+ # ─── prep_generate_manifest ─────────────────────────────────────────────────
1071
+
1072
+ prep_generate_manifest() {
1073
+ local filepath="$PROJECT_ROOT/.claude/prep-manifest.json"
1074
+ info "Writing manifest..."
1075
+
1076
+ local files_json="{"
1077
+ local first=true
1078
+ for entry in "${GENERATED_FILES[@]}"; do
1079
+ local fname flines
1080
+ fname="${entry%%|*}"
1081
+ flines="${entry##*|}"
1082
+ local checksum
1083
+ checksum=$(md5 -q "$PROJECT_ROOT/$fname" 2>/dev/null || md5sum "$PROJECT_ROOT/$fname" 2>/dev/null | awk '{print $1}' || echo "unknown")
1084
+ if $first; then
1085
+ first=false
1086
+ else
1087
+ files_json+=","
1088
+ fi
1089
+ files_json+="
1090
+ \"${fname}\": { \"checksum\": \"${checksum}\", \"lines\": ${flines} }"
1091
+ done
1092
+ files_json+="
1093
+ }"
1094
+
1095
+ cat > "$filepath" <<HEREDOC
1096
+ {
1097
+ "version": 1,
1098
+ "generated_at": "$(now_iso)",
1099
+ "stack": {
1100
+ "lang": "${LANG_DETECTED:-unknown}",
1101
+ "framework": "${FRAMEWORK:-none}",
1102
+ "test": "${TEST_FRAMEWORK:-unknown}",
1103
+ "package_manager": "${PACKAGE_MANAGER:-unknown}"
1104
+ },
1105
+ "files": ${files_json}
1106
+ }
1107
+ HEREDOC
1108
+
1109
+ # Validate JSON
1110
+ if command -v jq &>/dev/null; then
1111
+ if ! jq empty "$filepath" 2>/dev/null; then
1112
+ warn "prep-manifest.json may have invalid JSON — check manually"
1113
+ fi
1114
+ fi
1115
+
1116
+ track_file "$filepath"
1117
+ success "Written prep-manifest.json"
1118
+ }
1119
+
1120
+ # ─── prep_with_claude — Deep analysis using Claude Code ─────────────────────
1121
+
1122
+ prep_with_claude() {
1123
+ if ! $WITH_CLAUDE; then return; fi
1124
+
1125
+ if ! command -v claude &>/dev/null; then
1126
+ warn "claude CLI not found — skipping deep analysis"
1127
+ return
1128
+ fi
1129
+
1130
+ info "Running deep analysis with Claude Code..."
1131
+
1132
+ local source_sample
1133
+ source_sample=$(find "$PROJECT_ROOT/src" "$PROJECT_ROOT/app" "$PROJECT_ROOT/lib" \
1134
+ -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.go' -o -name '*.rs' \
1135
+ 2>/dev/null | head -20 | xargs cat 2>/dev/null | head -500 || true)
1136
+
1137
+ if [[ -z "$source_sample" ]]; then
1138
+ warn "No source files found for deep analysis"
1139
+ return
1140
+ fi
1141
+
1142
+ local analysis
1143
+ analysis=$(claude --print "Analyze this ${LANG_DETECTED:-} / ${FRAMEWORK:-} repository and provide:
1144
+ 1. A 2-3 sentence architecture overview
1145
+ 2. Key patterns and conventions you observe
1146
+ 3. Potential pitfalls for developers new to this codebase
1147
+ 4. Suggested focus areas for code quality
1148
+
1149
+ Source sample:
1150
+ ${source_sample}" 2>/dev/null || true)
1151
+
1152
+ if [[ -n "$analysis" ]]; then
1153
+ local filepath="$PROJECT_ROOT/.claude/DEEP-ANALYSIS.md"
1154
+ cat > "$filepath" <<HEREDOC
1155
+ # Deep Analysis (Claude-generated)
1156
+
1157
+ _Generated at $(now_iso)_
1158
+
1159
+ ${analysis}
1160
+ HEREDOC
1161
+ track_file "$filepath"
1162
+ success "Generated .claude/DEEP-ANALYSIS.md"
1163
+ else
1164
+ warn "Claude analysis returned empty — skipping"
1165
+ fi
1166
+ }
1167
+
1168
+ # ─── prep_validate — Validate generated files ──────────────────────────────
1169
+
1170
+ prep_validate() {
1171
+ local issues=0
1172
+
1173
+ # Check JSON files
1174
+ if command -v jq &>/dev/null; then
1175
+ for f in "$PROJECT_ROOT/.claude/settings.json" "$PROJECT_ROOT/.claude/prep-manifest.json"; do
1176
+ if [[ -f "$f" ]] && ! jq empty "$f" 2>/dev/null; then
1177
+ warn "Invalid JSON: ${f##"$PROJECT_ROOT"/}"
1178
+ issues=$((issues + 1))
1179
+ fi
1180
+ done
1181
+ fi
1182
+
1183
+ # Check markdown files aren't empty
1184
+ for f in "$PROJECT_ROOT/.claude/CLAUDE.md" "$PROJECT_ROOT/.claude/ARCHITECTURE.md" \
1185
+ "$PROJECT_ROOT/.claude/CODING-STANDARDS.md" "$PROJECT_ROOT/.claude/DEFINITION-OF-DONE.md"; do
1186
+ if [[ -f "$f" ]]; then
1187
+ local lines
1188
+ lines=$(wc -l < "$f" | tr -d ' ')
1189
+ if [[ "$lines" -lt 3 ]]; then
1190
+ warn "Suspiciously short: ${f##"$PROJECT_ROOT"/} (${lines} lines)"
1191
+ issues=$((issues + 1))
1192
+ fi
1193
+ fi
1194
+ done
1195
+
1196
+ # Check hooks are executable
1197
+ for f in "$PROJECT_ROOT/.claude/hooks/"*.sh; do
1198
+ if [[ -f "$f" ]] && [[ ! -x "$f" ]]; then
1199
+ warn "Hook not executable: ${f##"$PROJECT_ROOT"/}"
1200
+ chmod +x "$f"
1201
+ issues=$((issues + 1))
1202
+ fi
1203
+ done
1204
+
1205
+ if [[ "$issues" -eq 0 ]]; then
1206
+ success "Validation passed — all files OK"
1207
+ else
1208
+ warn "Validation found ${issues} issue(s)"
1209
+ fi
1210
+ }
1211
+
1212
+ # ─── prep_check — Audit mode ───────────────────────────────────────────────
1213
+
1214
+ prep_check() {
1215
+ echo -e "\n${CYAN}${BOLD}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
1216
+ echo -e "${CYAN}${BOLD}║ Prep Audit ║${RESET}"
1217
+ echo -e "${CYAN}${BOLD}╚═══════════════════════════════════════════════════════════════════╝${RESET}\n"
1218
+
1219
+ local score=0
1220
+ local total=0
1221
+ local missing=()
1222
+
1223
+ # Check each expected file
1224
+ local expected_files=(
1225
+ ".claude/CLAUDE.md"
1226
+ ".claude/settings.json"
1227
+ ".claude/ARCHITECTURE.md"
1228
+ ".claude/CODING-STANDARDS.md"
1229
+ ".claude/DEFINITION-OF-DONE.md"
1230
+ ".claude/agents/backend.md"
1231
+ ".claude/agents/tester.md"
1232
+ ".claude/hooks/pre-build.sh"
1233
+ ".claude/hooks/post-test.sh"
1234
+ ".claude/prep-manifest.json"
1235
+ ".github/ISSUE_TEMPLATE/agent-task.md"
1236
+ )
1237
+
1238
+ for f in "${expected_files[@]}"; do
1239
+ total=$((total + 1))
1240
+ if [[ -f "$PROJECT_ROOT/$f" ]]; then
1241
+ local lines
1242
+ lines=$(wc -l < "$PROJECT_ROOT/$f" | tr -d ' ')
1243
+ echo -e " ${GREEN}${BOLD}✓${RESET} ${f} ${DIM}(${lines} lines)${RESET}"
1244
+ score=$((score + 1))
1245
+
1246
+ # Check for auto markers in markdown files
1247
+ if [[ "$f" == *.md && "$f" != *"ISSUE_TEMPLATE"* && "$f" != *"DEFINITION-OF-DONE"* ]]; then
1248
+ if grep -q "<!-- cct:auto-start -->" "$PROJECT_ROOT/$f" 2>/dev/null; then
1249
+ echo -e " ${DIM}↳ Has auto-update markers${RESET}"
1250
+ else
1251
+ echo -e " ${YELLOW}↳ No auto-update markers (user-customized)${RESET}"
1252
+ fi
1253
+ fi
1254
+ else
1255
+ echo -e " ${RED}${BOLD}✗${RESET} ${f} ${DIM}(missing)${RESET}"
1256
+ missing+=("$f")
1257
+ fi
1258
+ done
1259
+
1260
+ # Report
1261
+ echo ""
1262
+ local pct=$((score * 100 / total))
1263
+ local grade
1264
+ if [[ $pct -ge 90 ]]; then grade="${GREEN}A${RESET}"
1265
+ elif [[ $pct -ge 75 ]]; then grade="${GREEN}B${RESET}"
1266
+ elif [[ $pct -ge 60 ]]; then grade="${YELLOW}C${RESET}"
1267
+ elif [[ $pct -ge 40 ]]; then grade="${YELLOW}D${RESET}"
1268
+ else grade="${RED}F${RESET}"
1269
+ fi
1270
+
1271
+ echo -e " ${BOLD}Score:${RESET} ${score}/${total} (${pct}%) — Grade: ${BOLD}${grade}${RESET}"
1272
+
1273
+ if [[ ${#missing[@]} -gt 0 ]]; then
1274
+ echo ""
1275
+ echo -e " ${BOLD}Missing files:${RESET}"
1276
+ for f in "${missing[@]}"; do
1277
+ echo -e " ${DIM}→ ${f}${RESET}"
1278
+ done
1279
+ echo ""
1280
+ echo -e " ${DIM}Run ${CYAN}shipwright prep${DIM} to generate missing files${RESET}"
1281
+ fi
1282
+ echo ""
1283
+ }
1284
+
1285
+ # ─── prep_report — Summary output ──────────────────────────────────────────
1286
+
1287
+ prep_report() {
1288
+ echo ""
1289
+ echo -e "${CYAN}${BOLD}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
1290
+ echo -e "${CYAN}${BOLD}║ Prep Complete ║${RESET}"
1291
+ echo -e "${CYAN}${BOLD}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
1292
+ echo ""
1293
+ echo -e " ${BOLD}Stack:${RESET} ${LANG_DETECTED:-unknown}${FRAMEWORK:+ / ${FRAMEWORK}}${TEST_FRAMEWORK:+ / ${TEST_FRAMEWORK}}"
1294
+ echo -e " ${BOLD}Files:${RESET} ${#GENERATED_FILES[@]} generated"
1295
+ echo ""
1296
+ echo -e " ${BOLD}Created:${RESET}"
1297
+ for entry in "${GENERATED_FILES[@]}"; do
1298
+ local fname flines
1299
+ fname="${entry%%|*}"
1300
+ flines="${entry##*|}"
1301
+ printf " ${GREEN}${BOLD}✓${RESET} %-42s ${DIM}(%s lines)${RESET}\n" "$fname" "$flines"
1302
+ done
1303
+ echo ""
1304
+ echo -e " ${DIM}Next steps:${RESET}"
1305
+ echo -e " ${DIM}1. Review generated files and customize as needed${RESET}"
1306
+ echo -e " ${DIM}2. Run ${CYAN}shipwright prep --check${DIM} to audit quality${RESET}"
1307
+ echo -e " ${DIM}3. Content between auto markers can be refreshed with ${CYAN}shipwright prep --update${RESET}"
1308
+ echo ""
1309
+ }
1310
+
1311
+ # ─── Main ───────────────────────────────────────────────────────────────────
1312
+
1313
+ main() {
1314
+ # Banner
1315
+ echo -e "\n${CYAN}${BOLD}▸ shipwright prep${RESET} ${DIM}v${VERSION}${RESET}\n"
1316
+
1317
+ # Init
1318
+ prep_init
1319
+
1320
+ # Check-only mode
1321
+ if $CHECK_ONLY; then
1322
+ prep_check
1323
+ exit 0
1324
+ fi
1325
+
1326
+ # Detection & analysis
1327
+ prep_detect_stack
1328
+ prep_scan_structure
1329
+ prep_extract_patterns
1330
+
1331
+ # Generation
1332
+ prep_generate_claude_md
1333
+ prep_generate_settings
1334
+ prep_generate_hooks
1335
+ prep_generate_agents
1336
+ prep_generate_architecture
1337
+ prep_generate_standards
1338
+ prep_generate_dod
1339
+ prep_generate_issue_templates
1340
+
1341
+ # Deep analysis (optional)
1342
+ prep_with_claude
1343
+
1344
+ # Manifest & validation
1345
+ prep_generate_manifest
1346
+ prep_validate
1347
+
1348
+ # Report
1349
+ prep_report
1350
+ }
1351
+
1352
+ main