From b8cfa61e55a2678dde33aafcff1c3c676247e1b0 Mon Sep 17 00:00:00 2001 From: lllyys Date: Wed, 29 Jul 2026 19:59:08 +0800 Subject: [PATCH 1/2] chore: remove tdd-guardian plugin footprint from the repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tdd-guardian gate has been non-enforcing for months (enforceOnTaskCompleted false; warn-only PreToolUse) and is redundant with the repo's real test authority: rule 10's Gate-3 run-tests.sh discipline (inline) + rule 55's orchestrator independent re-run (dispatch, never trusts a HANDOFF's pass claim). Reviewed by a 5-angle grill + two Codex passes → full-removal. The plugin stays ENABLED GLOBALLY (user ~/.claude.json, untouched) — this only removes its footprint from THIS repo: - Disable it for the repo: .claude/settings.json enabledPlugins "tdd-guardian@xiaolai": false (project settings take precedence over global, so vreader sessions stop loading its ~14 skills + 7 agents; other repos unaffected). - Delete the dead wrapper scripts/tdd-guardian-test.sh + its test, and the project config .claude/tdd-guardian/{config,state}.json. The wrapper's ONLY caller was the guardian's testCommand; its platform routing is a thin call into the shared code_paths_platform classifier, which the merge hooks + skills already use directly. - Fix the now-dangling references in active docs: rule 10, AGENTS.md, .claude/README.md (tree + Guardian Configs), .gitignore (dead state.json ignore), ADR 0001 (stale example). NOT touched (intentional): canon/* (rule 56 — the staleness sweep records the deleted artifact as missing: and demotes automation-tooling.md to stale; the next compile reconciles — never hand-edited) and historical records (dev-docs/plans, dev-docs/verification, .claude/codex-audits, the #107 row in docs/features.md — they record what was true then). Codex audit of the diff: clean. --- .claude/README.md | 2 - .claude/rules/10-tdd.md | 2 - .claude/settings.json | 4 +- .claude/tdd-guardian/config.json | 13 ---- .claude/tdd-guardian/state.json | 1 - .gitignore | 2 - AGENTS.md | 8 +-- docs/decisions/0001-android-port-strategy.md | 2 +- scripts/__tests__/tdd-guardian-test.test.sh | 54 ---------------- scripts/tdd-guardian-test.sh | 65 -------------------- 10 files changed, 8 insertions(+), 145 deletions(-) delete mode 100644 .claude/tdd-guardian/config.json delete mode 100644 .claude/tdd-guardian/state.json delete mode 100755 scripts/__tests__/tdd-guardian-test.test.sh delete mode 100755 scripts/tdd-guardian-test.sh diff --git a/.claude/README.md b/.claude/README.md index aaded66fc..24f53f5f9 100644 --- a/.claude/README.md +++ b/.claude/README.md @@ -33,7 +33,6 @@ Claude writes the code; Codex audits it independently. Cross-model verification ├── agents/ # Subagent definitions for /feature-workflow ├── hooks/ # UserPromptSubmit hook (>>-prefix prompt refinement) ├── docs-guardian/ # (currently empty — see Notes below) -├── tdd-guardian/ # TDD Guardian config (xcodebuild test command) └── loc-guardian.local.md # Per-file LOC limit + Swift extraction patterns ``` @@ -92,7 +91,6 @@ orchestrating session + the rules they duplicated. ## Guardian Configs -- **`tdd-guardian/config.json`** — drives the TDD Guardian agents. Configured with vreader's `xcodebuild build-for-testing && xcodebuild test-without-building` flow. - **`docs-guardian/`** — directory exists but the config has been removed; vreader has no website docs that need automated audit. - **`loc-guardian.local.md`** — 300-line cap per file, with Swift-aware extraction patterns (PersistenceActor extensions, ReaderContainerView+Concern.swift, etc.). diff --git a/.claude/rules/10-tdd.md b/.claude/rules/10-tdd.md index 407e81c0b..9ee015279 100644 --- a/.claude/rules/10-tdd.md +++ b/.claude/rules/10-tdd.md @@ -203,8 +203,6 @@ DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild test \ ... -only-testing:vreaderTests/MyClassTests/test_specificThing ``` -The TDD Guardian config at `.claude/tdd-guardian/config.json` invokes the same `xcodebuild test` flow. - ## Android / Kotlin TDD (feature #107 — the workflow can drive Android) The RED→GREEN→REFACTOR discipline is platform-neutral; only the framework + run diff --git a/.claude/settings.json b/.claude/settings.json index 5158b65fa..2565c519e 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -58,5 +58,7 @@ } ] }, - "enabledPlugins": {} + "enabledPlugins": { + "tdd-guardian@xiaolai": false + } } diff --git a/.claude/tdd-guardian/config.json b/.claude/tdd-guardian/config.json deleted file mode 100644 index de7bec55d..000000000 --- a/.claude/tdd-guardian/config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "enabled": true, - "testCommand": "bash scripts/tdd-guardian-test.sh", - "_testCommand_note": "Feature #107 PR-D: routed through a platform-aware wrapper so a Kotlin change no longer false-greens against the frozen iOS xcodebuild command. scripts/tdd-guardian-test.sh classifies changed files via code_paths_platform and runs the iOS xcodebuild lane (ios/shared — unchanged), the Android spike/Gradle lane (android-spike/android-app once #106 wires the app), or fails loudly (exit 2) for an android-app change with no app yet.", - "coverageCommand": "", - "coverageSummaryPath": "", - "mutationCommand": "", - "thresholds": {}, - "stack": "swift-xcode", - "smartStaleness": true, - "gateFreshnessMinutes": 120, - "enforceOnTaskCompleted": false -} diff --git a/.claude/tdd-guardian/state.json b/.claude/tdd-guardian/state.json deleted file mode 100644 index b477b2e99..000000000 --- a/.claude/tdd-guardian/state.json +++ /dev/null @@ -1 +0,0 @@ -{"last_gate_passed_at": "2026-04-04T00:27:00Z", "tests_passed": 659, "coverage_passed": true, "last_head_sha": "a33b5e94786c42d5551c48c19a84b5c5de42ee31"} diff --git a/.gitignore b/.gitignore index fa9e71229..4967b6781 100644 --- a/.gitignore +++ b/.gitignore @@ -22,14 +22,12 @@ test-books/ # - settings.local.json: per-user permission allowlist # - *.local.{md,json,yaml,yml}: any file that opts in to local-only via the # `.local.` infix (Claude Code convention) -# - tdd-guardian/state.json: per-run TDD state, regenerated on each invocation .claude/settings.local.json .claude/*.local.json .claude/*.local.md .claude/*.local.yaml .claude/*.local.yml .claude/state.json -.claude/tdd-guardian/state.json # Cron / loop runtime locks (per-session) .claude/scheduled_tasks.lock # Feature #130 — tracker row-ID reservation state (local, never tracked; diff --git a/AGENTS.md b/AGENTS.md index 75f01cc27..d47e00d54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,10 +41,10 @@ cross-platform rewrite. **Source of truth for the Android strategy: - **Test / verify**: Android runs through **`scripts/run-android-tests.sh`** / **`scripts/run-android-verify.sh`** (rule 49/52/53 watchdogs — never a bare `./gradlew`; rule 52 "Cause D" covers the Gradle-daemon/emulator ghost - classes, swept by `scripts/sweep-ghosts.sh`). The TDD Guardian routes through - `scripts/tdd-guardian-test.sh` so a Kotlin change can't false-green on the iOS - command. `check_audit_debt.sh` now classifies `android/`/`*.kt`/`contracts/` - as code at Stop time too. + classes, swept by `scripts/sweep-ghosts.sh`). A Kotlin change is gated by the + platform-aware `code_paths_platform` classifier + `run-android-tests.sh`, so + it can't false-green on the iOS command. `check_audit_debt.sh` now classifies + `android/`/`*.kt`/`contracts/` as code at Stop time too. - **SKIP-Android-until-ready**: the app shell is **#106**. Until it lands the only Android target is the `spikes/` harness, so an `android-app` change can't reach Gate-5 — the crons skip auto-starting it and the verify skill marks it diff --git a/docs/decisions/0001-android-port-strategy.md b/docs/decisions/0001-android-port-strategy.md index 691b16a8f..89d38658b 100644 --- a/docs/decisions/0001-android-port-strategy.md +++ b/docs/decisions/0001-android-port-strategy.md @@ -197,7 +197,7 @@ library/CLI harnesses first — an empty `android/` directory is low-value). vectors into `contracts/`. - **Spike B — CJK WebView reader, INSTRUMENTATION-first.** The cron's ability to drive an Android emulator/device is **UNVERIFIED** — the verification stack - (rule 47, `cron-prompts/verify.md`, `tdd-guardian/config.json`) is iOS-specific. + (rule 47, `cron-prompts/verify.md`) is iOS-specific. Make Spike B benchmark/instrumentation-driven (scroll / memory / CFI / selection metrics on a real 1000+-spine CJK novel via Readium-Kotlin), **not** UI-automation-dependent. Standing up a *minimally-automatable* Android diff --git a/scripts/__tests__/tdd-guardian-test.test.sh b/scripts/__tests__/tdd-guardian-test.test.sh deleted file mode 100755 index 3feec63e9..000000000 --- a/scripts/__tests__/tdd-guardian-test.test.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -# Feature #107 PR-D — verifies the tdd-guardian test wrapper ROUTES by platform -# (the false-green fix: a Kotlin change must not pass the iOS test command). -# Uses TDD_FORCE_PLATFORM to exercise each branch without running the slow iOS -# xcodebuild lane. Run: bash scripts/__tests__/tdd-guardian-test.test.sh - -set -uo pipefail -HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -WRAP="$HERE/../tdd-guardian-test.sh" -fails=0 - -# android-app with no app shell → must refuse to green (exit 2, explicit msg). -out="$(TDD_FORCE_PLATFORM=android-app bash "$WRAP" 2>&1)"; rc=$? -if [ "$rc" -eq 2 ] && grep -q "refusing to green" <<<"$out"; then - echo "ok — android-app (no #106 app) refuses to green (exit 2)" -else - echo "FAIL — android-app: expected exit 2 + 'refusing to green', got exit $rc"; fails=$((fails+1)) -fi - -# android-spike → routes to run-android-tests.sh (NO_EMULATOR here, exit 2 from -# the runner — the point is it took the ANDROID lane, not the iOS one). -out="$(TDD_FORCE_PLATFORM=android-spike bash "$WRAP" 2>&1)" -if grep -q "android-spike change" <<<"$out" && grep -q "RUN-ANDROID-TESTS RESULT:" <<<"$out"; then - echo "ok — android-spike routes to the Android runner (not iOS xcodebuild)" -else - echo "FAIL — android-spike did not route to the Android runner:"; echo "$out" | tail -3; fails=$((fails+1)) -fi - -# Untracked Android file (Codex Gate-4 High): a brand-new, not-yet-`git add`ed -# `android/*.kt` must classify android-app (→ refuse-to-green exit 2), NOT fall to -# the iOS lane via an empty diff. Exercises the real git-detection path (no -# TDD_FORCE_PLATFORM). The probe also proves `ls-files --others` is consulted. -REPO="$(cd "$HERE/../.." && pwd)" -PROBE="$REPO/android/_tdd_guardian_untracked_probe.kt" -mkdir -p "$REPO/android" -printf 'class Probe\n' > "$PROBE" -out="$(bash "$WRAP" 2>&1)"; rc=$? -rm -f "$PROBE"; rmdir "$REPO/android" 2>/dev/null || true -if [ "$rc" -eq 2 ] && grep -q "refusing to green" <<<"$out"; then - echo "ok — untracked android/*.kt classifies android-app (no iOS false-green)" -else - echo "FAIL — untracked android/*.kt did not route Android: exit $rc"; echo "$out" | tail -2; fails=$((fails+1)) -fi - -# Config wiring: the guardian config invokes the wrapper, not the raw xcodebuild. -CFG="$HERE/../../.claude/tdd-guardian/config.json" -if grep -q "tdd-guardian-test.sh" "$CFG"; then - echo "ok — tdd-guardian config routes through the wrapper" -else - echo "FAIL — tdd-guardian config still hard-codes a raw test command"; fails=$((fails+1)) -fi - -echo -if [ "$fails" -eq 0 ]; then echo "ALL PASS"; exit 0; else echo "$fails FAILURE(S)"; exit 1; fi diff --git a/scripts/tdd-guardian-test.sh b/scripts/tdd-guardian-test.sh deleted file mode 100755 index 030e87cb5..000000000 --- a/scripts/tdd-guardian-test.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -# Purpose: platform-aware test command for the TDD Guardian (feature #107 PR-D). -# The guardian's config previously hard-coded the iOS `xcodebuild` command, so a -# Kotlin change "passed" tests that never ran (a false-green). This wrapper -# classifies the changed files by platform (`code_paths_platform`) and routes: -# ios | shared → the iOS xcodebuild lane (unchanged behavior) -# android-spike → scripts/run-android-tests.sh (the spike emulator harness) -# android-app → scripts/run-android-tests.sh ./gradlew IF #106's app -# exists; ELSE FAIL LOUDLY (exit 2) — an android-app change -# must NOT green until the app + its tests are wired. -# -# Safety: anything that doesn't classify cleanly as Android runs the iOS lane, so -# the iOS guardian flow is identical to before. -set -uo pipefail - -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -# shellcheck source=.claude/hooks/lib/code-paths.sh -source "$REPO/.claude/hooks/lib/code-paths.sh" 2>/dev/null || true - -IOS_CMD="DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild build-for-testing -project vreader.xcodeproj -scheme vreader -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -quiet && DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild test-without-building -project vreader.xcodeproj -scheme vreader -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:vreaderTests -disable-concurrent-testing" - -# Classify the working-tree + staged changes. If the classifier is unavailable -# or there are no changes, fall through to the iOS lane (preserve old behavior). -# TDD_FORCE_PLATFORM is a test seam (unset in production → git-based detection). -platform="${TDD_FORCE_PLATFORM:-ios}" -if [ -z "${TDD_FORCE_PLATFORM:-}" ] && declare -f code_paths_platform >/dev/null 2>&1; then - # Tracked-unstaged + staged + UNTRACKED (Codex Gate-4 High: a brand-new - # `android/Foo.kt` that isn't `git add`ed yet must still classify Android, or - # a Kotlin change false-greens on the iOS lane via an empty diff). - # --no-renames (Codex Gate-4 Medium): a rename surfaces as delete(OLD path) + - # add(NEW path), so a `android/Foo.kt -> docs/x.md` rename keeps the Android - # source path in the input and still classifies android-app (rather than only - # the post-image `docs/x.md` → shared → iOS false-green). - changed="$( { - git -C "$REPO" diff --no-renames --name-only HEAD - git -C "$REPO" diff --no-renames --cached --name-only - git -C "$REPO" ls-files --others --exclude-standard - } 2>/dev/null )" - if [ -n "$changed" ]; then - platform="$(printf '%s\n' "$changed" | code_paths_platform)" - fi -fi - -case "$platform" in - android-spike) - echo "[tdd-guardian] android-spike change → scripts/run-android-tests.sh" - exec bash "$REPO/scripts/run-android-tests.sh" - ;; - android-app) - # Run gradlew from the dir that actually contains it (Codex Gate-4 Medium: - # if #106 lands the wrapper under android/, invoke it there, not at root). - gradle_dir="" - [ -f "$REPO/android/gradlew" ] && gradle_dir="$REPO/android" - [ -z "$gradle_dir" ] && [ -f "$REPO/gradlew" ] && gradle_dir="$REPO" - if [ -n "$gradle_dir" ]; then - echo "[tdd-guardian] android-app change → ./gradlew unit tests (in $gradle_dir)" - ANDROID_CMD="cd \"$gradle_dir\" && ./gradlew :app:testDebugUnitTest" exec bash "$REPO/scripts/run-android-tests.sh" - fi - echo "TDD-GUARDIAN: android-app change but no Android app shell yet (feature #106) — Kotlin tests cannot be asserted; refusing to green." >&2 - exit 2 - ;; - *) - eval "$IOS_CMD" - ;; -esac From 29388ca0aadf052f9d93b84500a2040a5a3188cb Mon Sep 17 00:00:00 2001 From: lllyys Date: Wed, 29 Jul 2026 19:59:09 +0800 Subject: [PATCH 2/2] chore: bump version to 3.67.9 --- project.yml | 4 ++-- vreader.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/project.yml b/project.yml index bcb778a1a..523e0c352 100644 --- a/project.yml +++ b/project.yml @@ -212,8 +212,8 @@ targets: settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.vreader.app - CURRENT_PROJECT_VERSION: 1049 - MARKETING_VERSION: 3.67.7 + CURRENT_PROJECT_VERSION: 1051 + MARKETING_VERSION: 3.67.9 # Feature #42 Phase-2 WI-1b: compile + link the vendored libmobi C. # USE_LIBXML2 turns on libmobi's OPF/XML writer (KF8→EPUB needs it); # the iOS SDK ships libxml2 headers at $(SDKROOT)/usr/include/libxml2 diff --git a/vreader.xcodeproj/project.pbxproj b/vreader.xcodeproj/project.pbxproj index c1f153ade..799d1ad0b 100644 --- a/vreader.xcodeproj/project.pbxproj +++ b/vreader.xcodeproj/project.pbxproj @@ -8113,7 +8113,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1049; + CURRENT_PROJECT_VERSION = 1051; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited) USE_LIBXML2=1"; HEADER_SEARCH_PATHS = "$(inherited) $(SDKROOT)/usr/include/libxml2 $(SRCROOT)/vreader/Services/Libmobi/src"; INFOPLIST_FILE = vreader/SupportingFiles/Info.plist; @@ -8121,7 +8121,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.67.7; + MARKETING_VERSION = 3.67.9; OTHER_LDFLAGS = "$(inherited) -lxml2"; PRODUCT_BUNDLE_IDENTIFIER = com.vreader.app; SDKROOT = iphoneos; @@ -8267,7 +8267,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1049; + CURRENT_PROJECT_VERSION = 1051; GCC_PREPROCESSOR_DEFINITIONS = "$(inherited) USE_LIBXML2=1"; HEADER_SEARCH_PATHS = "$(inherited) $(SDKROOT)/usr/include/libxml2 $(SRCROOT)/vreader/Services/Libmobi/src"; INFOPLIST_FILE = vreader/SupportingFiles/Info.plist; @@ -8275,7 +8275,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.67.7; + MARKETING_VERSION = 3.67.9; OTHER_LDFLAGS = "$(inherited) -lxml2"; PRODUCT_BUNDLE_IDENTIFIER = com.vreader.app; SDKROOT = iphoneos;