$npx -y skills add andyzengmath/quantum-loop --skill ql-housekeepDetect repo-hygiene issues that accumulate during long-running autonomous development (merge-conflict markers, orphan worktrees, CPC-variant duplicates, stale branches, version-manifest drift). Detection-only by default — reports findings, never deletes or modifies without explic
| 1 | # ql-housekeep — repo hygiene detector |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | `ql-housekeep` surfaces the hygiene failures that accumulate when an autonomous development pipeline runs for weeks without human sweep. It is a **detector**, not an actuator. Auto-fix is explicitly out of scope. |
| 6 | |
| 7 | This skill exists because `idea-stage/AUDIT_QL.md` catalogued eight categories of hygiene failure on master. The first job of the next pipeline iteration is to not produce that state again. |
| 8 | |
| 9 | ## When to use |
| 10 | |
| 11 | - Before starting a new `/ql-brainstorm` cycle on a repo that has been running parallel execution for weeks. |
| 12 | - As a gate in `ql-execute` between waves if you suspect accumulated drift. |
| 13 | - Manually, when you want a structured view of "what is wrong with the repo right now that the runtime cannot see". |
| 14 | |
| 15 | ## What it detects |
| 16 | |
| 17 | ### 1. Merge-conflict markers in tracked files |
| 18 | |
| 19 | Pattern: `^<<<<<<<` or `^=======$` or `^>>>>>>>`. These are the signature of an abandoned git merge that was committed without resolution. |
| 20 | |
| 21 | Note: the `^=======$` pattern can theoretically false-positive on Markdown setext-heading underlines (e.g., a heading followed by exactly seven `=` chars at column 1). In practice this is rare, and the `<<<<<<<` + `>>>>>>>` siblings are required for a real conflict block, so false positives are self-limiting. |
| 22 | |
| 23 | Command: |
| 24 | ```bash |
| 25 | grep -rn --include='*.md' --include='*.sh' --include='*.ts' --include='*.js' \ |
| 26 | --include='*.py' --include='*.json' --include='*.yml' --include='*.yaml' \ |
| 27 | -E '^<<<<<<<|^=======$|^>>>>>>>' . |
| 28 | ``` |
| 29 | |
| 30 | ### 2. Orphan git worktrees |
| 31 | |
| 32 | Directories under `.claude/worktrees/agent-*` or `.ql-wt/<story-id>/` that git no longer tracks. |
| 33 | |
| 34 | Command: |
| 35 | ```bash |
| 36 | for d in .claude/worktrees/agent-* .ql-wt/*; do |
| 37 | [ -d "$d" ] || continue |
| 38 | git worktree list | grep -q "$d" || echo "$d" |
| 39 | done |
| 40 | ``` |
| 41 | |
| 42 | ### 3. CPC-variant duplicate files |
| 43 | |
| 44 | Pattern: files matching `*-CPC-andyz-ZH84K.*`. These are OneDrive-renamed copies that indicate a parallel-hardening fork. If detected, the project likely has two pipelines coexisting — see `idea-stage/AUDIT_QL.md` for promotion protocol. |
| 45 | |
| 46 | Command: |
| 47 | ```bash |
| 48 | find . -path ./node_modules -prune -o -name '*-CPC-andyz-*' -print |
| 49 | ``` |
| 50 | |
| 51 | ### 4. Superseded-but-not-deleted files |
| 52 | |
| 53 | Files whose header docstring says "supersedes X" where X still exists. Currently catches: |
| 54 | - `lib/crash-recovery.sh` (superseded by `lib/resilience.sh:3`) |
| 55 | |
| 56 | Command (case-insensitive `Supersedes` / `supersedes`): |
| 57 | ```bash |
| 58 | for f in lib/*.sh; do |
| 59 | sup=$(grep -oiE 'supersedes lib/[a-z_-]+\.sh' "$f" 2>/dev/null | head -1 | awk '{print $NF}') |
| 60 | [ -n "$sup" ] && [ -f "$sup" ] && echo "DEAD: $sup (superseded by $f)" |
| 61 | done |
| 62 | ``` |
| 63 | |
| 64 | ### 5. Stale branches |
| 65 | |
| 66 | - `worktree-agent-*` branches that are no longer referenced by any live worktree. |
| 67 | - `ql/*` and `fix/*` branches whose tip commits are strict ancestors of master OR have been inactive > 90 days. |
| 68 | |
| 69 | Command: |
| 70 | ```bash |
| 71 | git branch -a --format='%(refname:short) %(committerdate:short) %(upstream:track)' \ |
| 72 | | awk '$1 ~ /^(ql|fix|worktree-agent)/' \ |
| 73 | | sort -k2 |
| 74 | ``` |
| 75 | |
| 76 | ### 6. Plugin version / CHANGELOG drift |
| 77 | |
| 78 | `.claude-plugin/plugin.json.version` must match `.claude-plugin/marketplace.json.version` and must have a corresponding entry in `CHANGELOG.md`. |
| 79 | |
| 80 | Command: |
| 81 | ```bash |
| 82 | plugin_v=$(jq -r .version .claude-plugin/plugin.json 2>/dev/null) |
| 83 | market_v=$(jq -r '.plugins[0].version // .version' .claude-plugin/marketplace.json 2>/dev/null) |
| 84 | [ "$plugin_v" = "$market_v" ] || echo "version mismatch: plugin=$plugin_v market=$market_v" |
| 85 | grep -q "^## \[$plugin_v\]" CHANGELOG.md 2>/dev/null || echo "CHANGELOG missing entry for v$plugin_v" |
| 86 | ``` |
| 87 | |
| 88 | ### 7. Stale `quantum.json` |
| 89 | |
| 90 | `quantum.json.updatedAt` older than 30 days with the project in active development suggests the team isn't dogfooding. |
| 91 | |
| 92 | Command (cross-platform: GNU `date`, macOS `date`, Git Bash): |
| 93 | ```bash |
| 94 | last=$(jq -r '.updatedAt // empty' quantum.json 2>/dev/null) |
| 95 | if [ -n "$last" ]; then |
| 96 | # Portable ISO-8601 → epoch via python3 (GNU date -d is not on macOS/BSD) |
| 97 | age_days=$(python3 -c " |
| 98 | import datetime,sys |
| 99 | t = datetime.datetime.fromisoformat('$last'.replace('Z', '+00:00')) |
| 100 | now = datetime.datetime.now(datetime.timezone.utc) |
| 101 | print(int((now - t).total_seconds() // 86400)) |
| 102 | " 2>/dev/null) |
| 103 | [ -n "$age_days" ] && [ "$age_days" -gt 30 ] && echo "quantum.json not updated in $age_days days" |
| 104 | fi |
| 105 | ``` |
| 106 | Fallback when `python3` is unavailable: compare `$last` lexicographically against a pre-computed `$threshold = "$(TZ=UTC date -u +%Y-%m-%d)"` minus 30 days (date-string compare works because ISO-8601 is lexicographic-sortable); implementer should prefer python3. |
| 107 | |
| 108 | ### 8. Duplicate test files (same logical test in two files) |
| 109 | |
| 110 | Pattern: two test fil |