create-anpunkit 2.0.3 → 2.1.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/bin/cli.js +3 -2
- package/package.json +1 -1
- package/template/.claude/agents/e2e-runner.md +15 -1
- package/template/.claude/agents/infra-provisioner.md +33 -2
- package/template/.claude/agents/planner.md +15 -5
- package/template/.claude/agents/researcher.md +19 -8
- package/template/.claude/agents/test-author.md +25 -2
- package/template/.claude/anpunkit-manifest.json +21 -21
- package/template/.claude/commands/infra.md +28 -3
- package/template/.claude/commands/overview.md +66 -23
- package/template/.claude/commands/phase.md +68 -16
- package/template/.claude/commands/replan.md +23 -9
- package/template/.cursor/commands/infra.md +26 -1
- package/template/.cursor/commands/overview.md +65 -22
- package/template/.cursor/commands/phase.md +67 -15
- package/template/.cursor/commands/replan.md +22 -8
- package/template/AGENTS.md +101 -25
- package/template/README.md +43 -18
- package/template/anpunkit.png +0 -0
- package/template/commands.src/infra.md +28 -3
- package/template/commands.src/overview.md +66 -23
- package/template/commands.src/phase.md +68 -16
- package/template/commands.src/replan.md +23 -9
- package/template/index.html +340 -0
- package/template/setup.sh +110 -47
package/template/setup.sh
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# hybrid -> merged idempotently, never replaced
|
|
13
13
|
#
|
|
14
14
|
# Flags: --src DIR --kb-path P --kb-remote URL --no-kb --force --dry-run
|
|
15
|
+
# --tools LIST (comma/space list from: claude cursor; default both)
|
|
16
|
+
# --add-tool T (additive: install T's files into an existing project)
|
|
15
17
|
set -euo pipefail
|
|
16
18
|
|
|
17
19
|
SRC="."
|
|
@@ -20,6 +22,9 @@ KB_REMOTE="${ANPUNKIT_KB_REMOTE:-}"
|
|
|
20
22
|
NO_KB=0
|
|
21
23
|
FORCE=0
|
|
22
24
|
DRY=0
|
|
25
|
+
TOOLS_FLAG=""
|
|
26
|
+
ADD_TOOL=""
|
|
27
|
+
AVAILABLE_TOOLS="claude cursor" # tools with real adapters; extend here
|
|
23
28
|
while [ $# -gt 0 ]; do
|
|
24
29
|
case "$1" in
|
|
25
30
|
--src) SRC="$2"; shift 2;;
|
|
@@ -28,6 +33,8 @@ while [ $# -gt 0 ]; do
|
|
|
28
33
|
--no-kb) NO_KB=1; shift;;
|
|
29
34
|
--force) FORCE=1; shift;;
|
|
30
35
|
--dry-run) DRY=1; shift;;
|
|
36
|
+
--tools) TOOLS_FLAG="$2"; shift 2;;
|
|
37
|
+
--add-tool) ADD_TOOL="$2"; shift 2;;
|
|
31
38
|
*) echo "unknown flag: $1" >&2; exit 2;;
|
|
32
39
|
esac
|
|
33
40
|
done
|
|
@@ -63,6 +70,36 @@ fi
|
|
|
63
70
|
say "mode: $MODE (installed: ${OLD_VER:-none} -> new: $NEW_VER)"
|
|
64
71
|
[ "$MODE" = "newer-installed" ] && say " ! installed version is newer than this package — proceeding as repair, review carefully."
|
|
65
72
|
|
|
73
|
+
# ---- tool selection (v2.1): install only the selected tools' adapters/hooks/files
|
|
74
|
+
TOOLS_FILE="$DEST/.claude/anpunkit-tools.json"
|
|
75
|
+
norm_tools() { printf '%s' "$1" | tr ',' ' ' | tr -s ' ' | sed 's/^ //; s/ $//'; }
|
|
76
|
+
read_recorded_tools() {
|
|
77
|
+
[ -f "$TOOLS_FILE" ] || { printf ''; return; }
|
|
78
|
+
node -e "try{console.log((JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).tools||[]).join(' '))}catch(e){console.log('')}" "$TOOLS_FILE" 2>/dev/null || printf ''
|
|
79
|
+
}
|
|
80
|
+
valid_tool() { case " $AVAILABLE_TOOLS " in *" $1 "*) return 0;; *) return 1;; esac; }
|
|
81
|
+
|
|
82
|
+
RECORDED="$(read_recorded_tools)"
|
|
83
|
+
if [ -n "$TOOLS_FLAG" ]; then
|
|
84
|
+
TOOLS="$(norm_tools "$TOOLS_FLAG")"
|
|
85
|
+
elif [ -n "$RECORDED" ]; then
|
|
86
|
+
TOOLS="$RECORDED" # re-run: preserve prior selection
|
|
87
|
+
elif [ "$MODE" = "fresh" ] && [ -t 0 ]; then
|
|
88
|
+
printf " Select tools to install (space-separated from: %s) [claude cursor]: " "$AVAILABLE_TOOLS"
|
|
89
|
+
read -r _sel || true
|
|
90
|
+
TOOLS="$(norm_tools "${_sel:-claude cursor}")"
|
|
91
|
+
else
|
|
92
|
+
TOOLS="claude cursor" # non-interactive default = both (no regression)
|
|
93
|
+
fi
|
|
94
|
+
# additive: --add-tool unions into the selection (and is recorded)
|
|
95
|
+
[ -n "$ADD_TOOL" ] && TOOLS="$(norm_tools "$TOOLS $(norm_tools "$ADD_TOOL")" | tr ' ' '\n' | sort -u | tr '\n' ' ')"
|
|
96
|
+
TOOLS="$(norm_tools "$TOOLS")"
|
|
97
|
+
# validate
|
|
98
|
+
for t in $TOOLS; do valid_tool "$t" || { echo "FATAL: unknown tool '$t' (available: $AVAILABLE_TOOLS)"; exit 2; }; done
|
|
99
|
+
[ -z "$TOOLS" ] && { echo "FATAL: no tools selected"; exit 2; }
|
|
100
|
+
tool_selected() { case " $TOOLS " in *" $1 "*) return 0;; *) return 1;; esac; }
|
|
101
|
+
say "tools: $TOOLS$([ -n "$ADD_TOOL" ] && echo ' (+'"$ADD_TOOL"' added)')"
|
|
102
|
+
|
|
66
103
|
BACKUP="$DEST/.anpunkit-backup-$(date +%Y%m%d-%H%M%S)"
|
|
67
104
|
backup_one() {
|
|
68
105
|
[ "$MODE" = "fresh" ] && return 0
|
|
@@ -77,6 +114,11 @@ backup_one() {
|
|
|
77
114
|
install_kit_file() {
|
|
78
115
|
local rel="$1" s="$SRC/$1" d="$DEST/$1"
|
|
79
116
|
[ -f "$s" ] || return 0
|
|
117
|
+
# v2.1 tool gating: cursor-only files skipped unless cursor selected.
|
|
118
|
+
# (.claude/* is shared infra — Cursor reads .claude/agents + .claude/hooks too.)
|
|
119
|
+
case "$rel" in
|
|
120
|
+
.cursor/*) tool_selected cursor || { act "skip $rel (cursor not selected)"; return 0; };;
|
|
121
|
+
esac
|
|
80
122
|
if [ ! -f "$d" ]; then
|
|
81
123
|
act "write $rel"; [ "$DRY" = 1 ] && return 0
|
|
82
124
|
mkdir -p "$(dirname "$d")"; cp -p "$s" "$d"; return 0
|
|
@@ -114,61 +156,76 @@ else
|
|
|
114
156
|
fi
|
|
115
157
|
|
|
116
158
|
# ---- generate command adapters from commands.src/ (manifest-owned output)
|
|
117
|
-
say ""; say "[2/6] generate command adapters:"
|
|
159
|
+
say ""; say "[2/6] generate command adapters (tools: $TOOLS):"
|
|
118
160
|
gen_adapters() {
|
|
119
|
-
mkdir -p "$DEST/.claude/commands"
|
|
161
|
+
tool_selected claude && mkdir -p "$DEST/.claude/commands"
|
|
162
|
+
tool_selected cursor && mkdir -p "$DEST/.cursor/commands" "$DEST/.cursor/rules"
|
|
120
163
|
for f in "$DEST"/commands.src/*.md; do
|
|
121
164
|
[ -f "$f" ] || continue
|
|
122
165
|
n=$(basename "$f")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
166
|
+
if tool_selected claude; then
|
|
167
|
+
act "claude .claude/commands/$n"
|
|
168
|
+
[ "$DRY" = 1 ] || cp -p "$f" "$DEST/.claude/commands/$n"
|
|
169
|
+
fi
|
|
170
|
+
if tool_selected cursor; then
|
|
171
|
+
act "cursor .cursor/commands/$n"
|
|
172
|
+
[ "$DRY" = 1 ] || awk 'BEGIN{fm=0} NR==1 && $0=="---"{fm=1; next} fm==1 && $0=="---"{fm=0; next} fm==0{print}' "$f" \
|
|
173
|
+
| sed '/^$/{1d}' > "$DEST/.cursor/commands/$n"
|
|
174
|
+
fi
|
|
129
175
|
done
|
|
130
176
|
}
|
|
131
177
|
gen_adapters
|
|
132
178
|
|
|
133
|
-
# ---- hybrid JSON merge: ensure the
|
|
134
|
-
say ""; say "[3/6] wire hooks (idempotent merge):"
|
|
179
|
+
# ---- hybrid JSON merge: ensure the hook entries for selected tools, never drop user keys
|
|
180
|
+
say ""; say "[3/6] wire hooks (idempotent merge, tools: $TOOLS):"
|
|
135
181
|
merge_settings() {
|
|
136
|
-
[ "$DRY" = 1 ] && { act "merge .claude/settings.json
|
|
137
|
-
|
|
182
|
+
[ "$DRY" = 1 ] && { act "merge $(tool_selected claude && echo -n '.claude/settings.json ')$(tool_selected cursor && echo -n '.cursor/hooks.json')"; return 0; }
|
|
183
|
+
tool_selected claude && backup_one "$DEST/.claude/settings.json"
|
|
184
|
+
tool_selected cursor && backup_one "$DEST/.cursor/hooks.json"
|
|
185
|
+
CLAUDE_SEL=$(tool_selected claude && echo 1 || echo 0) \
|
|
186
|
+
CURSOR_SEL=$(tool_selected cursor && echo 1 || echo 0) \
|
|
138
187
|
node - "$DEST" <<'NODE'
|
|
139
188
|
const fs=require('fs'),path=require('path');
|
|
140
189
|
const dest=process.argv[2];
|
|
190
|
+
const claudeSel=process.env.CLAUDE_SEL==='1', cursorSel=process.env.CURSOR_SEL==='1';
|
|
141
191
|
const load=(p,d)=>{try{return JSON.parse(fs.readFileSync(p,'utf8'))}catch(e){return d}};
|
|
142
192
|
const save=(p,o)=>{fs.mkdirSync(path.dirname(p),{recursive:true});fs.writeFileSync(p,JSON.stringify(o,null,2)+"\n")};
|
|
193
|
+
const out=[];
|
|
143
194
|
// Claude settings.json
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
s
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
ensure('
|
|
157
|
-
ensure('
|
|
158
|
-
|
|
195
|
+
if(claudeSel){
|
|
196
|
+
const sp=path.join(dest,'.claude/settings.json');
|
|
197
|
+
const s=load(sp,{});
|
|
198
|
+
if(!s.$schema)s.$schema='https://json.schemastore.org/claude-code-settings.json';
|
|
199
|
+
s.hooks=s.hooks||{};
|
|
200
|
+
const ensure=(event,matcher,cmd)=>{
|
|
201
|
+
const arr=s.hooks[event]=s.hooks[event]||[];
|
|
202
|
+
for(const blk of arr)for(const h of (blk.hooks||[]))if(h.command===cmd)return;
|
|
203
|
+
const blk={hooks:[{type:'command',command:cmd}]};
|
|
204
|
+
if(matcher)blk.matcher=matcher;
|
|
205
|
+
arr.push(blk);
|
|
206
|
+
};
|
|
207
|
+
ensure('SessionStart','startup|clear|compact','bash .claude/hooks/session-start.sh');
|
|
208
|
+
ensure('PreCompact','auto|manual','bash .claude/hooks/pre-compact.sh');
|
|
209
|
+
ensure('SubagentStop','','bash .claude/hooks/subagent-stop.sh');
|
|
210
|
+
save(sp,s);
|
|
211
|
+
out.push('.claude/settings.json');
|
|
212
|
+
}
|
|
159
213
|
// Cursor hooks.json
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
c
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
cursorEnsure('
|
|
169
|
-
cursorEnsure('
|
|
170
|
-
|
|
171
|
-
|
|
214
|
+
if(cursorSel){
|
|
215
|
+
const cp=path.join(dest,'.cursor/hooks.json');
|
|
216
|
+
const c=load(cp,{version:1,hooks:{}});
|
|
217
|
+
c.hooks=c.hooks||{};
|
|
218
|
+
const cursorEnsure=(event,cmd)=>{
|
|
219
|
+
const arr=c.hooks[event]=c.hooks[event]||[];
|
|
220
|
+
if(!arr.some(h=>h.command===cmd))arr.push({command:cmd});
|
|
221
|
+
};
|
|
222
|
+
cursorEnsure('sessionStart','bash .claude/hooks/cursor-session-start.sh');
|
|
223
|
+
cursorEnsure('preCompact','bash .claude/hooks/pre-compact.sh');
|
|
224
|
+
cursorEnsure('subagentStop','bash .claude/hooks/subagent-stop.sh');
|
|
225
|
+
save(cp,c);
|
|
226
|
+
out.push('.cursor/hooks.json');
|
|
227
|
+
}
|
|
228
|
+
console.log(' merged '+(out.join(' + ')||'(nothing — no tool selected hooks)'));
|
|
172
229
|
NODE
|
|
173
230
|
}
|
|
174
231
|
merge_settings
|
|
@@ -180,13 +237,17 @@ say ""; say "[4/6] AGENTS.md / CLAUDE.md verify:"
|
|
|
180
237
|
verify_import() {
|
|
181
238
|
local CL="$DEST/CLAUDE.md" AG="$DEST/AGENTS.md"
|
|
182
239
|
[ -f "$AG" ] || { echo "FATAL VERIFY: AGENTS.md not on disk"; exit 1; }
|
|
183
|
-
#
|
|
184
|
-
if ! grep -qE '^@AGENTS\.md[[:space:]]*$' "$CL"; then
|
|
185
|
-
echo "FATAL VERIFY: CLAUDE.md is missing a bare top-level '@AGENTS.md' import line"; exit 1; fi
|
|
186
|
-
# sentinel present in AGENTS.md
|
|
240
|
+
# sentinel present in AGENTS.md (shared — both tools read AGENTS.md)
|
|
187
241
|
if ! grep -q 'ANPUNKIT-AGENTS-SENTINEL' "$AG"; then
|
|
188
242
|
echo "FATAL VERIFY: AGENTS.md sentinel missing (file clobbered or empty?)"; exit 1; fi
|
|
189
|
-
|
|
243
|
+
# CLAUDE.md bare @AGENTS.md import is Claude-specific — only verify if claude selected
|
|
244
|
+
if tool_selected claude; then
|
|
245
|
+
if ! grep -qE '^@AGENTS\.md[[:space:]]*$' "$CL"; then
|
|
246
|
+
echo "FATAL VERIFY: CLAUDE.md is missing a bare top-level '@AGENTS.md' import line"; exit 1; fi
|
|
247
|
+
say " VERIFY ok: bare @AGENTS.md import + AGENTS.md on disk + sentinel present."
|
|
248
|
+
else
|
|
249
|
+
say " VERIFY ok: AGENTS.md on disk + sentinel present (claude not selected — CLAUDE.md import not required)."
|
|
250
|
+
fi
|
|
190
251
|
}
|
|
191
252
|
verify_import
|
|
192
253
|
|
|
@@ -194,7 +255,7 @@ verify_import
|
|
|
194
255
|
say ""; say "[5/6] .gitignore patch:"
|
|
195
256
|
patch_gitignore() {
|
|
196
257
|
local gi="$DEST/.gitignore"
|
|
197
|
-
local -a needles=("docs/.snapshots/" "docs/.kb-snapshot.md" ".anpunkit-backup-*/" "*.anpunkit-new")
|
|
258
|
+
local -a needles=("docs/.snapshots/" "docs/.kb-snapshot.md" "docs/evidence/" ".anpunkit-backup-*/" "*.anpunkit-new")
|
|
198
259
|
[ "$DRY" = 1 ] && { act "patch .gitignore (${#needles[@]} entries)"; return 0; }
|
|
199
260
|
touch "$gi"
|
|
200
261
|
for n in "${needles[@]}"; do grep -qxF "$n" "$gi" || printf '%s\n' "$n" >> "$gi"; done
|
|
@@ -244,12 +305,14 @@ configure_kb() {
|
|
|
244
305
|
|
|
245
306
|
configure_kb
|
|
246
307
|
|
|
247
|
-
# ---- finalize: stamp the installed manifest (copy the new one)
|
|
308
|
+
# ---- finalize: stamp the installed manifest (copy the new one) + record tools
|
|
248
309
|
if [ "$DRY" != 1 ]; then
|
|
249
310
|
cp -p "$MAN_SRC" "$MAN_DEST" 2>/dev/null || true
|
|
311
|
+
mkdir -p "$DEST/.claude"
|
|
312
|
+
node -e "require('fs').writeFileSync(process.argv[1],JSON.stringify({tools:process.argv[2].split(' ').filter(Boolean)},null,2)+'\n')" "$TOOLS_FILE" "$TOOLS" 2>/dev/null || true
|
|
250
313
|
[ -d "$BACKUP" ] && say "" && say "backup written: ${BACKUP#$DEST/}"
|
|
251
314
|
fi
|
|
252
315
|
|
|
253
|
-
say ""; say "anpunkit setup complete ($MODE). Open Claude Code or Cursor — the SessionStart/sessionStart hook fires automatically."
|
|
316
|
+
say ""; say "anpunkit setup complete ($MODE, tools: $TOOLS). Open Claude Code or Cursor — the SessionStart/sessionStart hook fires automatically."
|
|
254
317
|
if [ "$DRY" = 1 ]; then say "(dry-run: no files were written.)"; fi
|
|
255
318
|
exit 0
|