create-anpunkit 2.0.0 → 2.0.1
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
CHANGED
|
@@ -16,10 +16,56 @@ if (!fs.existsSync(SETUP)) {
|
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// --- locate a REAL bash on Windows (never the System32 WSL relay).
|
|
20
|
+
// In PowerShell, bare `bash` resolves to C:\Windows\System32\bash.exe, which
|
|
21
|
+
// relays into WSL and fails with "execvpe(/bin/bash) failed" when no distro
|
|
22
|
+
// is installed. We must find Git Bash explicitly.
|
|
23
|
+
function findBash() {
|
|
24
|
+
if (process.platform !== 'win32') return 'bash';
|
|
25
|
+
const candidates = [];
|
|
26
|
+
if (process.env.ANPUNKIT_BASH) candidates.push(process.env.ANPUNKIT_BASH);
|
|
27
|
+
// Derive from git on PATH: <Git>\cmd\git.exe or <Git>\mingw64\bin\git.exe -> <Git>\bin\bash.exe
|
|
28
|
+
try {
|
|
29
|
+
const r = spawnSync('where', ['git'], { encoding: 'utf8' });
|
|
30
|
+
if (r.status === 0) {
|
|
31
|
+
for (const line of r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean)) {
|
|
32
|
+
const d1 = path.resolve(path.dirname(line), '..');
|
|
33
|
+
const d2 = path.resolve(d1, '..');
|
|
34
|
+
candidates.push(path.join(d1, 'bin', 'bash.exe'), path.join(d2, 'bin', 'bash.exe'));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (_) { /* where.exe missing — fall through */ }
|
|
38
|
+
// Standard Git for Windows install locations
|
|
39
|
+
for (const base of [
|
|
40
|
+
process.env.ProgramFiles,
|
|
41
|
+
process.env['ProgramFiles(x86)'],
|
|
42
|
+
process.env.LocalAppData ? path.join(process.env.LocalAppData, 'Programs') : null
|
|
43
|
+
]) {
|
|
44
|
+
if (base) candidates.push(path.join(base, 'Git', 'bin', 'bash.exe'));
|
|
45
|
+
}
|
|
46
|
+
for (const c of candidates) {
|
|
47
|
+
if (c && !/system32/i.test(c) && fs.existsSync(c)) return c;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bash = findBash();
|
|
53
|
+
if (!bash) {
|
|
54
|
+
console.error('create-anpunkit: could not find Git Bash.');
|
|
55
|
+
console.error('The Windows `bash` on PATH is the WSL relay (System32), which is not usable here.');
|
|
56
|
+
console.error('Fix one of:');
|
|
57
|
+
console.error(' 1. Install Git for Windows (includes Git Bash): https://git-scm.com/download/win');
|
|
58
|
+
console.error(' 2. Run `npx create-anpunkit` from a Git Bash terminal instead of PowerShell.');
|
|
59
|
+
console.error(' 3. Set ANPUNKIT_BASH to the full path of a bash.exe.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
19
63
|
// Pass through recognised flags only; setup.sh validates the rest.
|
|
20
64
|
const passthrough = ['--kb-path', '--kb-remote', '--no-kb', '--force', '--dry-run'];
|
|
21
65
|
const argv = process.argv.slice(2);
|
|
22
|
-
|
|
66
|
+
// Git Bash is happiest with forward slashes; Windows APIs accept them too.
|
|
67
|
+
const fwd = p => p.split(path.sep).join('/');
|
|
68
|
+
const args = ['--src', fwd(TEMPLATE)];
|
|
23
69
|
for (let i = 0; i < argv.length; i++) {
|
|
24
70
|
const a = argv[i];
|
|
25
71
|
if (passthrough.includes(a)) {
|
|
@@ -31,11 +77,10 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
31
77
|
}
|
|
32
78
|
}
|
|
33
79
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const r = spawnSync(bash, [SETUP, ...args], { stdio: 'inherit', cwd: process.cwd() });
|
|
80
|
+
console.log(`create-anpunkit -> running setup.sh (bash: ${bash})`);
|
|
81
|
+
const r = spawnSync(bash, [fwd(SETUP), ...args], { stdio: 'inherit', cwd: process.cwd() });
|
|
37
82
|
if (r.error) {
|
|
38
|
-
console.error(
|
|
83
|
+
console.error(`create-anpunkit: failed to run ${bash}: ${r.error.message}`);
|
|
39
84
|
process.exit(1);
|
|
40
85
|
}
|
|
41
86
|
process.exit(r.status === null ? 1 : r.status);
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.0.
|
|
2
|
+
"version": "2.0.1",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": "AGENTS.md",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"path": "setup.sh",
|
|
22
|
-
"sha256": "
|
|
22
|
+
"sha256": "b6b2f2adbff866f8ed0398006ab2eefb346d9a81b50dc9bccbb3a8a22f6a1b04"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"path": ".claude/agents/debugger.md",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"path": ".claude/hooks/cursor-session-start.sh",
|
|
58
|
-
"sha256": "
|
|
58
|
+
"sha256": "e08bcd14fbcfe1831b64e7d8f181b57b02c561cd8d0de6eb16fe06db259be6af"
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"path": ".claude/hooks/pre-compact.sh",
|
|
@@ -231,11 +231,11 @@
|
|
|
231
231
|
},
|
|
232
232
|
{
|
|
233
233
|
"path": "create-anpunkit/package.json",
|
|
234
|
-
"sha256": "
|
|
234
|
+
"sha256": "3fa34141b9698d4ceb6a17d036e86d115de8cd5371c63df3f25705fe2b518d14"
|
|
235
235
|
},
|
|
236
236
|
{
|
|
237
237
|
"path": "create-anpunkit/bin/cli.js",
|
|
238
|
-
"sha256": "
|
|
238
|
+
"sha256": "129dae04923b6d380d3ca4601b5dcd2baa6ad4c51c827dddae3e43ea6a9c50e2"
|
|
239
239
|
},
|
|
240
240
|
{
|
|
241
241
|
"path": "create-anpunkit/build.sh",
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
# session-start.sh body (single copy — anti-drift) and wraps its output in the
|
|
6
6
|
# JSON envelope Cursor requires. Cursor sets CLAUDE_PROJECT_DIR (compat alias),
|
|
7
7
|
# so the shared script needs no changes.
|
|
8
|
+
# Uses node for JSON (the kit assumes Node; python3 is a fallback only).
|
|
8
9
|
# Verified against cursor.com/docs/hooks (sessionStart output schema), 2026-06.
|
|
9
10
|
set -euo pipefail
|
|
10
11
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
11
12
|
OUT="$(bash "$HERE/session-start.sh" 2>/dev/null || true)"
|
|
12
|
-
|
|
13
|
-
$OUT
|
|
14
|
-
|
|
13
|
+
if command -v node >/dev/null 2>&1; then
|
|
14
|
+
printf '%s' "$OUT" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(JSON.stringify({additional_context:d})))'
|
|
15
|
+
else
|
|
16
|
+
printf '%s' "$OUT" | python3 -c 'import json,sys; print(json.dumps({"additional_context": sys.stdin.read()}))'
|
|
17
|
+
fi
|
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
Newest first. One entry per architectural change. Appended by `/log-decision`.
|
|
19
19
|
|
|
20
|
+
- **(v2.0.1)** — Windows portability fix for the npx installer. (1) `cli.js` no
|
|
21
|
+
longer trusts bare `bash` on win32: PowerShell resolves it to the System32 WSL
|
|
22
|
+
relay, which fails with `execvpe(/bin/bash)` when no distro is installed. The
|
|
23
|
+
bin now locates Git Bash explicitly (ANPUNKIT_BASH override -> derive from
|
|
24
|
+
`where git` -> standard install paths), refuses System32, and fails loudly with
|
|
25
|
+
install guidance. (2) `setup.sh` and `cursor-session-start.sh` no longer depend
|
|
26
|
+
on `python3` (absent or a Store stub on many Windows machines): all JSON and
|
|
27
|
+
checksum work now uses Node, which the kit already requires. Paths passed into
|
|
28
|
+
Git Bash are normalized to forward slashes.
|
|
29
|
+
|
|
20
30
|
- **(v2.0)** — Major release. Project name: **anpunkit**; distributed
|
|
21
31
|
as `npx create-anpunkit`. Five locked features: (1) **TDD-first** via a SCAFFOLD
|
|
22
32
|
step (implementer writes stubs only; test-author writes the suite blind against
|
package/template/setup.sh
CHANGED
|
@@ -39,7 +39,7 @@ act() { if [ "$DRY" = 1 ]; then say " [dry-run] $*"; else say " $*"; fi; }
|
|
|
39
39
|
sha() {
|
|
40
40
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
|
|
41
41
|
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
|
|
42
|
-
else
|
|
42
|
+
else node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync(process.argv[1])).digest('hex'))" "$1"; fi
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
say "anpunkit setup — dest: $DEST src: $SRC $([ "$DRY" = 1 ] && echo '(DRY-RUN)')"
|
|
@@ -47,12 +47,12 @@ say "anpunkit setup — dest: $DEST src: $SRC $([ "$DRY" = 1 ] && echo '(DRY-R
|
|
|
47
47
|
MAN_SRC="$SRC/.claude/anpunkit-manifest.json"
|
|
48
48
|
MAN_DEST="$DEST/.claude/anpunkit-manifest.json"
|
|
49
49
|
[ -f "$MAN_SRC" ] || { echo "FATAL: missing $MAN_SRC (corrupt kit)"; exit 1; }
|
|
50
|
-
NEW_VER=$(
|
|
50
|
+
NEW_VER=$(node -e "console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version)" "$MAN_SRC")
|
|
51
51
|
|
|
52
52
|
# ---- determine mode from installed manifest
|
|
53
53
|
MODE="fresh"
|
|
54
54
|
if [ -f "$MAN_DEST" ]; then
|
|
55
|
-
OLD_VER=$(
|
|
55
|
+
OLD_VER=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')}catch(e){console.log('')}" "$MAN_DEST" 2>/dev/null || echo "")
|
|
56
56
|
if [ "$OLD_VER" = "$NEW_VER" ]; then MODE="repair"
|
|
57
57
|
elif [ -z "$OLD_VER" ]; then MODE="upgrade"
|
|
58
58
|
else
|
|
@@ -82,19 +82,23 @@ install_kit_file() {
|
|
|
82
82
|
mkdir -p "$(dirname "$d")"; cp -p "$s" "$d"; return 0
|
|
83
83
|
fi
|
|
84
84
|
# present — compare against the INSTALLED manifest's recorded checksum
|
|
85
|
-
local recorded; recorded=$(
|
|
86
|
-
|
|
87
|
-
try
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
local recorded; recorded=$(node - "$MAN_DEST" "$rel" <<'NODE' 2>/dev/null || true
|
|
86
|
+
const fs=require('fs');
|
|
87
|
+
try{
|
|
88
|
+
const m=JSON.parse(fs.readFileSync(process.argv[2],'utf8'));
|
|
89
|
+
const f=(m.files||[]).find(f=>f.path===process.argv[3]);
|
|
90
|
+
console.log(f?f.sha256:'');
|
|
91
|
+
}catch(e){console.log('');}
|
|
92
|
+
NODE
|
|
92
93
|
)
|
|
93
94
|
local cur; cur=$(sha "$d")
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
local new; new=$(sha "$s")
|
|
96
|
+
# already identical to the new version -> nothing to do (regardless of manifest)
|
|
97
|
+
if [ "$cur" = "$new" ]; then act "ok $rel (identical)"; return 0; fi
|
|
98
|
+
# NOTE: bash gives || and && EQUAL precedence (left-assoc) — group explicitly.
|
|
99
|
+
if [ "$cur" = "$recorded" ] || { [ -z "$recorded" ] && [ "$SRC" = "." ]; }; then
|
|
100
|
+
# unmodified since last install (or clone flow) -> refresh to the new version
|
|
101
|
+
backup_one "$d"; act "refresh $rel"; [ "$DRY" = 1 ] || cp -p "$s" "$d"
|
|
98
102
|
else
|
|
99
103
|
# user-modified kit file
|
|
100
104
|
if [ "$FORCE" = 1 ]; then backup_one "$d"; act "force $rel (overwritten)"; [ "$DRY" = 1 ] || cp -p "$s" "$d"
|
|
@@ -104,7 +108,7 @@ PY
|
|
|
104
108
|
|
|
105
109
|
say ""; say "[1/6] kit-owned files (taxonomy):"
|
|
106
110
|
if [ "$SRC" != "." ]; then
|
|
107
|
-
while IFS= read -r rel; do install_kit_file "$rel"; done < <(
|
|
111
|
+
while IFS= read -r rel; do install_kit_file "$rel"; done < <(node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).files.forEach(f=>console.log(f.path))" "$MAN_SRC")
|
|
108
112
|
else
|
|
109
113
|
say " clone flow (src=.) — kit files already in place; verifying + generating only."
|
|
110
114
|
fi
|
|
@@ -131,44 +135,41 @@ say ""; say "[3/6] wire hooks (idempotent merge):"
|
|
|
131
135
|
merge_settings() {
|
|
132
136
|
[ "$DRY" = 1 ] && { act "merge .claude/settings.json + .cursor/hooks.json"; return 0; }
|
|
133
137
|
backup_one "$DEST/.claude/settings.json"; backup_one "$DEST/.cursor/hooks.json"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
dest=
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
s=
|
|
143
|
-
s.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
ensure(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
c=
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
json.dump(c,open(cp,"w"),indent=2); open(cp,"a").write("\n")
|
|
170
|
-
print(" merged .claude/settings.json + .cursor/hooks.json")
|
|
171
|
-
PY
|
|
138
|
+
node - "$DEST" <<'NODE'
|
|
139
|
+
const fs=require('fs'),path=require('path');
|
|
140
|
+
const dest=process.argv[2];
|
|
141
|
+
const load=(p,d)=>{try{return JSON.parse(fs.readFileSync(p,'utf8'))}catch(e){return d}};
|
|
142
|
+
const save=(p,o)=>{fs.mkdirSync(path.dirname(p),{recursive:true});fs.writeFileSync(p,JSON.stringify(o,null,2)+"\n")};
|
|
143
|
+
// Claude settings.json
|
|
144
|
+
const sp=path.join(dest,'.claude/settings.json');
|
|
145
|
+
const s=load(sp,{});
|
|
146
|
+
if(!s.$schema)s.$schema='https://json.schemastore.org/claude-code-settings.json';
|
|
147
|
+
s.hooks=s.hooks||{};
|
|
148
|
+
const ensure=(event,matcher,cmd)=>{
|
|
149
|
+
const arr=s.hooks[event]=s.hooks[event]||[];
|
|
150
|
+
for(const blk of arr)for(const h of (blk.hooks||[]))if(h.command===cmd)return;
|
|
151
|
+
const blk={hooks:[{type:'command',command:cmd}]};
|
|
152
|
+
if(matcher)blk.matcher=matcher;
|
|
153
|
+
arr.push(blk);
|
|
154
|
+
};
|
|
155
|
+
ensure('SessionStart','startup|clear|compact','bash .claude/hooks/session-start.sh');
|
|
156
|
+
ensure('PreCompact','auto|manual','bash .claude/hooks/pre-compact.sh');
|
|
157
|
+
ensure('SubagentStop','','bash .claude/hooks/subagent-stop.sh');
|
|
158
|
+
save(sp,s);
|
|
159
|
+
// Cursor hooks.json
|
|
160
|
+
const cp=path.join(dest,'.cursor/hooks.json');
|
|
161
|
+
const c=load(cp,{version:1,hooks:{}});
|
|
162
|
+
c.hooks=c.hooks||{};
|
|
163
|
+
const cursorEnsure=(event,cmd)=>{
|
|
164
|
+
const arr=c.hooks[event]=c.hooks[event]||[];
|
|
165
|
+
if(!arr.some(h=>h.command===cmd))arr.push({command:cmd});
|
|
166
|
+
};
|
|
167
|
+
cursorEnsure('sessionStart','bash .claude/hooks/cursor-session-start.sh');
|
|
168
|
+
cursorEnsure('preCompact','bash .claude/hooks/pre-compact.sh');
|
|
169
|
+
cursorEnsure('subagentStop','bash .claude/hooks/subagent-stop.sh');
|
|
170
|
+
save(cp,c);
|
|
171
|
+
console.log(' merged .claude/settings.json + .cursor/hooks.json');
|
|
172
|
+
NODE
|
|
172
173
|
}
|
|
173
174
|
merge_settings
|
|
174
175
|
|
|
@@ -220,7 +221,7 @@ configure_kb() {
|
|
|
220
221
|
git ls-remote "$KB_REMOTE" >/dev/null 2>&1 || say " ! warning: cannot reach KB remote '$KB_REMOTE' (configuring anyway)."; fi
|
|
221
222
|
[ "$DRY" = 1 ] && { act "write .claude/kb-config.json"; return 0; }
|
|
222
223
|
mkdir -p "$DEST/.claude"
|
|
223
|
-
|
|
224
|
+
node -e "require('fs').writeFileSync(process.argv[1],JSON.stringify({path:process.argv[2],remote:process.argv[3]},null,2)+'\n')" "$cfg" "$KB_PATH" "$KB_REMOTE"
|
|
224
225
|
say " wrote .claude/kb-config.json"
|
|
225
226
|
}
|
|
226
227
|
configure_kb
|