fix(memory): test covers CLAUDE.global.md guard entry; doctor asserts exact global symlink target
This commit is contained in:
@@ -7,6 +7,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- BREAKING(layout): repo-root global memory renamed CLAUDE.md → CLAUDE.global.md; run `bash link.sh` once after pulling (doctor.sh now checks the exact target)
|
||||||
- graphify skill dist refreshed 0.8.45 → 0.9.6 (out-of-band `make plugin`; SKILL.md + query/extraction references updated by the generator).
|
- graphify skill dist refreshed 0.8.45 → 0.9.6 (out-of-band `make plugin`; SKILL.md + query/extraction references updated by the generator).
|
||||||
- `/deploy` checklist reshaped on first-real-run feedback, in two passes: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners — step = comment header + command lines up to the next blank line, a `@delta:` directive governs the whole block; and the checklist is now **display-only** — `NEXT.sh` is no longer written at all (throwaway artifact; `PENDING.json` + the live runbook regenerate it in any session) and every hand-back **ends the turn with the full checklist as the final text, no tool call after it** (a checklist printed above a blocking question tool was observed never reaching the user). Template `templates/deploy/PROCEDURE.md` restyled to match.
|
- `/deploy` checklist reshaped on first-real-run feedback, in two passes: runbook steps are **one command per line, interactive-session style** (an early step opens the ssh session; later lines run on the box; local steps say "from your machine") instead of folded `ssh host "cd … && …"` one-liners — step = comment header + command lines up to the next blank line, a `@delta:` directive governs the whole block; and the checklist is now **display-only** — `NEXT.sh` is no longer written at all (throwaway artifact; `PENDING.json` + the live runbook regenerate it in any session) and every hand-back **ends the turn with the full checklist as the final text, no tool call after it** (a checklist printed above a blocking question tool was observed never reaching the user). Template `templates/deploy/PROCEDURE.md` restyled to match.
|
||||||
- `settings.json`: `inputNeededNotifEnabled: true` adopted (harness notification toggle); committed layout otherwise unchanged.
|
- `settings.json`: `inputNeededNotifEnabled: true` adopted (harness notification toggle); committed layout otherwise unchanged.
|
||||||
|
|||||||
@@ -63,6 +63,17 @@ check_symlink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_symlink "CLAUDE.md"
|
check_symlink "CLAUDE.md"
|
||||||
|
# check_symlink only asserts the canonical path lands inside $REPO — after a
|
||||||
|
# `git pull` without `link.sh`, ~/.claude/CLAUDE.md can still resolve inside
|
||||||
|
# $REPO but at the wrong file (the 29-line project CLAUDE.md instead of
|
||||||
|
# CLAUDE.global.md), passing green while the global doctrine is silently gone.
|
||||||
|
_claude_md_target=$(readlink "$HOME/.claude/CLAUDE.md" 2>/dev/null || true)
|
||||||
|
if [ "$_claude_md_target" != "$REPO/CLAUDE.global.md" ]; then
|
||||||
|
# shellcheck disable=SC2088 # literal label, not a tilde-expansion attempt
|
||||||
|
warn "~/.claude/CLAUDE.md points to $_claude_md_target — expected \
|
||||||
|
$REPO/CLAUDE.global.md; run: bash link.sh"
|
||||||
|
fi
|
||||||
|
unset _claude_md_target
|
||||||
check_symlink "settings.json"
|
check_symlink "settings.json"
|
||||||
check_symlink "agents"
|
check_symlink "agents"
|
||||||
check_symlink "skills"
|
check_symlink "skills"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# single-occurrence + column-0 closing brace) so drift in install-plugins.sh
|
# single-occurrence + column-0 closing brace) so drift in install-plugins.sh
|
||||||
# propagates into this test instead of testing a stale copy. GUARDED_CONFIGS,
|
# propagates into this test instead of testing a stale copy. GUARDED_CONFIGS,
|
||||||
# CFG_SNAPSHOT, REPO and an info() stub are defined here — the array literal
|
# CFG_SNAPSHOT, REPO and an info() stub are defined here — the array literal
|
||||||
# at install-plugins.sh:41 is outside the extracted range.
|
# at install-plugins.sh:43-44 is outside the extracted range.
|
||||||
set -u
|
set -u
|
||||||
INSTALL_SH="$(cd "$(dirname "$0")/../.." && pwd)/install-plugins.sh"
|
INSTALL_SH="$(cd "$(dirname "$0")/../.." && pwd)/install-plugins.sh"
|
||||||
pass=0; fail=0
|
pass=0; fail=0
|
||||||
@@ -19,11 +19,12 @@ awk '/^restore_curated_configs\(\) \{/,/^\}/' "$INSTALL_SH" > "$SUT"
|
|||||||
REPO="$(mktemp -d)"
|
REPO="$(mktemp -d)"
|
||||||
CFG_SNAPSHOT="$(mktemp -d)"
|
CFG_SNAPSHOT="$(mktemp -d)"
|
||||||
EXPECT="$(mktemp -d)" # our own reference copy — independent of CFG_SNAPSHOT (SUT rm -rf's it)
|
EXPECT="$(mktemp -d)" # our own reference copy — independent of CFG_SNAPSHOT (SUT rm -rf's it)
|
||||||
GUARDED_CONFIGS=("CLAUDE.md" ".claude/settings.json" "settings.json")
|
GUARDED_CONFIGS=("CLAUDE.md" "CLAUDE.global.md" ".claude/settings.json" "settings.json")
|
||||||
info() { :; } # stub — extracted body calls info(), irrelevant to the assertions
|
info() { :; } # stub — extracted body calls info(), irrelevant to the assertions
|
||||||
|
|
||||||
mkdir -p "$REPO/.claude"
|
mkdir -p "$REPO/.claude"
|
||||||
printf 'CLAUDE original\n' > "$REPO/CLAUDE.md"
|
printf 'CLAUDE original\n' > "$REPO/CLAUDE.md"
|
||||||
|
printf 'CLAUDE.global original\n' > "$REPO/CLAUDE.global.md"
|
||||||
printf '{"a":1}\n' > "$REPO/.claude/settings.json"
|
printf '{"a":1}\n' > "$REPO/.claude/settings.json"
|
||||||
printf '{"b":2}\n' > "$REPO/settings.json"
|
printf '{"b":2}\n' > "$REPO/settings.json"
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ for f in "${GUARDED_CONFIGS[@]}"; do
|
|||||||
cp "$REPO/$f" "$EXPECT/$f"
|
cp "$REPO/$f" "$EXPECT/$f"
|
||||||
done
|
done
|
||||||
|
|
||||||
# simulate installer drift: mutate ONE guarded file, leave the other two alone
|
# simulate installer drift: mutate ONE guarded file, leave the other three alone
|
||||||
printf 'CLAUDE CLOBBERED BY INSTALLER\n' > "$REPO/CLAUDE.md"
|
printf 'CLAUDE CLOBBERED BY INSTALLER\n' > "$REPO/CLAUDE.md"
|
||||||
|
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
@@ -42,20 +43,22 @@ restore_curated_configs
|
|||||||
|
|
||||||
cmp -s "$REPO/CLAUDE.md" "$EXPECT/CLAUDE.md"
|
cmp -s "$REPO/CLAUDE.md" "$EXPECT/CLAUDE.md"
|
||||||
check T1-mutated-file-restored "$?" 0
|
check T1-mutated-file-restored "$?" 0
|
||||||
|
cmp -s "$REPO/CLAUDE.global.md" "$EXPECT/CLAUDE.global.md"
|
||||||
|
check T2-untouched-global-md-unchanged "$?" 0
|
||||||
cmp -s "$REPO/.claude/settings.json" "$EXPECT/.claude/settings.json"
|
cmp -s "$REPO/.claude/settings.json" "$EXPECT/.claude/settings.json"
|
||||||
check T2-untouched-local-settings-unchanged "$?" 0
|
check T3-untouched-local-settings-unchanged "$?" 0
|
||||||
cmp -s "$REPO/settings.json" "$EXPECT/settings.json"
|
cmp -s "$REPO/settings.json" "$EXPECT/settings.json"
|
||||||
check T3-untouched-settings-unchanged "$?" 0
|
check T4-untouched-settings-unchanged "$?" 0
|
||||||
if [ -d "$CFG_SNAPSHOT" ]; then r4=present; else r4=gone; fi
|
if [ -d "$CFG_SNAPSHOT" ]; then r5=present; else r5=gone; fi
|
||||||
check T4-snapshot-dir-removed "$r4" gone
|
check T5-snapshot-dir-removed "$r5" gone
|
||||||
|
|
||||||
# --- T5: mktemp failure -> fail-closed (install-plugins.sh, the header block
|
# --- T6: mktemp failure -> fail-closed (install-plugins.sh, the header block
|
||||||
# that builds CFG_SNAPSHOT) — refuses to run unguarded instead of warning and
|
# that builds CFG_SNAPSHOT) — refuses to run unguarded instead of warning and
|
||||||
# continuing. Extracted with a WIDER range than the SUT above: this logic
|
# continuing. Extracted with a WIDER range than the SUT above: this logic
|
||||||
# lives in the top-level if/else, outside restore_curated_configs().
|
# lives in the top-level if/else, outside restore_curated_configs().
|
||||||
SUT2="$(mktemp)"
|
SUT2="$(mktemp)"
|
||||||
awk '/^GUARDED_CONFIGS=/,/^fi$/' "$INSTALL_SH" > "$SUT2"
|
awk '/^GUARDED_CONFIGS=/,/^fi$/' "$INSTALL_SH" > "$SUT2"
|
||||||
ERR5="$(mktemp)"
|
ERR6="$(mktemp)"
|
||||||
(
|
(
|
||||||
# shellcheck disable=SC2329 # invoked indirectly by the sourced snippet below
|
# shellcheck disable=SC2329 # invoked indirectly by the sourced snippet below
|
||||||
mktemp() { return 1; } # force the header's CFG_SNAPSHOT creation to fail
|
mktemp() { return 1; } # force the header's CFG_SNAPSHOT creation to fail
|
||||||
@@ -68,11 +71,11 @@ ERR5="$(mktemp)"
|
|||||||
REPO="$(command mktemp -d)"
|
REPO="$(command mktemp -d)"
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
source "$SUT2"
|
source "$SUT2"
|
||||||
) >/dev/null 2>"$ERR5"
|
) >/dev/null 2>"$ERR6"
|
||||||
rc5=$?
|
rc6=$?
|
||||||
check T5-mktemp-failure-aborts "$rc5" 1
|
check T6-mktemp-failure-aborts "$rc6" 1
|
||||||
if grep -qi 'mktemp failed' "$ERR5"; then r5msg=yes; else r5msg=no; fi
|
if grep -qi 'mktemp failed' "$ERR6"; then r6msg=yes; else r6msg=no; fi
|
||||||
check T5-mktemp-failure-loud "$r5msg" yes
|
check T6-mktemp-failure-loud "$r6msg" yes
|
||||||
rm -f "$ERR5" "$SUT2"
|
rm -f "$ERR6" "$SUT2"
|
||||||
|
|
||||||
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user