# Changelog 2026-07-09 -- repo-agnostic sweep + repo-lint L9 self-reference guard

Follow-on to DOCFIX-139/140 (the mechanical ipv4->dc-dc rename sweep). This repo
has now been renamed/forked twice in its short life (per D-110); rather than
sweep by hand again next time, this delivery removes the remaining hardcoded
self-references and adds a lint rule so a NEW hardcode can't land silently.
Executed under blanket approval; no live cloud mutation -- repo content only.
Validation: repo-lint 0 fail / 1 documented WARN; `tests/repo-lint` harness
34/34 (was 29/29; 5 L9 cases added); full repo-wide grep for the current repo
name across scripts/runbooks/skill confirms only the two deliberately-exempted
environment.md fact lines remain.

## Items

### 1. DOCFIX-141 -- new repo-lint rule L9: self-reference (repo-agnostic guard)
FILE: `scripts/repo_lint.py`

WHAT: a new check that FAILS when a script/RUN-block/skill file hardcodes the
CURRENT repo's own directory name (`R.name`) as a literal path/URL token.
Scope: whole-file for `scripts/**/*.{sh,py}` and `.claude/hooks/*.{sh,py}`;
fenced-code-block-only for `runbooks/*.md` (prose naming a repo -- including a
sibling repo by name -- is harmless; only an executed literal is the hazard);
whole-file for `.claude/skills/**/*.md` (agent-operational instructions, not
incidental prose). Opt-out marker `repo-lint: allow-selfref` for a genuine,
reviewed exception (mirrors the existing L2 `allow-stale-tokens` convention).

WHY: this repo was seeded from `openstack-caracal-ipv4` this same week (D-110)
and DOCFIX-139 was a manual, one-time grep-and-fix sweep for the resulting
stale references. Nothing stopped a NEW hardcode from being written today, or
stopped this same class of bug from recurring on the next fork/rename. A lint
rule converts "remember to grep for this" into an enforced gate.

SIDE FIX: `all_text()`'s `.claude` exclusion was narrowed from "skip all of
`.claude/`" to "skip only `.claude/worktrees/`" (matching `.gitattributes`'
own scoping of the worktree gitignore rule). The blanket exclusion meant
`.claude/skills/` and `.claude/hooks/` -- both real, checked-in repo content,
not session-local worktree copies -- were invisible to L1 (and now L9) lint
entirely. Verified no new L1 findings from widening scope (checked non-ASCII/
CR byte counts in all 6 newly-in-scope files before narrowing: all clean).

HARNESS: `tests/repo-lint/run-tests.sh` T29-T33 (script hardcode FAILS, opt-out
marker suppresses it, runbook prose outside a code block does NOT fire, runbook
code-block hardcode FAILS, skill file hardcode FAILS). 34/34 green.

KNOWN LIMITATION (logged, not actioned -- pre-existing, not introduced here):
L9's runbook check inherits L3/L4/L6's `in_code` detection, which only
recognizes triple-backtick fences. `runbooks/d011-batch3-window-DRAFT.md`
(4-space-indented pseudo-code) and `runbooks/tenant-onboarding-v2-DRAFT.md`
(`--- BEGIN block ... --- END block ---` comment delimiters) use different
conventions and are NOT reached by L3/L4/L6/L9 at all -- confirmed by manually
grepping both for the DOCFIX-139 hardcodes, which lint did not flag. This is a
real, separate lint-coverage gap (affects the existing L3/L4/L6 checks too, not
just the new L9), logged here rather than fixed, since generalizing in-code
detection to arbitrary block conventions is its own scoped piece of work.
DOCFIX candidate: either teach the checks to recognize indented/BEGIN-END
blocks, or standardize all runbooks onto triple-backtick fences.

REVERT: `git checkout HEAD~ -- scripts/repo_lint.py tests/repo-lint/run-tests.sh`
(re-arms both the self-reference blind spot and the `.claude/skills` /
`.claude/hooks` lint-invisibility; not recommended).

### 2. DOCFIX-141 (cont.) -- self-reference removal across skill + 4 runbooks
FILES:
- `.claude/skills/openstack-cloud-ops/SKILL.md` -- frontmatter description,
  Step 0 identity/discovery text (now: confirm identity via `git remote -v`
  rather than guess a clone path; ask the operator for the clone URL rather
  than hardcode one), session-bootstrap line (now references `$REPO`).
- `.claude/skills/openstack-cloud-ops/references/environment.md` -- the repo
  URL is KEPT (this file's stated purpose is documenting anchor facts, exactly
  like its VIP/version/hostname entries -- re-verify before command-use, same
  discipline as everything else here) but tagged `repo-lint: allow-selfref`
  with an explicit rationale; added a `$REPO` convention pointer bullet.
- `runbooks/README.md` -- new Conventions bullet establishing `$REPO`: set once
  per session (`export REPO=$(git rev-parse --show-toplevel)` if already `cd`'d
  into the clone, else `export REPO=~/<wherever you cloned it>`); every RUN
  block that needs the repo path references `$REPO`, never a literal.
- `runbooks/ops-update-procedure.md`, `runbooks/d011-batch3-window-DRAFT.md`,
  `runbooks/phase-01-bundle-deploy.md`, `runbooks/tenant-onboarding-v2-DRAFT.md`
  -- the same 8 hardcoded paths fixed mechanically in DOCFIX-139 (rename) are
  now converted to `$REPO` (required, fails loud if unset -- no hardcoded
  fallback guess, consistent with hard rule 2 "never use an inferred value").
- `clientdocs/sweep-receipt.txt` (L7 re-record; reviewed for drift from the
  tenant-onboarding-v2-DRAFT.md edit -- no client-facing mirror, hash-only).

WHY: converts "the clone dir must be named exactly X" from an assumption baked
into 8 command sites into one explicit, operator-stated session input --
exactly the "measured or carried from confirmed as-built" standard hard rule 2
already requires for every other value in this repo.

REVERT: `git checkout HEAD~ -- <file>` per file above.
