cc-discipline 2.13.0 → 2.13.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/init.sh +81 -27
- package/package.json +1 -1
package/init.sh
CHANGED
|
@@ -403,41 +403,95 @@ KEPT_RETIRED=""
|
|
|
403
403
|
OLD_MANIFEST=$(mktemp 2>/dev/null || echo ".claude/.skills-manifest.old")
|
|
404
404
|
[ -f "$SKILLS_MANIFEST" ] && cp "$SKILLS_MANIFEST" "$OLD_MANIFEST"
|
|
405
405
|
|
|
406
|
-
#
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
406
|
+
# ─── Skill content hashing ───
|
|
407
|
+
# Two properties matter here, and both were learned the hard way.
|
|
408
|
+
#
|
|
409
|
+
# 1. Record WHICH algorithm produced a digest. The fallback below resolves
|
|
410
|
+
# differently depending on what is installed, so a manifest holding a bare
|
|
411
|
+
# digest silently mis-compares the moment tool availability changes — every
|
|
412
|
+
# hash mismatches, every retired skill looks user-modified, and the
|
|
413
|
+
# retirement pass does nothing. (fixed 2026-08-28, before 2.13.0 shipped.)
|
|
414
|
+
#
|
|
415
|
+
# 2. Hash line-ending-NORMALIZED content, not raw bytes. Projects that commit
|
|
416
|
+
# .claude/ and run with core.autocrlf=true and no .gitattributes get their
|
|
417
|
+
# SKILL.md rewritten to CRLF by git on checkout. Byte-exact comparison then
|
|
418
|
+
# reports every skill as modified, with the same two consequences as above
|
|
419
|
+
# plus a .new file dropped beside every skill on every upgrade. Measured on
|
|
420
|
+
# one install: the file differed from its manifest entry by exactly 44 bytes
|
|
421
|
+
# across 44 lines, and matched perfectly once normalized. (fixed 2026-08-28)
|
|
422
|
+
#
|
|
423
|
+
# sha256sum and `shasum -a 256` produce identical digests, so both label sha256.
|
|
424
|
+
|
|
425
|
+
_cc_digest_with() { # $1=tool $2=file $3=norm|raw -> bare digest
|
|
426
|
+
if [ "$3" = "norm" ]; then
|
|
427
|
+
case "$1" in
|
|
428
|
+
sha256sum) sed 's/\r$//' "$2" | sha256sum | cut -d' ' -f1 ;;
|
|
429
|
+
shasum) sed 's/\r$//' "$2" | shasum -a 256 | cut -d' ' -f1 ;;
|
|
430
|
+
md5sum) sed 's/\r$//' "$2" | md5sum | cut -d' ' -f1 ;;
|
|
431
|
+
md5) sed 's/\r$//' "$2" | md5 -q ;;
|
|
432
|
+
cksum) sed 's/\r$//' "$2" | cksum | cut -d' ' -f1 ;;
|
|
433
|
+
esac
|
|
434
|
+
else
|
|
435
|
+
case "$1" in
|
|
436
|
+
sha256sum) sha256sum "$2" | cut -d' ' -f1 ;;
|
|
437
|
+
shasum) shasum -a 256 "$2" | cut -d' ' -f1 ;;
|
|
438
|
+
md5sum) md5sum "$2" | cut -d' ' -f1 ;;
|
|
439
|
+
md5) md5 -q "$2" ;;
|
|
440
|
+
cksum) cksum "$2" | cut -d' ' -f1 ;;
|
|
441
|
+
esac
|
|
442
|
+
fi
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
_cc_algo() {
|
|
446
|
+
for _a in sha256sum shasum md5sum md5 cksum; do
|
|
447
|
+
command -v "$_a" >/dev/null 2>&1 && { echo "$_a"; return; }
|
|
448
|
+
done
|
|
449
|
+
echo cksum
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
_cc_label() {
|
|
453
|
+
case "$1" in sha256sum|shasum) echo sha256 ;; md5sum|md5) echo md5 ;; *) echo cksum ;; esac
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# What gets written into the manifest: normalized, algorithm-labelled.
|
|
413
457
|
_cc_hash() {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
elif command -v md5sum >/dev/null 2>&1; then echo "md5:$(md5sum "$1" | cut -d' ' -f1)"
|
|
417
|
-
elif command -v md5 >/dev/null 2>&1; then echo "md5:$(md5 -q "$1")"
|
|
418
|
-
else echo "cksum:$(cksum "$1" | cut -d' ' -f1)"; fi
|
|
458
|
+
_a=$(_cc_algo)
|
|
459
|
+
echo "$(_cc_label "$_a"):$(_cc_digest_with "$_a" "$1" norm)"
|
|
419
460
|
}
|
|
420
461
|
|
|
421
|
-
# True when manifest entry $2 still describes
|
|
422
|
-
#
|
|
423
|
-
#
|
|
424
|
-
#
|
|
462
|
+
# True when manifest entry $2 still describes file $1. Accepts, in order of how
|
|
463
|
+
# the entry was most likely written:
|
|
464
|
+
# - "<algo>:<digest>" from 2.13.1+, normalized
|
|
465
|
+
# - "<algo>:<digest>" from 2.13.0, byte-exact
|
|
466
|
+
# - a bare digest from 2.12.2/2.12.3, algorithm and normalization both unknown
|
|
467
|
+
# Anything that matches under any of those readings counts as pristine. Being
|
|
468
|
+
# permissive here is the safe direction: a false "modified" freezes a skill in
|
|
469
|
+
# place forever and buries the user in .new files, while a false "pristine" can
|
|
470
|
+
# only overwrite a file that is byte-identical to what we would install anyway.
|
|
425
471
|
_cc_hash_matches() {
|
|
426
472
|
_f="$1"; _rec="$2"
|
|
427
473
|
[ -n "$_rec" ] || return 1
|
|
474
|
+
[ -f "$_f" ] || return 1
|
|
428
475
|
case "$_rec" in
|
|
429
|
-
*:*)
|
|
476
|
+
*:*)
|
|
477
|
+
_rl=${_rec%%:*}; _rd=${_rec#*:}
|
|
478
|
+
case "$_rl" in
|
|
479
|
+
sha256) _cands="sha256sum shasum" ;;
|
|
480
|
+
md5) _cands="md5sum md5" ;;
|
|
481
|
+
*) _cands="cksum" ;;
|
|
482
|
+
esac
|
|
483
|
+
for _al in $_cands; do
|
|
484
|
+
command -v "$_al" >/dev/null 2>&1 || continue
|
|
485
|
+
[ "$(_cc_digest_with "$_al" "$_f" norm)" = "$_rd" ] && return 0
|
|
486
|
+
[ "$(_cc_digest_with "$_al" "$_f" raw)" = "$_rd" ] && return 0
|
|
487
|
+
done
|
|
488
|
+
return 1
|
|
489
|
+
;;
|
|
430
490
|
esac
|
|
431
|
-
for
|
|
432
|
-
command -v "$
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
shasum) _got=$(shasum -a 256 "$_f" | cut -d' ' -f1) ;;
|
|
436
|
-
md5sum) _got=$(md5sum "$_f" | cut -d' ' -f1) ;;
|
|
437
|
-
md5) _got=$(md5 -q "$_f") ;;
|
|
438
|
-
cksum) _got=$(cksum "$_f" | cut -d' ' -f1) ;;
|
|
439
|
-
esac
|
|
440
|
-
[ "$_got" = "$_rec" ] && return 0
|
|
491
|
+
for _al in sha256sum shasum md5sum md5 cksum; do
|
|
492
|
+
command -v "$_al" >/dev/null 2>&1 || continue
|
|
493
|
+
[ "$(_cc_digest_with "$_al" "$_f" norm)" = "$_rec" ] && return 0
|
|
494
|
+
[ "$(_cc_digest_with "$_al" "$_f" raw)" = "$_rec" ] && return 0
|
|
441
495
|
done
|
|
442
496
|
return 1
|
|
443
497
|
}
|