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.
- package/LICENSE +21 -0
- package/README.md +926 -0
- package/claude-code/CLAUDE.md.shipwright +125 -0
- package/claude-code/hooks/notify-idle.sh +35 -0
- package/claude-code/hooks/pre-compact-save.sh +57 -0
- package/claude-code/hooks/task-completed.sh +170 -0
- package/claude-code/hooks/teammate-idle.sh +68 -0
- package/claude-code/settings.json.template +184 -0
- package/completions/_shipwright +140 -0
- package/completions/shipwright.bash +89 -0
- package/completions/shipwright.fish +107 -0
- package/docs/KNOWN-ISSUES.md +199 -0
- package/docs/TIPS.md +331 -0
- package/docs/definition-of-done.example.md +16 -0
- package/docs/patterns/README.md +139 -0
- package/docs/patterns/audit-loop.md +149 -0
- package/docs/patterns/bug-hunt.md +183 -0
- package/docs/patterns/feature-implementation.md +159 -0
- package/docs/patterns/refactoring.md +183 -0
- package/docs/patterns/research-exploration.md +144 -0
- package/docs/patterns/test-generation.md +173 -0
- package/package.json +49 -0
- package/scripts/adapters/docker-deploy.sh +50 -0
- package/scripts/adapters/fly-deploy.sh +41 -0
- package/scripts/adapters/iterm2-adapter.sh +122 -0
- package/scripts/adapters/railway-deploy.sh +34 -0
- package/scripts/adapters/tmux-adapter.sh +87 -0
- package/scripts/adapters/vercel-deploy.sh +35 -0
- package/scripts/adapters/wezterm-adapter.sh +103 -0
- package/scripts/cct +242 -0
- package/scripts/cct-cleanup.sh +172 -0
- package/scripts/cct-cost.sh +590 -0
- package/scripts/cct-daemon.sh +3189 -0
- package/scripts/cct-doctor.sh +328 -0
- package/scripts/cct-fix.sh +478 -0
- package/scripts/cct-fleet.sh +904 -0
- package/scripts/cct-init.sh +282 -0
- package/scripts/cct-logs.sh +273 -0
- package/scripts/cct-loop.sh +1332 -0
- package/scripts/cct-memory.sh +1148 -0
- package/scripts/cct-pipeline.sh +3844 -0
- package/scripts/cct-prep.sh +1352 -0
- package/scripts/cct-ps.sh +168 -0
- package/scripts/cct-reaper.sh +390 -0
- package/scripts/cct-session.sh +284 -0
- package/scripts/cct-status.sh +169 -0
- package/scripts/cct-templates.sh +242 -0
- package/scripts/cct-upgrade.sh +422 -0
- package/scripts/cct-worktree.sh +405 -0
- package/scripts/postinstall.mjs +96 -0
- package/templates/pipelines/autonomous.json +71 -0
- package/templates/pipelines/cost-aware.json +95 -0
- package/templates/pipelines/deployed.json +79 -0
- package/templates/pipelines/enterprise.json +114 -0
- package/templates/pipelines/fast.json +63 -0
- package/templates/pipelines/full.json +104 -0
- package/templates/pipelines/hotfix.json +63 -0
- package/templates/pipelines/standard.json +91 -0
- package/tmux/claude-teams-overlay.conf +109 -0
- package/tmux/templates/architecture.json +19 -0
- package/tmux/templates/bug-fix.json +24 -0
- package/tmux/templates/code-review.json +24 -0
- package/tmux/templates/devops.json +19 -0
- package/tmux/templates/documentation.json +19 -0
- package/tmux/templates/exploration.json +19 -0
- package/tmux/templates/feature-dev.json +24 -0
- package/tmux/templates/full-stack.json +24 -0
- package/tmux/templates/migration.json +24 -0
- package/tmux/templates/refactor.json +19 -0
- package/tmux/templates/security-audit.json +24 -0
- package/tmux/templates/testing.json +24 -0
- 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
|