cc-discipline 2.12.0 → 2.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/init.sh +92 -2
  2. package/package.json +1 -1
package/init.sh CHANGED
@@ -367,12 +367,102 @@ cp "$SCRIPT_DIR/templates/.claude/agents/investigator.md" .claude/agents/
367
367
  echo -e "${GREEN}Installing skills...${NC}"
368
368
  # Install every skill directory under templates/ — no per-skill enumeration,
369
369
  # so adding a new skill needs zero changes here.
370
+ #
371
+ # Copy into an EXPLICIT destination directory. Do not use `cp -r "$skill_dir"
372
+ # .claude/skills/` here: the `*/` glob gives $skill_dir a trailing slash, and
373
+ # GNU and BSD cp disagree about what that means. GNU copies the directory;
374
+ # BSD (macOS) copies its *contents*, so every skill's SKILL.md landed on top of
375
+ # the previous one at .claude/skills/SKILL.md and no skill was ever updated.
376
+ # Shipped broken in v2.11.0 (the "directory-driven install" change) and fixed in
377
+ # v2.12.1. Symptom on an affected machine: a stray .claude/skills/SKILL.md plus
378
+ # skill dirs frozen at their first-install date. status/doctor could not catch
379
+ # it because they glob `skills/*/` and a bare file is not a directory.
380
+ # Skills are NOT force-overwritten. Users legitimately tune them per repo
381
+ # (/self-check's "Project-specific Checks" section exists precisely to be filled
382
+ # in), and until v2.12.2 every upgrade silently replaced those edits — a 73KB
383
+ # hand-written self-check was wiped twice in one day before this was fixed.
384
+ #
385
+ # The dpkg-conffile approach: remember the hash of the template WE installed. On
386
+ # the next upgrade, if the file on disk still matches that hash the user never
387
+ # touched it and we can update freely; if it differs the user owns it, so we keep
388
+ # their file and drop the new template beside it as SKILL.md.new.
389
+ #
390
+ # The manifest hash is always set to the template we shipped this run, including
391
+ # in the preserved case. That is what makes adoption self-healing: if the user
392
+ # later replaces their file with the .new one, the next run sees disk == manifest
393
+ # and resumes silent updates.
394
+ SKILLS_MANIFEST=".claude/.cc-discipline-skills.manifest"
395
+ NEW_MANIFEST=$(mktemp 2>/dev/null || echo ".claude/.skills-manifest.tmp")
396
+ PRESERVED_SKILLS=""
397
+
398
+ _cc_hash() {
399
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
400
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
401
+ elif command -v md5sum >/dev/null 2>&1; then md5sum "$1" | cut -d' ' -f1
402
+ elif command -v md5 >/dev/null 2>&1; then md5 -q "$1"
403
+ else cksum "$1" | cut -d' ' -f1; fi
404
+ }
405
+
370
406
  for skill_dir in "$SCRIPT_DIR"/templates/.claude/skills/*/; do
371
407
  [ -d "$skill_dir" ] || continue
372
- cp -r "$skill_dir" .claude/skills/
373
- echo "/$(basename "$skill_dir")"
408
+ skill_name=$(basename "$skill_dir")
409
+ mkdir -p ".claude/skills/$skill_name"
410
+ skill_status="updated"
411
+
412
+ for src in "$skill_dir"*; do
413
+ [ -f "$src" ] || continue
414
+ base=$(basename "$src")
415
+ rel="$skill_name/$base"
416
+ dst=".claude/skills/$rel"
417
+ tpl_hash=$(_cc_hash "$src")
418
+
419
+ if [ ! -f "$dst" ]; then
420
+ cp "$src" "$dst" # new file — nothing to protect
421
+ else
422
+ disk_hash=$(_cc_hash "$dst")
423
+ recorded=$(grep "^$rel " "$SKILLS_MANIFEST" 2>/dev/null | head -1 | cut -d' ' -f2)
424
+ if [ "$disk_hash" = "$tpl_hash" ]; then
425
+ : # already current
426
+ elif [ -n "$recorded" ] && [ "$disk_hash" = "$recorded" ]; then
427
+ cp "$src" "$dst" # pristine older version — safe
428
+ elif [ -z "$recorded" ]; then
429
+ # No manifest yet (first run on an existing install) and the file
430
+ # differs from the template. Cannot tell "older version" from
431
+ # "user edited", so assume the user's work matters.
432
+ cp "$src" "$dst.new"
433
+ skill_status="preserved"
434
+ else
435
+ cp "$src" "$dst.new" # user-modified — keep theirs
436
+ skill_status="preserved"
437
+ fi
438
+ fi
439
+ echo "$rel $tpl_hash" >> "$NEW_MANIFEST"
440
+ done
441
+
442
+ if [ "$skill_status" = "preserved" ]; then
443
+ PRESERVED_SKILLS="$PRESERVED_SKILLS $skill_name"
444
+ echo -e " ${YELLOW}~ /$skill_name (your version kept; new template at SKILL.md.new)${NC}"
445
+ else
446
+ echo " ✓ /$skill_name"
447
+ fi
374
448
  done
375
449
 
450
+ [ -f "$NEW_MANIFEST" ] && mv "$NEW_MANIFEST" "$SKILLS_MANIFEST"
451
+
452
+ if [ -n "$PRESERVED_SKILLS" ]; then
453
+ echo -e " ${YELLOW}Locally modified skills were not overwritten:${PRESERVED_SKILLS}${NC}"
454
+ echo -e " ${YELLOW}Compare with: diff .claude/skills/<name>/SKILL.md{,.new}${NC}"
455
+ echo -e " ${YELLOW}Take the new one with: mv .claude/skills/<name>/SKILL.md{.new,}${NC}"
456
+ fi
457
+
458
+ # Clean up the stray file left behind by the v2.11.0–v2.12.0 bug above. A bare
459
+ # SKILL.md directly under skills/ is never a valid skill (skills live in
460
+ # subdirectories), so this only ever removes that debris.
461
+ if [ -f ".claude/skills/SKILL.md" ]; then
462
+ rm -f ".claude/skills/SKILL.md"
463
+ echo -e " ${YELLOW}✓ removed stray .claude/skills/SKILL.md (v2.11.0 macOS install bug)${NC}"
464
+ fi
465
+
376
466
  # ─── Handle CLAUDE.md ───
377
467
  if [ ! -f "CLAUDE.md" ]; then
378
468
  # No CLAUDE.md exists — generate from template
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-discipline",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
4
4
  "description": "Discipline framework for Claude Code — rules, hooks, and agents that keep AI on track",
5
5
  "bin": {
6
6
  "cc-discipline": "bin/cli.js"