-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcreate-project
More file actions
executable file
·249 lines (222 loc) · 8.67 KB
/
Copy pathcreate-project
File metadata and controls
executable file
·249 lines (222 loc) · 8.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/usr/bin/env bash
# Stamp this template into a new project directory, deterministically.
#
# Runs the whole mechanical flow in order: extract this template's committed
# tree into the target, rename the skeleton to <dist-name>, merge (never
# clobber) an existing .gitignore, git-init if needed, create the development
# environment with uv (or the venv/pip fallback) and run `make check`, install
# the claude-okf-repo-kit operating contract when a kit clone is available,
# rerun the gate after installation, and delete the single-use template scripts
# and the template-only CI workflow from the target. What it cannot
# do — the project README, CHANGELOG, goal, and playbook content — it prints as
# next steps.
#
# The target may be a fresh directory or an existing repo that only carries
# goal-shaped content (like docs/GOAL.md): any file the template would land on
# is a refusal, listed by path, except .gitignore which is merged line-wise.
#
# Agent-neutral by design: plain bash, no Claude Code or Codex assumptions.
set -euo pipefail
usage() {
cat <<'EOF'
Usage:
bash scripts/create-project TARGET DIST [--kit DIR | --no-kit] [--pip] [--skip-venv]
Options:
--kit DIR Install claude-okf-repo-kit from DIR.
--no-kit Skip kit installation.
--pip Use venv and pip even when uv is available.
--skip-venv Skip dependency installation and verification.
TARGET must not contain #; Python console-script shebangs cannot portably use it.
EOF
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
if [ "$#" -lt 2 ]; then
usage >&2
exit 2
fi
TARGET_ARG="$1"
DIST="$2"
shift 2
KIT_DIR=""
NO_KIT=0
SKIP_VENV=0
USE_PIP=0
while [ "$#" -gt 0 ]; do
case "$1" in
--kit)
KIT_DIR="${2:?--kit needs a path}"
shift 2
;;
--no-kit)
NO_KIT=1
shift
;;
--skip-venv)
SKIP_VENV=1
shift
;;
--pip)
USE_PIP=1
shift
;;
*)
printf 'Unknown option: %s\n' "$1" >&2
exit 2
;;
esac
done
if ! printf '%s' "$DIST" | grep -Eq '^[a-z][a-z0-9]*(-[a-z0-9]+)*$'; then
printf 'Invalid dist name %s: use lowercase letters, digits, and single hyphens (e.g. spec-drift).\n' "$DIST" >&2
exit 2
fi
TEMPLATE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if ! git -C "$TEMPLATE_ROOT" rev-parse HEAD >/dev/null 2>&1; then
printf 'This script extracts the committed template tree and needs a git checkout of the template.\n' >&2
exit 1
fi
# Resolve before creating anything, so a refusal leaves no stray directory.
TARGET="$(python3 -c 'import os, sys; print(os.path.abspath(sys.argv[1]))' "$TARGET_ARG")"
case "$TARGET" in
*'#'*)
printf 'Target %s contains #: Python console-script shebangs cannot portably use that path.\n' "$TARGET" >&2
exit 2
;;
esac
case "$TARGET/" in
"$TEMPLATE_ROOT"/*)
printf 'Target %s is inside the template checkout; pick a directory elsewhere.\n' "$TARGET" >&2
exit 1
;;
esac
mkdir -p "$TARGET"
# Collision check against the committed tree. .gitignore is merged, so it is
# exempt; everything else refuses rather than overwrites.
collisions=()
while IFS= read -r path; do
case "$path" in
*/) continue ;; # directory entries
.gitignore) continue ;; # merged below
esac
if [ -e "$TARGET/$path" ]; then
collisions+=("$path")
fi
done < <(git -C "$TEMPLATE_ROOT" archive HEAD | tar -tf -)
if [ "${#collisions[@]}" -gt 0 ]; then
printf 'Refusing: the target already has files the template would land on:\n' >&2
printf ' - %s\n' "${collisions[@]}" >&2
exit 1
fi
# Extract, holding .gitignore aside when the target already has one.
if [ -f "$TARGET/.gitignore" ]; then
git -C "$TEMPLATE_ROOT" archive HEAD | tar -xf - -C "$TARGET" --exclude .gitignore
while IFS= read -r line; do
grep -qxF "$line" "$TARGET/.gitignore" || printf '%s\n' "$line" >> "$TARGET/.gitignore"
done < <(git -C "$TEMPLATE_ROOT" show HEAD:.gitignore)
gitignore_note="merged template entries into existing .gitignore"
else
git -C "$TEMPLATE_ROOT" archive HEAD | tar -xf - -C "$TARGET"
gitignore_note="installed template .gitignore"
fi
if ! git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then
git -C "$TARGET" init -q
git_note="initialized a new git repository"
else
git_note="kept the existing git repository"
fi
# Rename the skeleton with the target's own copy of the script, then remove
# the template-side tooling from the target: both single-use scripts, the
# smoke test that exercises them (it has nothing to test once they are gone),
# and the template-chassis workflow (its jobs run those scripts and assert the
# template's own version, so they only fail in a generated project).
bash "$TARGET/scripts/rename-project" "$DIST" | sed -n '1,2p'
rm -f "$TARGET/scripts/rename-project" "$TARGET/scripts/create-project" \
"$TARGET/tests/test_create_project.py" \
"$TARGET/.github/workflows/template-chassis.yml"
venv_note="skipped (--skip-venv); preferred: uv sync --all-extras && uv run make check; fallback: python3 -m venv .venv && .venv/bin/pip install -e \".[dev,anthropic,openai]\" && make check"
if [ "$SKIP_VENV" -eq 0 ]; then
if [ "$USE_PIP" -eq 0 ] && command -v uv >/dev/null 2>&1; then
if (cd "$TARGET" && uv sync --all-extras --quiet && uv run make check >/dev/null 2>&1); then
venv_note="uv synced all extras; uv.lock created; make check passed"
else
printf 'uv setup or make check FAILED in the target; inspect before continuing:\n cd %s && uv sync --all-extras && uv run make check\n' "$TARGET" >&2
exit 1
fi
else
python3 -m venv "$TARGET/.venv"
(cd "$TARGET" && .venv/bin/python -m pip install -q -e ".[dev,anthropic,openai]")
if make -C "$TARGET" check >/dev/null 2>&1; then
venv_note="venv/pip environment created; make check passed"
else
printf 'make check FAILED in the target; inspect before continuing:\n cd %s && make check\n' "$TARGET" >&2
exit 1
fi
fi
fi
kit_note="not installed; run when ready: bash /path/to/claude-okf-repo-kit/scripts/update-existing-repo $TARGET"
kit_installed=0
if [ "$NO_KIT" -eq 0 ] && [ -z "$KIT_DIR" ] && [ -d "$TEMPLATE_ROOT/../claude-okf-repo-kit/scripts" ]; then
KIT_DIR="$(cd "$TEMPLATE_ROOT/../claude-okf-repo-kit" && pwd)"
fi
if [ "$NO_KIT" -eq 1 ]; then
KIT_DIR=""
kit_note="skipped (--no-kit)"
fi
if [ -n "$KIT_DIR" ]; then
if [ -f "$KIT_DIR/scripts/update-existing-repo" ]; then
printf '\nKit installer output:\n'
bash "$KIT_DIR/scripts/update-existing-repo" "$TARGET"
kit_note="claude-okf-repo-kit installed from $KIT_DIR"
kit_installed=1
else
printf 'Kit path %s has no scripts/update-existing-repo; skipping kit install.\n' "$KIT_DIR" >&2
fi
fi
if [ "$SKIP_VENV" -eq 0 ] && [ "$kit_installed" -eq 1 ]; then
if [ "$USE_PIP" -eq 0 ] && command -v uv >/dev/null 2>&1; then
if ! (cd "$TARGET" && uv run make check >/dev/null 2>&1); then
printf 'Post-kit make check FAILED in the target; inspect before continuing:\n cd %s && uv run make check\n' "$TARGET" >&2
exit 1
fi
elif ! make -C "$TARGET" check >/dev/null 2>&1; then
printf 'Post-kit make check FAILED in the target; inspect before continuing:\n cd %s && make check\n' "$TARGET" >&2
exit 1
fi
kit_note="$kit_note; post-kit make check passed"
fi
PKG="${DIST//-/_}"
cat <<EOF
Project created
Target: $TARGET
Stack: $DIST (package $PKG), $git_note, $gitignore_note
Gate: $venv_note
Kit: $kit_note
Judgment steps this script cannot do:
1. Rewrite README.md for the project (it still describes the template).
2. Review CHANGELOG.md's new 0.1.0 entry, the reset docs/log.md,
maintainer contact details, and LICENSE ownership.
EOF
next_step=3
if [ "$kit_installed" -eq 1 ]; then
cat <<'EOF'
3. Resolve numbered kit candidates before the first commit. CLAUDE.2.md is
a review candidate, not an instruction file Claude Code loads. Merge
shared rules into AGENTS.md, add Claude-specific imports and skill
references to CLAUDE.md, then delete CLAUDE.2.md. Review other numbered
candidates the same way; do not commit unresolved candidates.
EOF
next_step=4
fi
cat <<EOF
$next_step. Provide the goal: drop a finished docs/GOAL.md in, or run the goal
interview from Claude Code. AGENTS.md already carries Codex's stack
commands; port any kit-specific workflow rules, hooks, and skills per the
kit README's second-agent section, or fill the goal by hand. Then fill the
playbook's master-objective and verification-command brackets from it.
$((next_step + 1)). If the CLI will not call language models, delete the provider layer
(the README's "Deleting the model-provider layer" section lists every
piece).
$((next_step + 2)). Review and commit (including uv.lock when uv created it).
EOF