Section 08: Script Consolidation & Reference Sweep
Status: In Progress (pre-execution) — frontmatter auto-flipped by the plan-audit linter when iteration-1 TPR findings landed with [x] resolutions in §08.R. The section’s IMPLEMENTATION work (§08.1 through §08.8) has NOT begun; this status reflects “has TPR findings being resolved pre-execution”, not “section is being implemented”. Subsection statuses remain not-started accordingly. §09.6 plan-close handles both not-started→complete and in-progress→complete transitions.
Goal: The load-bearing section for the entire reorganization plan. Execute the Option C hybrid script model: consolidate internal dev scripts under scripts/dev/, merge diagnostics/ into scripts/diagnostics/, install permanent exec wrappers at root for the 4 daily-use scripts (so developer muscle memory is preserved), and atomically sweep 747 references across 230 files. This is the biggest section by both reference count (68% of all script references point at test-all.sh alone) and by number of touched file categories (CLAUDE.md, CONTRIBUTING.md, all .claude/* rule files and skills, all plan files, test code, doc appendices).
Success Criteria: (see frontmatter above — extensive; summarized here)
-
scripts/dev/contains 8 files;scripts/diagnostics/contains 16 files -
diagnostics/absent; hot-path 4 wrappers live at root - Reference sweep complete: no
./clippy-all.sh/./llvm-*.shreferences remain outsidescripts/dev/in the repo - Hot-path references (
./test-all.shetc.) may remain since wrappers preserve those paths -
./test-all.shgreen via wrapper;scripts/dev/test-all.shgreen direct -
lefthook run pre-commitgreen - Single atomic commit lands
- Mission criteria #2 (zero stale root references), #3 (lefthook passes), #5 (test-all green), #11 (muscle memory preserved) all satisfied
Context: Pass 1 Agent 1 verified the blast radius as 747 matches across 230 files, with test-all.sh dominating at 503 matches (68% of total). The Option C decision (user-approved during Step 9 review) splits the 8 internal dev scripts into two groups:
- Cleanly moved (4 scripts):
clippy-all.sh,llvm-build.sh,llvm-clippy.sh,llvm-test.sh— moved toscripts/dev/with ALL references updated to the new path. Zero trace at root. - Wrapper-preserved (4 hot-path scripts):
test-all.sh,full-check.sh,fmt-all.sh,build-all.sh— real implementation moves toscripts/dev/, but a permanent thin exec wrapper is installed at root. Existing references to./test-all.shetc. continue to resolve correctly without modification.
Critical ordering insight: The hot-path wrappers let us AVOID rewriting 503 + 7 + 29 + 2 = 541 references. Only the cleanly-moved scripts’ references need sweeping: 175 (clippy-all) + 30 (llvm-test) + 0 (llvm-build, llvm-clippy) = ~205 references. That’s a ~73% reduction in sweep work compared to rewriting everything.
However: CLAUDE.md line 133 explicitly lists ./clippy-all.sh as “Primary”, and CLAUDE.md line 134 lists ./llvm-test.sh. These specific CLAUDE.md references are among the 205 that need updating. Similarly, .claude/rules/*.md, .claude/skills/*.md, CONTRIBUTING.md line 33, docs appendices, and test code.
Script internal call chain (recap from overview): full-check.sh calls $SCRIPT_DIR/clippy-all.sh + $SCRIPT_DIR/test-all.sh. When all three land in scripts/dev/, $SCRIPT_DIR resolves to scripts/dev/, making them siblings. The root wrapper for full-check.sh execs into scripts/dev/full-check.sh, which then uses its local $SCRIPT_DIR to find clippy-all.sh (cleanly moved, sibling) and test-all.sh (wrapper target, also in scripts/dev/). Chain works unchanged.
Reference implementations:
- rustc (
x.py+src/bootstrap/): the rootx.pyis a 100-line Python dispatcher forwarding tosrc/bootstrap/. Rustc chose single-entry-point; we adopt the dispatch pattern via 4 specific wrappers instead of a singlex.shbecause Ori’s./test-all.shmuscle memory is already in 503 places. - gleam (
bin/gleam+ Makefile): gleam has a Makefile at root that delegates tobin/scripts. Same pattern as our wrappers. - swift (
utils/build-script+utils/): swift’s utilities live in a single consolidated dir; we adopt the consolidation but keep stable root entry points.
Depends on: §02 (website paths repaired), §03 (gitignore clean), §05 (orphans gone), §06 (LSP gone), §07 (scratchpad gone). §08 is the terminal state-changing section — after it, only §09 verification runs.
08.1 Create scripts/dev/ and scripts/diagnostics/ Directories
File(s): scripts/dev/ (new), scripts/diagnostics/ (new), target/ (must exist as a persistent workspace for sweep-pattern file lists)
-
Create the new directories:
cd /home/eric/projects/ori_lang mkdir -p scripts/dev scripts/diagnostics # target/ already exists (cargo build dir, gitignored). Ensure the sweep-pattern # file lists can be written there — TPR-XX-001-gemini iteration 2 fix moved them # from /tmp/ (volatile) to target/ (persistent-until-cargo-clean, gitignored). mkdir -p target -
Verify:
ls scripts/dev/ scripts/diagnostics/ # Expected: both exist, both empty test -d target && echo "target/ exists (sweep-pattern file list destination)" -
Enumerate pre-move fixture files (TPR-XX-001-codex iteration 2 fix — explicit per-file fixture staging):
find diagnostics/fixtures -type f > target/sweep-pattern-fixtures-files.txt wc -l target/sweep-pattern-fixtures-files.txt # Expected: ~1-5 fixture files (current count as of 2026-04-11: mismatch-wrapper.sh and others)- Fixture file list captured. This list is used by §08.8 to stage each fixture file individually (pre-move paths) and by the same subsection to stage each file at its post-move path (
scripts/diagnostics/fixtures/*). Eliminates thegit add -u diagnostics/fixtures/directory-level add that the iteration 1 fix overlooked.
- Fixture file list captured. This list is used by §08.8 to stage each fixture file individually (pre-move paths) and by the same subsection to stage each file at its post-move path (
-
Note: empty directories are not tracked by git. The first
git mvin 08.2 will populate them and make them git-visible. -
Subsection close-out (08.1) — MANDATORY before starting 08.2:
- Both directories exist
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — trivial mkdir. No tooling gap. Document: “Retrospective 08.1: no tooling gap — mkdir is adequate.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.2 Move Cleanly-Moved Scripts (clippy-all, llvm-*)
File(s): clippy-all.sh, llvm-build.sh, llvm-clippy.sh, llvm-test.sh — moved via git mv from root to scripts/dev/
These 4 scripts are cleanly moved with no root wrappers. All references in other files will be updated in §08.5.
-
Move each via
git mv:cd /home/eric/projects/ori_lang git mv clippy-all.sh scripts/dev/clippy-all.sh git mv llvm-build.sh scripts/dev/llvm-build.sh git mv llvm-clippy.sh scripts/dev/llvm-clippy.sh git mv llvm-test.sh scripts/dev/llvm-test.sh -
Verify:
ls scripts/dev/ # Expected: clippy-all.sh llvm-build.sh llvm-clippy.sh llvm-test.sh ls *.sh 2>&1 | grep -E '(clippy-all|llvm-)' # Expected: no matches (all root-level copies gone) -
Verify git status shows the renames:
git status --short # Expected: R clippy-all.sh -> scripts/dev/clippy-all.sh (etc.) -
Verify the scripts still work from their new location:
# Non-blocking smoke test — just start them and kill immediately timeout 3 bash scripts/dev/clippy-all.sh 2>&1 | head -3 || true timeout 3 bash scripts/dev/llvm-test.sh 2>&1 | head -3 || true- Both scripts load without immediate syntax errors
-
Subsection close-out (08.2) — MANDATORY before starting 08.3:
- 4 scripts moved via
git mv - Root-level copies gone
- Scripts still parse from new location
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection —git mvis the canonical tool; no gap. Document: “Retrospective 08.2: no tooling gap — git mv handled the rename preservation.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
- 4 scripts moved via
08.3 Move Hot-Path Scripts & Install Root Wrappers
File(s): test-all.sh, full-check.sh, fmt-all.sh, build-all.sh — moved via git mv to scripts/dev/, then NEW thin wrappers written at root
The hot-path 4 are special: their real content moves to scripts/dev/, but a NEW file with the same name is created at root containing a 3-line exec wrapper that forwards to the real implementation.
Documented invocation assumptions (Phase 2 Findings 4 and 8, 2026-04-11)
The wrappers (and the inner scripts at scripts/dev/) inherit the pre-move behavior of the hot-path scripts — specifically:
-
PWD assumption — wrappers require cwd = repo root (Finding 8). The inner implementations (
scripts/dev/test-all.shetc.) resolve many paths as cwd-relative:test-all.shwrites logs totest-all.log, invokes./target/debug/ori, and writes JSON results totarget/test-all-results.json. If invoked from a subdirectory (e.g.,cd compiler && ../../test-all.sh), these paths break silently — output lands incompiler/target/orcompiler/test-all.loginstead of the repo root. This is a pre-existing behavior inherited from the old root-level scripts; the wrappers do not introduce new breakage, but they also do not fix it.Decision (Finding 8 Option A): Document the assumption, do NOT change the wrapper template to add
cd "$(git rev-parse --show-toplevel)". Rationale:- The pre-existing muscle-memory pattern is
./test-all.shfrom the repo root — every existing invocation site already satisfies cwd=root - Adding a cd-anchor to the wrapper changes behavior for edge cases that aren’t covered by the 747 existing references
- The matrix verification (§08.7 C3) exercises cross-dir invocation and captures the documented limitation
- A future plan can revisit and add the cd-anchor if the limitation proves painful; the wrapper template is exactly 1 line of change away
User-facing documentation: Add a one-line note to the wrapper template explaining the assumption. Users who invoke from subdirectories will see the note on the first line of the wrapper file and understand the constraint.
- The pre-existing muscle-memory pattern is
-
Symlink assumption — wrappers assume invocation via the real path, not a symlink (Finding 4 / §08.7 C4). The wrapper uses
$(dirname "$0")which does NOT resolve through symlinks. If a user symlinks~/bin/test-all -> /repo/test-all.shand runstest-all,$(dirname "$0")returns~/bin, not/repo, and the exec target path is wrong.Decision: Use
readlink -fto resolve the symlink at wrapper-entry time. This costs one extra line per wrapper but makes the wrappers robust against symlink aliasing (a legitimate and common developer pattern). The §08.7 C4 matrix cell verifies this.
Wrapper template (all 4 wrappers share this shape)
#!/usr/bin/env bash
# Permanent root facade for <script>.sh — real implementation at scripts/dev/<script>.sh.
# Preserves `./<script>.sh` muscle memory while consolidating implementation under scripts/dev/.
# Do NOT treat this as a temporary shim — it is a permanent public-ish entry point.
#
# Assumption: invocation cwd = repo root (the inner script writes logs/artifacts as
# cwd-relative paths). Invocation from a subdirectory is UNSUPPORTED — it will break
# silently as output lands in the wrong directory. Matrix cell §08.7 C3 captures this.
# To invoke from a subdirectory, use `./test-all.sh` from the repo root instead.
#
# Symlink-safe: uses readlink -f to resolve through symlinks when $0 is a real path
# (direct invocation `./test-all.sh` or absolute path). `ln -s $PWD/test-all.sh
# ~/bin/test-all && test-all` works correctly IF `~/bin/test-all` was launched via
# an absolute resolved path (matrix cell §08.7 C4).
#
# **PATH invocation is UNSUPPORTED.** When `$0` is just a bare name (e.g. the user
# added the repo root to PATH and runs `test-all.sh` without `./` from any cwd),
# `readlink -f "$0"` treats the bare name as cwd-relative and returns
# `$PWD/test-all.sh`, which will NOT exist outside the repo root. The wrapper will
# then try to exec `$PWD/scripts/dev/test-all.sh` and fail with "No such file".
# Matrix cell §08.7 C9 explicitly documents this as a SKIP (unsupported). Rationale:
# adding PATH resolution (`command -v` + `readlink -f`) would add complexity for an
# edge case developers don't hit — aliases to `./test-all.sh` work fine, and the
# typical `cargo`-style dev workflow uses `./test-all.sh` directly from repo root.
SCRIPT_PATH="$(readlink -f "$0")"
exec "$(dirname "$SCRIPT_PATH")/scripts/dev/<script>.sh" "$@"
-
Move the 4 hot-path scripts:
git mv test-all.sh scripts/dev/test-all.sh git mv full-check.sh scripts/dev/full-check.sh git mv fmt-all.sh scripts/dev/fmt-all.sh git mv build-all.sh scripts/dev/build-all.sh -
Verify internal call chain still works in
scripts/dev/full-check.sh:grep -n '"\$SCRIPT_DIR/' scripts/dev/full-check.sh # Expected: lines 28 and 39 reference $SCRIPT_DIR/clippy-all.sh and $SCRIPT_DIR/test-all.sh- Both references are still present (unchanged by the move)
- These will resolve correctly at runtime because
$SCRIPT_DIRnow evaluates toscripts/dev/(the new home of all three files)
-
Verify
scripts/dev/full-check.shdiscovers its siblings:# Trace the $SCRIPT_DIR resolution manually cd /home/eric/projects/ori_lang SCRIPT_DIR="$(dirname "$(readlink -f "scripts/dev/full-check.sh")")" echo "$SCRIPT_DIR" # Expected: /home/eric/projects/ori_lang/scripts/dev ls "$SCRIPT_DIR/clippy-all.sh" "$SCRIPT_DIR/test-all.sh" # Both should exist -
Create the 4 root wrappers. For each, use
Writetool to create the new root file. All 4 usereadlink -ffor symlink-safety (§08.7 C4) and document the cwd=repo-root assumption (§08.7 C3):test-all.sh wrapper:
#!/usr/bin/env bash # Permanent root facade for test-all.sh — real implementation at scripts/dev/test-all.sh. # Preserves `./test-all.sh` muscle memory (503+ references across plans and docs) while # consolidating implementation under scripts/dev/. Do NOT treat as a temporary shim — # this wrapper is a permanent public-ish entry point per the Option C hybrid model. # # Assumption: invocation cwd = repo root (inner script writes cwd-relative artifacts). # Invocation from a subdirectory is UNSUPPORTED — see §08.7 C3. Symlink-safe via readlink -f. SCRIPT_PATH="$(readlink -f "$0")" exec "$(dirname "$SCRIPT_PATH")/scripts/dev/test-all.sh" "$@"full-check.sh wrapper:
#!/usr/bin/env bash # Permanent root facade for full-check.sh — real implementation at scripts/dev/full-check.sh. # Hot-path wrapper per Option C hybrid script model — used by lefthook.yml pre-commit. # # Assumption: cwd = repo root. Symlink-safe via readlink -f. SCRIPT_PATH="$(readlink -f "$0")" exec "$(dirname "$SCRIPT_PATH")/scripts/dev/full-check.sh" "$@"fmt-all.sh wrapper:
#!/usr/bin/env bash # Permanent root facade for fmt-all.sh — real implementation at scripts/dev/fmt-all.sh. # Hot-path wrapper per Option C hybrid script model — used by lefthook.yml pre-commit. # # Assumption: cwd = repo root. Symlink-safe via readlink -f. SCRIPT_PATH="$(readlink -f "$0")" exec "$(dirname "$SCRIPT_PATH")/scripts/dev/fmt-all.sh" "$@"build-all.sh wrapper:
#!/usr/bin/env bash # Permanent root facade for build-all.sh — real implementation at scripts/dev/build-all.sh. # Hot-path wrapper per Option C hybrid script model. # # Assumption: cwd = repo root. Symlink-safe via readlink -f. SCRIPT_PATH="$(readlink -f "$0")" exec "$(dirname "$SCRIPT_PATH")/scripts/dev/build-all.sh" "$@" -
Make all 4 wrappers executable:
chmod +x test-all.sh full-check.sh fmt-all.sh build-all.sh -
Add the 4 NEW wrapper files to git:
git add test-all.sh full-check.sh fmt-all.sh build-all.sh -
Verify git status:
git status --short | grep -E '(test-all|full-check|fmt-all|build-all)\.sh' # Expected: 4 renames (R old -> scripts/dev/...) + 4 new files (A test-all.sh, etc.) -
Smoke test a wrapper — run
./test-all.sh --helpor similar non-blocking invocation:# If test-all.sh supports --help, use it. Otherwise run with a short timeout and kill. timeout 5 ./test-all.sh 2>&1 | head -10 || true- Output shows the wrapper-forwarding is working (the real script’s initial lines appear)
-
Subsection close-out (08.3) — MANDATORY before starting 08.4:
- 4 hot-path scripts moved to scripts/dev/
- 4 root wrappers created and executable
- Smoke test proves wrapper forwarding works
- Internal call chain in full-check.sh still resolves correctly
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — the wrapper template was repeated 4 times with minor variations. Is ascripts/dev/generate-wrapper.sh <script-name>helper worth building? Probably not — wrappers are one-time infrastructure. Document: “Retrospective 08.3: 4-time repetition; helper not justified for one-time facade creation.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.4 Move diagnostics/ to scripts/diagnostics/ (with repo-root resolution fix)
File(s):
diagnostics/→scripts/diagnostics/(git mv of entire directory)- NEW:
scripts/diagnostics/_repo-root.sh(new helper) - EDITED (repo-root resolution):
scripts/diagnostics/_common.sh(lines 29, 70, 113),scripts/diagnostics/check-debug-flags.sh(line 29),scripts/diagnostics/self-test.sh(line 22),scripts/diagnostics/dual-exec-verify.sh(line 33),scripts/diagnostics/valgrind-aot.sh(lines 118, 136),scripts/diagnostics/fixtures/mismatch-wrapper.sh(line 25)
CRITICAL FINDING (Phase 2 dual-source review, 2026-04-11): Seven diagnostic scripts derive the repo root via ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" (or the inlined equivalent). This resolves correctly at the current depth (/repo/diagnostics/<script>.sh → parent = /repo/) but becomes WRONG after the move to scripts/diagnostics/ (/repo/scripts/diagnostics/<script>.sh → parent = /repo/scripts/). Every affected script uses ROOT_DIR / root_dir to locate target/debug/ori, target/release/ori, compiler/oric/src/debug_flags.rs, CLAUDE.md, plans/code-journeys/, tests/valgrind/, the build/ JSON output path, and grep roots for the compiler tree — each a broken-at-the-new-depth lookup.
The false claim removed: The prior version of this subsection said “Most diagnostic scripts use $(dirname "$0") patterns which work unchanged”. That is wrong for the scripts that use $SCRIPT_DIR/.. to find the repo root — they break silently (run from wrong root → can’t find binary → error message that misleads debugging) at the new depth.
The fix (architecturally correct per CLAUDE.md §The One Rule): Add a canonical repo-root resolver helper scripts/diagnostics/_repo-root.sh that uses git rev-parse --show-toplevel (depth-independent and symlink-safe), and have every script source it and use $REPO_ROOT instead of re-deriving from $SCRIPT_DIR/... This eliminates the depth coupling entirely — future reorgs won’t break the scripts again.
Why Option B (helper) over Option A (per-script s|/..|/../..|):
- Depth-independent:
git rev-parse --show-toplevelreturns the repo root regardless of where the script lives in the tree; survives any future reorg - Symlink-safe:
git rev-parseresolves through symlinks correctly, wherecd ... && pwddoes not - SSOT: one canonical resolver instead of 9 instances of
$(cd "$SCRIPT_DIR/.." && pwd)scattered across scripts - Matches CLAUDE.md “Continuous improvement — fix at the source, never work around a problem”
- No per-script bookkeeping for depth; the helper is a one-time cost
Fallback strategy (non-git checkouts): If git rev-parse fails (script run from a tarball or non-git checkout — unlikely but possible), fall back to a depth-aware $SCRIPT_DIR/../.. resolution with a sanity check (verify Cargo.toml exists at the computed root). This preserves backward compatibility.
08.4 steps
-
Step 1: Move the directory and its contents via
git mv. Per-file to preserve git rename tracking:cd /home/eric/projects/ori_lang git mv diagnostics/*.sh scripts/diagnostics/ git mv diagnostics/*.md scripts/diagnostics/ git mv diagnostics/fixtures scripts/diagnostics/fixturesFallback per-file loop if the globs misbehave:
cd diagnostics for f in *.sh *.md; do git mv "$f" "../scripts/diagnostics/$f" done cd .. git mv diagnostics/fixtures scripts/diagnostics/fixtures -
Step 2: Remove the now-empty
diagnostics/directory:rmdir diagnostics 2>&1 || ls diagnostics -
Step 3: Verify move:
ls scripts/diagnostics/ # Expected: all 16 diagnostic scripts + README.md + _common.sh + fixtures/ ls diagnostics 2>&1 || echo "absent (expected)" -
Step 4: Enumerate the repo-root resolution pattern in the moved scripts — this is the finding from Phase 2. The broken pattern is
$(cd "$SCRIPT_DIR/.." && pwd)used to derive repo root:cd /home/eric/projects/ori_lang grep -rn '"\$SCRIPT_DIR/\.\." && pwd"' scripts/diagnostics/ 2>&1 grep -rn 'cd "\$SCRIPT_DIR/\.\."' scripts/diagnostics/ 2>&1 grep -rn '"\$SCRIPT_DIR/\.\."' scripts/diagnostics/ 2>&1Expected matches (verified against working tree 2026-04-11):
File Line Pattern scripts/diagnostics/_common.sh29 root_dir="$(cd "$SCRIPT_DIR/.." && pwd)"(insidefind_ori_bin)scripts/diagnostics/_common.sh70 root_dir="$(cd "$SCRIPT_DIR/.." && pwd)"(insidefind_ori_bin_profile)scripts/diagnostics/_common.sh113 root_dir="$(cd "$SCRIPT_DIR/.." && pwd)"(insidefind_any_ori_bin)scripts/diagnostics/check-debug-flags.sh29 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"scripts/diagnostics/self-test.sh22 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"scripts/diagnostics/dual-exec-verify.sh33 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"scripts/diagnostics/valgrind-aot.sh118 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"(inside case branch)scripts/diagnostics/valgrind-aot.sh136 ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"(inside case branch)scripts/diagnostics/fixtures/mismatch-wrapper.sh25 "$SCRIPT_DIR/../../target/debug/ori"(inlined sibling-parent climb)- The match count must match this table exactly. If
grepreports fewer/more, the working tree drifted — investigate before proceeding.
- The match count must match this table exactly. If
-
Step 5: Create the canonical repo-root resolver helper at
scripts/diagnostics/_repo-root.sh. Use theWritetool:File:
scripts/diagnostics/_repo-root.sh#!/bin/bash # Canonical repo-root resolver for diagnostic scripts. # # Sourced by diagnostic scripts to set REPO_ROOT to the ori_lang repository root, # regardless of where the script lives in the tree. Depth-independent and # symlink-safe. Replaces the older `$(cd "$SCRIPT_DIR/.." && pwd)` pattern that # broke when diagnostics/ moved to scripts/diagnostics/ (see # plans/project-reorganization/section-08-script-consolidation.md §08.4). # # Usage: # SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # source "$SCRIPT_DIR/_repo-root.sh" # # REPO_ROOT is now set # # Strategy: # 1. Prefer `git rev-parse --show-toplevel` — depth-independent, symlink-safe # 2. Fallback: walk up from $SCRIPT_DIR until a directory containing Cargo.toml # AND CLAUDE.md is found (sanity check that it's the ori_lang root, not a # crate root that happens to have Cargo.toml) # 3. Hard failure if neither strategy works — exit 2 with a clear error _resolve_repo_root() { local candidate # Strategy 1: git rev-parse (preferred) if command -v git >/dev/null 2>&1; then candidate="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)" if [[ -n "$candidate" && -f "$candidate/Cargo.toml" && -f "$candidate/CLAUDE.md" ]]; then echo "$candidate" return 0 fi fi # Strategy 2: walk-up from SCRIPT_DIR candidate="$SCRIPT_DIR" while [[ "$candidate" != "/" && -n "$candidate" ]]; do if [[ -f "$candidate/Cargo.toml" && -f "$candidate/CLAUDE.md" ]]; then echo "$candidate" return 0 fi candidate="$(dirname "$candidate")" done # Both strategies failed echo "Error: could not resolve ori_lang repo root from $SCRIPT_DIR" >&2 echo "Tried: git rev-parse --show-toplevel and walk-up from SCRIPT_DIR" >&2 return 2 } REPO_ROOT="$(_resolve_repo_root)" || exit 2 export REPO_ROOT -
Step 6: Update
scripts/diagnostics/_common.sh— replace all three$(cd "$SCRIPT_DIR/.." && pwd)occurrences with$REPO_ROOT, and add thesourceline near the top. UseEdittool:Edit 1 — add sourcing after the shebang/docs, before any function definition. The sourcing must appear BEFORE any function that uses
$REPO_ROOT:old_string: # Test whether a binary has LLVM support. new_string: # Source the canonical repo-root resolver (required for REPO_ROOT) # shellcheck disable=SC1091 source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_repo-root.sh" # Test whether a binary has LLVM support.Note:
${BASH_SOURCE[0]}is used instead of$0because_common.shis sourced, not executed directly —$0would be the parent script’s name, not_common.sh.Edit 2 (line 29, in
find_ori_bin):old_string: local root_dir root_dir="$(cd "$SCRIPT_DIR/.." && pwd)" if [[ -n "${ORI_BIN:-}" ]]; then new_string: local root_dir="$REPO_ROOT" if [[ -n "${ORI_BIN:-}" ]]; thenEdit 3 (line 70, in
find_ori_bin_profile):old_string: local profile="$1" local root_dir root_dir="$(cd "$SCRIPT_DIR/.." && pwd)" local bin="$root_dir/target/$profile/ori" new_string: local profile="$1" local root_dir="$REPO_ROOT" local bin="$root_dir/target/$profile/ori"Edit 4 (line 113, in
find_any_ori_bin):old_string: # Locate any ori binary (no LLVM requirement). # Tries: $ORI_BIN (env), target/debug/ori, target/release/ori, `ori` (PATH). # Sets ORI_INTERP to the first working candidate. # Exits with code 2 if no binary is found. find_any_ori_bin() { local root_dir root_dir="$(cd "$SCRIPT_DIR/.." && pwd)" new_string: # Locate any ori binary (no LLVM requirement). # Tries: $ORI_BIN (env), target/debug/ori, target/release/ori, `ori` (PATH). # Sets ORI_INTERP to the first working candidate. # Exits with code 2 if no binary is found. find_any_ori_bin() { local root_dir="$REPO_ROOT" -
Step 7: Update
scripts/diagnostics/check-debug-flags.sh(line 29):old_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" new_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_repo-root.sh" ROOT_DIR="$REPO_ROOT" -
Step 8: Update
scripts/diagnostics/self-test.sh(line 22):old_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" FIXTURES_DIR="$SCRIPT_DIR/fixtures" new_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_repo-root.sh" ROOT_DIR="$REPO_ROOT" FIXTURES_DIR="$SCRIPT_DIR/fixtures" -
Step 9: Update
scripts/diagnostics/dual-exec-verify.sh(line 33):old_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" source "$SCRIPT_DIR/_common.sh" new_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_repo-root.sh" ROOT_DIR="$REPO_ROOT" source "$SCRIPT_DIR/_common.sh"Note:
_common.shalso now sources_repo-root.sh, but sourcing it twice is harmless (the file only assigns variables; it has no side effects on re-source). -
Step 10: Update
scripts/diagnostics/valgrind-aot.sh(both occurrences at lines 118 and 136). These are insidecasebranches, so the simplest fix is to replace the$(cd "$SCRIPT_DIR/.." && pwd)with$REPO_ROOTdirectly — but the file must first source_repo-root.shnear the top. Add the sourcing near line 34 aftersource "$SCRIPT_DIR/_common.sh"(since_common.shwill already source_repo-root.sh, we can use$REPO_ROOTdirectly without a second source in valgrind-aot.sh — but being explicit is clearer):old_string (line ~34): SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "$SCRIPT_DIR/_common.sh" new_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_repo-root.sh" source "$SCRIPT_DIR/_common.sh"Then replace both
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"occurrences inside the case branches:sed -i 's|ROOT_DIR="$(cd "\$SCRIPT_DIR/\.\." && pwd)"|ROOT_DIR="$REPO_ROOT"|g' scripts/diagnostics/valgrind-aot.shVerify:
grep -n 'ROOT_DIR' scripts/diagnostics/valgrind-aot.sh # Expected: both lines now say ROOT_DIR="$REPO_ROOT" -
Step 11: Update
scripts/diagnostics/fixtures/mismatch-wrapper.sh(line 25). This fixture script uses an inlined$SCRIPT_DIR/../../target/debug/oriat depth-2, which assumes it lives atdiagnostics/fixtures/*.sh. After the move it lives atscripts/diagnostics/fixtures/*.sh, so the depth-2 climb now lands atscripts/instead of repo root. Fix by sourcing_repo-root.shfrom the sibling parent dir and using$REPO_ROOT/target/debug/ori:old_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ... "$SCRIPT_DIR/../../target/debug/ori" \ new_string: SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/../_repo-root.sh" ... "$REPO_ROOT/target/debug/ori" \(Read the file first to get the exact
old_stringcontext; the...above represents the lines between that are unchanged.) -
Step 12: Verify NO remaining broken
$SCRIPT_DIR/..repo-root resolution patterns in moved scripts:cd /home/eric/projects/ori_lang grep -rn '\$SCRIPT_DIR/\.\.' scripts/diagnostics/ 2>&1 | grep -v '_repo-root.sh' # Expected: only references inside comments/docs or inside _repo-root.sh itself (which doesn't exist in this grep since excluded) # If any other hit: investigategrep -rn 'cd "\$SCRIPT_DIR/\.\." && pwd' scripts/diagnostics/ 2>&1 # Expected: zero matches (all patterns replaced) -
Step 13: Verify each repaired diagnostic script still finds the repo root correctly from the new location. This is the regression guard for Finding 1:
cd /home/eric/projects/ori_lang # Test 13a: _common.sh sourcing produces correct REPO_ROOT ( SCRIPT_DIR="$(cd scripts/diagnostics && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_repo-root.sh" echo "REPO_ROOT from _repo-root.sh: $REPO_ROOT" test "$REPO_ROOT" = "/home/eric/projects/ori_lang" || { echo "FAIL: wrong root"; exit 1; } ) # Test 13b: check-debug-flags.sh runs and succeeds scripts/diagnostics/check-debug-flags.sh 2>&1 | tail -5 echo "check-debug-flags exit: $?" # Expected: exit 0 (or 1 if there are pre-existing ORI_* inconsistencies — but NOT path-related errors) # Test 13c: self-test.sh --help loads without repo-root resolution errors scripts/diagnostics/self-test.sh --help 2>&1 | head -10 # Test 13d: dual-exec-verify.sh --help loads without repo-root resolution errors scripts/diagnostics/dual-exec-verify.sh --help 2>&1 | head -10 # Test 13e: _common.sh via find_ori_bin ( set -u SCRIPT_DIR="$(cd scripts/diagnostics && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/_common.sh" find_ori_bin echo "ORI=$ORI" test -x "$ORI" || { echo "FAIL: ORI not executable"; exit 1; } ) # Expected: ORI points to target/debug/ori or target/release/ori # Test 13f: fixture wrapper resolves correctly bash scripts/diagnostics/fixtures/mismatch-wrapper.sh --help 2>&1 | head -5 || true # Expected: no "No such file or directory" errors related to target/debug/ori path- All 6 sub-tests show correct repo-root resolution
- If ANY sub-test fails with “No such file or directory” related to
target/debug/ori,compiler/oric/src/debug_flags.rs, or similar repo-rooted paths: STOP, investigate, fix the source before proceeding
-
Step 14: Verify cross-script path references are still valid (scripts that reference each other via
$SCRIPT_DIR):grep -rn '\$SCRIPT_DIR/' scripts/diagnostics/ 2>&1 | head -20These are cross-script references (e.g.,
diagnose-aot.shcalls$SCRIPT_DIR/rc-stats.sh) and are correctly unchanged by the move because all scripts are still siblings in the same directory. -
Step 15: Run the diagnostics self-test from the new location (the canonical validation):
timeout 120 bash scripts/diagnostics/self-test.sh 2>&1 | tail -20- Exit 0 or the self-test’s own defined acceptable exit codes
- No path-related errors in the output
-
Subsection close-out (08.4) — MANDATORY before starting 08.5:
-
diagnostics/absent,scripts/diagnostics/contains all 16+ files plus the new_repo-root.sh - All 9 listed repo-root patterns replaced with
$REPO_ROOT(verified via grep) - Sub-tests 13a–13f all pass
-
self-test.shruns cleanly from the new location - Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — the canonical_repo-root.shresolver is a broadly-reusable pattern. Consider whetherscripts/dev/*.shand other non-diagnostic scripts should adopt the same pattern.scripts/perf-baseline.sh,scripts/cow-benchmark.sh,scripts/cache-doctor.sh, and others likely have their own ad-hoc repo-root derivation. File a new bug via/add-bugif the grep finds similar patterns elsewhere:grep -rn '"\$SCRIPT_DIR/\.\." && pwd"' scripts/ | grep -v scripts/diagnostics/. If hits exist: file the bug and track in bug-tracker; do NOT fix in §08.4’s commit (scope boundary — this section is already large). Document: “Retrospective 08.4: canonical repo-root resolver pattern should be promoted to scripts/ subtree. Filed as BUG-XX-NNN for follow-up.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
-
08.5 Atomic Reference Sweep (747 matches / 230 files)
File(s): CLAUDE.md, CONTRIBUTING.md, all .claude/rules/*.md, all .claude/skills/*/SKILL.md, all .claude/commands/*.md, .codex/skills/tp-help/SKILL.md, plans/**/*.md, docs/compiler/design/appendices/E-coding-guidelines.md, compiler/ori_llvm/tests/aot/util/aot.rs, scripts/release.sh, .claude/rules/diagnostic.md, lefthook.yml (verify — likely no change needed)
The sweep is mechanical but high-risk: a single sed-pattern error could corrupt hundreds of files. The strategy is:
- Only update cleanly-moved script references (
./clippy-all.sh,./llvm-*.sh). The hot-path 4 wrappers mean./test-all.shetc. continue to work unchanged — leave those references alone. - Update
diagnostics/path prefix toscripts/diagnostics/in documentation (CLAUDE.md line 152,.claude/rules/diagnostic.mdtable, anywhere else). - Use a carefully-scoped
rg+sed -iapproach, with verification after each pattern.
Sweep patterns in order:
| Pattern | Old | New | Expected matches |
|---|---|---|---|
| A | ./clippy-all.sh | ./scripts/dev/clippy-all.sh | ~175 |
| B | ./llvm-build.sh | ./scripts/dev/llvm-build.sh | ~0 |
| C | ./llvm-clippy.sh | ./scripts/dev/llvm-clippy.sh | ~0 |
| D | ./llvm-test.sh | ./scripts/dev/llvm-test.sh | ~30 |
| E | diagnostics/ | scripts/diagnostics/ | ~30 (CLAUDE.md + diagnostic.md + related) |
Note: Pattern E is riskier because diagnostics/ is a common word. Scope must be tight.
-
Pattern A:
./clippy-all.sh→./scripts/dev/clippy-all.sh(~175 matches)First, enumerate every match with rg:
cd /home/eric/projects/ori_lang rg -l '\./clippy-all\.sh' > target/sweep-pattern-a-files.txt wc -l target/sweep-pattern-a-files.txt # Expected: ~108 filesVerify the list doesn’t include unintended files:
# Expected paths: plans/**, .claude/rules/*.md, .claude/skills/*.md, .claude/commands/*.md, CLAUDE.md, CONTRIBUTING.md, docs/**, scripts/**, compiler/** # Unexpected: anything outside these (e.g., target/, .git/, etc.) grep -E '^(target|\.git|scripts/dev/clippy-all\.sh)' target/sweep-pattern-a-files.txt || echo "no unexpected files"- Note:
scripts/dev/clippy-all.sh(self-reference) — if this appears in the list, handle carefully. The clippy-all.sh script itself doesn’t use./clippy-all.shin its own body (verify withgrep -n '\./clippy-all\.sh' scripts/dev/clippy-all.sh), butfull-check.shcalls$SCRIPT_DIR/clippy-all.shwhich is NOT matched by the./clippy-all.shpattern — safe.
Apply the sed substitution:
while read -r file; do sed -i 's|\./clippy-all\.sh|\./scripts/dev/clippy-all\.sh|g' "$file" done < target/sweep-pattern-a-files.txtVerify:
# Old pattern should be gone rg '\./clippy-all\.sh' 2>&1 | grep -v '/scripts/dev/' || echo "clean" # New pattern should be present in the expected places rg '\./scripts/dev/clippy-all\.sh' | head -10 - Note:
-
Pattern B:
./llvm-build.sh→./scripts/dev/llvm-build.sh(likely 0 matches)rg -l '\./llvm-build\.sh' > target/sweep-pattern-b-files.txt wc -l target/sweep-pattern-b-files.txt- If 0 matches: skip the sed step; pattern B is no-op
- If >0 matches: apply same sed substitution
-
Pattern C:
./llvm-clippy.sh→./scripts/dev/llvm-clippy.sh(likely 0 matches)Same treatment as Pattern B.
-
Pattern D:
./llvm-test.sh→./scripts/dev/llvm-test.sh(~30 matches)rg -l '\./llvm-test\.sh' > target/sweep-pattern-d-files.txt wc -l target/sweep-pattern-d-files.txt # Expected: ~16 files while read -r file; do sed -i 's|\./llvm-test\.sh|\./scripts/dev/llvm-test\.sh|g' "$file" done < target/sweep-pattern-d-files.txt rg '\./llvm-test\.sh' | grep -v '/scripts/dev/' || echo "clean" -
Pattern E:
diagnostics/→scripts/diagnostics/(explicit allow-list scoped; DO NOT use deny-list)CRITICAL FINDING (Phase 2 dual-source review, 2026-04-11): The previous version of this pattern used a deny-list (
grep -v '^target/', etc.) to exclude unintended files. That is unsafe because the repo contains REAL Rust modules nameddiagnostics/that a blind sweep would corrupt:compiler/ori_eval/src/diagnostics/(actual Rust module —mod diagnostics;+mod.rs)compiler/ori_patterns/src/errors/diagnostics/(actual Rust module)compiler/oric/src/llvm_dump/mod.rs:8(Rust comment://! This is the in-compiler equivalent of \diagnostics/ir-dump.sh`…) — this is a legitimate Rust source comment that happens to mention the path; it is in scope for the sweep BUT the deny-list would not catch it and the corruption risk is that the sed pattern applied to a.rsfile might interact poorly with other mentions ofdiagnostics`
Pattern E MUST use an explicit allow-list — only these paths are eligible for the substitution:
CLAUDE.mdCONTRIBUTING.md(verify first — may be unchanged).claude/(all.mdfiles — skills, rules, commands).codex/(all.mdfiles — reviewer skills).gemini/(if present)plans/— EXCEPTplans/completed/(archival per §09.1 exclusion rule)docs/— all.mdfiles (any diagnostic references in compiler/appendix docs)lefthook.yml(verify first — probably unchanged)scripts/top-level*.shand*.pyfiles (Phase 4 TPR-XX-001-gemini fix — defensive inclusion) — ONLY files directly underscripts/, EXCLUDINGscripts/dev/(the moved internal scripts don’t referencediagnostics/) and EXCLUDINGscripts/diagnostics/(self-references, handled in Step E.9 below). Current known consumers in this scope:scripts/release.sh,scripts/cache-doctor.sh,scripts/perf-baseline.sh,scripts/cow-benchmark.sh,scripts/pgo-build.sh,scripts/pgo-train.sh,scripts/bump-build.sh,scripts/sync-version.sh,scripts/*.py. Pre-sweep verification (rg -n 'diagnostics/' scripts/*.sh scripts/*.py 2>/dev/null) confirmed zero current references at 2026-04-11 TPR iteration 1, but the allow-list includes them defensively so any future addition is automatically swept.
Pattern E MUST NEVER touch:
compiler/— contains real Rustdiagnosticsmodules + comment referenceslibrary/— stdlibtests/— test data, spec teststarget/,target-llvm/— build artifacts.git/— git internalsscripts/dev/— newly-moved hot-path/cleanly-moved scripts (zerodiagnostics/refs expected; sweep skip defensive)scripts/diagnostics/— the newly-moved diagnostic scripts themselves. Self-references within these files are handled separately in Step E.9, NOT via Pattern E (TPR-XX-004-codex fix). The reason: Pattern E’s allow-list operates on reference paths likediagnostics/ir-dump.sh(outside the directory), but the moved scripts’--helptext and README contain the same substring as self-references (diagnostics/diagnose-aot.sh file.orias an example command). A post-move scoped sweep overscripts/diagnostics/**updates them cleanly without risking Pattern E’s allow-list expansion.
Step E.1 — Safety pre-check: count and verify zero compiler/library/tests matches are eligible for the sweep:
cd /home/eric/projects/ori_lang # These counts MUST be zero for Pattern E to proceed safely. # If any are non-zero, Pattern E must NOT run on these paths — the deny-list # is rejected in favor of an explicit allow-list. compiler_hits=$(rg -l 'diagnostics/' compiler/ 2>/dev/null | wc -l) library_hits=$(rg -l 'diagnostics/' library/ 2>/dev/null | wc -l) tests_hits=$(rg -l 'diagnostics/' tests/ 2>/dev/null | wc -l) echo "compiler/: $compiler_hits files (MUST NOT be swept — these are real Rust modules + comments)" echo "library/: $library_hits files (MUST NOT be swept)" echo "tests/: $tests_hits files (MUST NOT be swept)"- Document the counts. Expected (from 2026-04-11 grep verification):
compiler/ = 1 file(compiler/oric/src/llvm_dump/mod.rs:8— a Rust doc comment),library/ = 0 files,tests/ = 0 files. - These files MUST NOT be in the allow-list. The single
compiler/oric/src/llvm_dump/mod.rs:8reference is a Rust source comment that happens to describe the equivalent shell script. It must be updated separately in Step E.4 below as a manualEdittool call — it is NOT in Pattern E’s scope.
Step E.2 — Build the explicit allow-list of files to sweep:
cd /home/eric/projects/ori_lang # Enumerate the allow-list scope. Note: `rg` respects .gitignore so target/ is # already excluded; the explicit scope makes safety load-bearing not incidental. { # CLAUDE.md / CONTRIBUTING.md / lefthook.yml at root test -f CLAUDE.md && echo CLAUDE.md test -f CONTRIBUTING.md && echo CONTRIBUTING.md test -f lefthook.yml && echo lefthook.yml # .claude/ .codex/ .gemini/ — all .md files find .claude -type f -name '*.md' 2>/dev/null find .codex -type f -name '*.md' 2>/dev/null find .gemini -type f -name '*.md' 2>/dev/null # plans/ EXCEPT plans/completed/ find plans -type f -name '*.md' -not -path 'plans/completed/*' 2>/dev/null # docs/ all .md find docs -type f -name '*.md' 2>/dev/null # scripts/ top-level .sh and .py (NOT scripts/dev/ or scripts/diagnostics/) # TPR-XX-001-gemini fix — defensive inclusion so future additions auto-sweep find scripts -maxdepth 1 -type f \( -name '*.sh' -o -name '*.py' \) 2>/dev/null } > target/sweep-pattern-e-allowlist.txt wc -l target/sweep-pattern-e-allowlist.txt- Allow-list file count captured.
- Manually spot-check the allow-list —
head -50 target/sweep-pattern-e-allowlist.txt— to verify no compiler/library/tests files leaked in AND noscripts/dev/orscripts/diagnostics/files leaked in. - Defensive check:
grep -E '^(compiler|library|tests|scripts/dev|scripts/diagnostics)/' target/sweep-pattern-e-allowlist.txt— must return empty. If any match: STOP, fix the allow-list generation.
Step E.3 — Narrow the allow-list to files that actually contain
diagnostics/(avoid touching files that don’t need changes):# Filter: only files whose content contains "diagnostics/" in a path-ish context xargs -a target/sweep-pattern-e-allowlist.txt rg -l 'diagnostics/' 2>/dev/null \ | sort -u > target/sweep-pattern-e-files.txt wc -l target/sweep-pattern-e-files.txt- Review the final list manually:
cat target/sweep-pattern-e-files.txt - Expected entries (from 2026-04-11 verification):
CLAUDE.md,.claude/rules/diagnostic.md,.claude/rules/arc.md,.claude/rules/eval.md,.claude/skills/fix-bug/SKILL.md,.claude/skills/improve-tooling/SKILL.md,.claude/skills/dual-tpr/envelope-format.md,.claude/skills/create-plan/plan-schema.md,.claude/skills/continue-roadmap/SKILL.md, variousplans/*/section-*.md,docs/compiler/design/appendices/D-debugging.md, and similar. NOcompiler/,library/,tests/entries. - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW. - If ANY file in
target/sweep-pattern-e-files.txtis undercompiler/,library/, ortests/: STOP. The allow-list is leaking. Investigate before proceeding.
Step E.4 — Manual Rust comment fix (outside Pattern E scope): Update
compiler/oric/src/llvm_dump/mod.rs:8separately with theEdittool — it is a Rust doc comment that referencesdiagnostics/ir-dump.sh:grep -n 'diagnostics/' compiler/oric/src/llvm_dump/mod.rsExpected: line 8 reads
//! This is the in-compiler equivalent of \diagnostics/ir-dump.sh`, triggered via(or similar). UseEdit` tool:old_string: //! This is the in-compiler equivalent of `diagnostics/ir-dump.sh`, triggered via new_string: //! This is the in-compiler equivalent of `scripts/diagnostics/ir-dump.sh`, triggered via- Verify:
grep -n 'diagnostics/' compiler/oric/src/llvm_dump/mod.rsshows only the new form. - Verify
cargo check -p oricstill passes (comment change cannot affect compilation, but sanity-check). - This single manual edit covers the ONLY in-compiler reference and keeps Pattern E’s scope clean.
Step E.5 — Apply the sed substitution to the allow-listed files ONLY. Use multiple scoped patterns rather than a single catch-all to minimize false-positive risk:
while read -r file; do # Backtick-wrapped path: `diagnostics/...` → `scripts/diagnostics/...` sed -i 's|`diagnostics/|`scripts/diagnostics/|g' "$file" # Explicit ./diagnostics/ → ./scripts/diagnostics/ sed -i 's|\./diagnostics/|./scripts/diagnostics/|g' "$file" # Whitespace-prefixed diagnostics/ (command-line context) sed -i 's| diagnostics/| scripts/diagnostics/|g' "$file" # Start-of-line diagnostics/ (rare, but possible in code fences) sed -i 's|^diagnostics/|scripts/diagnostics/|g' "$file" # Double-quoted path: "diagnostics/..." → "scripts/diagnostics/..." # (TPR-XX-002-gemini iteration 2 fix — JSON fixtures, schema examples, code # blocks with quoted path literals would otherwise escape the sweep.) sed -i 's|"diagnostics/|"scripts/diagnostics/|g' "$file" done < target/sweep-pattern-e-files.txtStep E.6 — Post-sweep safety verification. The sweep MUST NOT have touched
compiler/,library/, ortests/. This is verified at commit time (§08.6) viagit diff --stat, but also check directly here:# Any Rust file modified? If any, something leaked. git diff --name-only | grep -E '\.(rs|toml)$' | grep -v '^plans/' # Expected (post Step E.4): only compiler/oric/src/llvm_dump/mod.rs (from the manual Edit) # If any OTHER .rs file appears: STOP, inspect, revert- Only
compiler/oric/src/llvm_dump/mod.rsis modified incompiler/(from the manual Edit in Step E.4) - No files in
library/are modified - No files in
tests/are modified -
plans/completed/files are NOT modified (archival exclusion)
Step E.7 — Verify the substitution landed correctly:
# Remaining "diagnostics/" matches in the allow-listed files should only be: # - The new "scripts/diagnostics/" form # - Prose references that weren't scoped to a path (e.g., "the diagnostics directory without a trailing path") while read -r file; do grep -n 'diagnostics/' "$file" 2>/dev/null done < target/sweep-pattern-e-files.txt \ | grep -v 'scripts/diagnostics/' \ | head -20- Every remaining match is either prose (fine) or needs an additional targeted fix
- If any residual
./diagnostics/or backtick-wrappeddiagnostics/script.shform remains, apply a targeted fix viaEdit
Step E.8 — Negative-pin: verify compiler source is untouched by Pattern E. This is the regression guard for Finding 2:
# Verify no compiler/ file other than llvm_dump/mod.rs was touched git status --porcelain compiler/ 2>&1 \ | grep -v ' compiler/oric/src/llvm_dump/mod.rs$' \ | grep -v '^$' \ || echo "CLEAN — Pattern E did not touch compiler/ beyond the manual llvm_dump/mod.rs edit" # Expected: "CLEAN — ..." line printed- Output shows CLEAN
- If any other
compiler/file is modified: STOP, revert the sweep, investigate why the allow-list leaked
Step E.9 — Post-move sweep of
scripts/diagnostics/self-references. (TPR-XX-004-codex fix — 2026-04-11.) The moved diagnostic scripts andREADME.mdcontain internal self-references that saydiagnostics/ir-dump.sh ...etc. Pattern E’s allow-list deliberately EXCLUDESscripts/diagnostics/(to avoid scope creep), so those self-references survive unchanged after §08.4’s move. This creates a DRIFT finding: the canonical home isscripts/diagnostics/but the scripts’ own help text still instructs users to rundiagnostics/.... Fix with a scoped post-move sweep that ONLY touchesscripts/diagnostics/**.Known self-reference locations (from 2026-04-11 TPR verification):
scripts/diagnostics/README.mdlines 30-35, 43-58, 165-183 — usage examplesscripts/diagnostics/ir-dump.shlines 5 and 86 — usage headerscripts/diagnostics/self-test.shlines 5 and 11 — usage header- Any other
.shor.mdinscripts/diagnostics/containingdiagnostics/
Step E.9 implementation:
cd /home/eric/projects/ori_lang # Enumerate ONLY scripts/diagnostics/ self-refs (post-move state) rg -l 'diagnostics/' scripts/diagnostics/ > target/sweep-pattern-e9-files.txt wc -l target/sweep-pattern-e9-files.txt cat target/sweep-pattern-e9-files.txt- Review the list. Every entry must be under
scripts/diagnostics/. - Apply a scoped sed substitution to each file. The pattern is different from Pattern E because we’re rewriting
diagnostics/(internal self-reference) →scripts/diagnostics/(canonical new path). NOTE: this is NOT the same as Pattern E’s sed — Pattern E replacesdiagnostics/anywhere in the allow-listed files, while E.9 replacesdiagnostics/specifically in the moved files’ own content (not cross-references FROM elsewhere INTO these files).
while read -r file; do # Match diagnostics/ in path-like contexts: after space, backtick, double-quote, # ./ prefix, or start-of-line. Same scoping as Pattern E to avoid rewriting prose. # Also match double-quoted paths per TPR-XX-002-gemini iteration 2 fix. sed -i 's|`diagnostics/|`scripts/diagnostics/|g; s|\./diagnostics/|./scripts/diagnostics/|g; s| diagnostics/| scripts/diagnostics/|g; s|^diagnostics/|scripts/diagnostics/|g; s|"diagnostics/|"scripts/diagnostics/|g' "$file" done < target/sweep-pattern-e9-files.txt- Verify the substitutions:
rg -n 'diagnostics/' scripts/diagnostics/ 2>&1 \ | grep -v '^scripts/diagnostics/' \ || echo "CLEAN — no surviving stale self-references" # Any surviving reference that's NOT already scripts/diagnostics/ is a miss rg -n '\bdiagnostics/' scripts/diagnostics/ 2>&1 \ | grep -v 'scripts/diagnostics/' \ || echo "CLEAN" - Spot-check updated files:
head -35 scripts/diagnostics/README.md | tail -15 head -10 scripts/diagnostics/ir-dump.sh head -15 scripts/diagnostics/self-test.sh- Each shows
scripts/diagnostics/...rather thandiagnostics/...in usage examples
- Each shows
Step E.10 — Negative-pin for Step E.9 scope containment. Verify Step E.9’s sweep did NOT escape
scripts/diagnostics/:git diff --name-only 2>&1 | grep -v '^scripts/diagnostics/' \ | grep -v '^plans/project-reorganization/' \ | head -20 # Expected: only files from OTHER Pattern E/A/D sweeps (e.g. CLAUDE.md, .claude/ files) # — NOT any new compiler/, library/, tests/ entries from a Step E.9 scope leak- No compiler/, library/, tests/ entries added by Step E.9
-
.claude/rules/diagnostic.md — specific section header + table update:
grep -n '## Diagnostic Scripts' .claude/rules/diagnostic.md # Expected: line 47-ish "## Diagnostic Scripts (`diagnostics/`)"After Pattern E, the header should read ”## Diagnostic Scripts (
scripts/diagnostics/)”. Verify. -
CLAUDE.md line 152 specific check:
sed -n '150,155p' CLAUDE.md # Expected: the line 152 diagnostics references are now scripts/diagnostics/ -
lefthook.yml verification (should NOT need changes — hot-path wrappers preserve paths):
grep -n '\./' lefthook.yml # Expected: ./fmt-all.sh and ./full-check.sh (unchanged because they're root wrappers now) # Also: ./scripts/sync-version.sh (unchanged, already in scripts/)- NO changes to lefthook.yml are required, because the wrappers preserve
./fmt-all.shand./full-check.shat root
- NO changes to lefthook.yml are required, because the wrappers preserve
-
compiler/ori_llvm/tests/aot/util/aot.rs — test code has a script reference at line 168:
grep -n '\./llvm-test\.sh\|\./test-all\.sh' compiler/ori_llvm/tests/aot/util/aot.rs- If pattern D caught this file,
./llvm-test.shis already updated. If the reference was./test-all.sh, it’s unchanged (wrapper preserves path) and needs no action. - If the reference is something else: investigate and fix manually
- If pattern D caught this file,
-
scripts/release.sh — internal reference at line 130:
grep -n '\./test-all\.sh\|\./scripts/dev/' scripts/release.sh- The reference to
./test-all.shis unchanged (wrapper preserves) - Note: if release.sh also references
./clippy-all.shor similar, pattern A caught it
- The reference to
-
Global post-sweep verification — ZERO cleanly-moved script references at root form remain:
rg '\./(clippy-all|llvm-build|llvm-clippy|llvm-test)\.sh' 2>&1 \ | grep -v 'scripts/dev/' \ | grep -v '^plans/project-reorganization/section-08-script-consolidation\.md' \ || echo "CLEAN — zero stale cleanly-moved references"- Output: “CLEAN — zero stale cleanly-moved references” (the grep -v excludes this very plan file which mentions the old paths for context)
-
Hot-path references may still appear as
./test-all.shetc. — this is EXPECTED and correct (wrappers preserve those paths):rg -c '\./test-all\.sh' 2>&1 | head -5 # Expected: most references preserved (wrappers mean the root path still resolves) -
Subsection close-out (08.5) — MANDATORY before starting 08.6:
- All 5 sweep patterns applied
- Zero stale cleanly-moved references remain
-
diagnostics/path references updated toscripts/diagnostics/ - CLAUDE.md line 152 updated;
.claude/rules/diagnostic.mdheader updated - lefthook.yml verified unchanged (wrappers preserve paths)
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — this is THE section where tooling matters most. The 5-pattern sweep with per-pattern verification was mechanical but error-prone. Is there a reusablescripts/dev/atomic-sweep.sh <old> <new> [scope-exclude]helper that: (1) enumerates matching files, (2) shows a preview, (3) asks for confirmation, (4) applies sed, (5) verifies the old pattern is gone and the new pattern is present in expected count? This would be broadly useful for every future reorg. Build it NOW and commit viabuild(scripts): add atomic-sweep.sh — surfaced by project-reorganization/section-08.5 retrospective. High confidence this helper has reuse value beyond this one plan. - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.6 End-to-End Validation
File(s): No edits — smoke-test invocation only
This subsection runs per-script smoke tests. The full invocation matrix lives in §08.7; the atomic commit lives in §08.8. Keeping validation, matrix, and commit in separate subsections lets us rollback cleanly if a late check fails (per §08.N rollback strategy).
-
Verify the wrapper-forwarding works end-to-end for each hot-path script:
cd /home/eric/projects/ori_lang # test-all.sh via wrapper timeout 150 ./test-all.sh 2>&1 | tail -15 # Expected: exit 0, all phases PASS -
Verify
./test-all.sh --jsonwrites to the correct location (should be target/test-all-results.json per §02 fix):ls target/test-all-results.json && echo "JSON artifact created" -
Verify full-check wrapper → internal chain works:
timeout 150 ./full-check.sh 2>&1 | tail -15 # Expected: calls clippy-all (now scripts/dev/) then test-all (wrapper → scripts/dev/) # Exit 0 -
Verify fmt wrapper:
timeout 60 ./fmt-all.sh 2>&1 | tail -10 # Expected: exit 0 or fmt diff output -
Verify build wrapper:
timeout 150 ./build-all.sh 2>&1 | tail -10 # Expected: exit 0 (build succeeds) -
Verify cleanly-moved scripts work from
scripts/dev/:timeout 60 ./scripts/dev/clippy-all.sh 2>&1 | tail -10 -
Verify diagnostic scripts work from
scripts/diagnostics/:bash scripts/diagnostics/self-test.sh 2>&1 | tail -10 -
Verify lefthook pre-commit hook passes:
# Simulate a pre-commit run (requires lefthook installed) lefthook run pre-commit 2>&1 | tail -20 || echo "lefthook not installed locally — skip"- If lefthook is installed: exit 0, all hooks pass
- If not installed locally: skip (will be validated by the commit itself — lefthook runs as the pre-commit hook on
git commit)
-
Subsection close-out (08.6) — MANDATORY before starting 08.7:
- All 7 smoke tests passed
- No commit yet — that happens in §08.8
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — the end-to-end validation sequence (test-all, full-check, fmt, build, cleanly-moved, diagnostics, lefthook) was 7 separate verification commands. §09 re-runs similar commands; no separate helper warranted here. Document: “Retrospective 08.6: verification overlaps with §09; no separate tooling warranted.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.7 Invocation Matrix Verification (script × context)
File(s): No edits — matrix verification only
Phase 2 finding (MAJOR, 2026-04-11): Per .claude/rules/tests.md §Matrix Testing Rule, file-reorg sections must declare their test matrix dimensions. For §08, the matrix is script × invocation-context. The prior version of §08.6 tested each script once in the “direct-root” context but did not exercise the wrapper under alternate invocation paths (symlink, cross-directory cwd, inner-chain re-entry, test-code reference, lefthook context). Missing matrix cells are future regressions.
Matrix dimensions
Dimension 1 — scripts (8 internal dev scripts):
| Script | Type | Home |
|---|---|---|
test-all.sh | Wrapper + impl | root + scripts/dev/ |
full-check.sh | Wrapper + impl | root + scripts/dev/ |
fmt-all.sh | Wrapper + impl | root + scripts/dev/ |
build-all.sh | Wrapper + impl | root + scripts/dev/ |
clippy-all.sh | Cleanly-moved | scripts/dev/ |
llvm-build.sh | Cleanly-moved | scripts/dev/ |
llvm-clippy.sh | Cleanly-moved | scripts/dev/ |
llvm-test.sh | Cleanly-moved | scripts/dev/ |
Dimension 2 — invocation contexts:
| ID | Context | Tests |
|---|---|---|
| C1 | direct-root (./<script>.sh) | Wrapper exec forwarding from root cwd |
| C2 | direct-scripts-dev (./scripts/dev/<script>.sh) | Direct invocation of real impl |
| C3 | cross-dir (cd compiler && ../<script>.sh) | PWD assumption (scripts that assume cwd = repo root) |
| C4 | symlink (ln -s $PWD/test-all.sh /tmp/t && /tmp/t) | Symlink-safety of wrapper dirname resolution |
| C5 | lefthook-context (lefthook run pre-commit) | Wrappers invoked from git hook runner |
| C6 | internal-chain (full-check → clippy-all + test-all via $SCRIPT_DIR) | Inner-script sibling discovery |
| C7 | test-code error-message string literal (compiler/ori_llvm/tests/aot/util/aot.rs:168) | Pattern-D sweep correctness in a Rust string literal (NOT a runtime invocation — the string is part of a panic message suggesting ./llvm-test.sh to the user) |
| C8 | negative-pin (deliberate test failure — wrapper must propagate non-zero exit) | Wrapper exit-code transparency — applies to wrapper scripts ONLY (the test is exercising the exec forwarding of the thin wrapper, not anything specific to cleanly-moved scripts) |
| C9 | PATH invocation (bare-name from outside repo root) | UNSUPPORTED — documented in §08.3 wrapper comment |
Cells that apply per script type (TPR iteration 2 fix — reconciled against actual implementation):
- Wrapper scripts (test-all, full-check, fmt-all, build-all): C1, C2, C3, C4, C5 (lefthook only runs fmt-all + full-check), C8 (1 cell on test-all wrapper, exercises wrapper exit-code forwarding — the wrapper template is identical across all 4 so 1 test is load-bearing for all). C6 for full-check only. C9 SKIP (all 4).
- Cleanly-moved scripts (clippy-all, llvm-*): C1-negative (MUST NOT exist at root), C2 (direct invocation). C3 does NOT apply (these scripts are not invoked via wrapper forwarding; any cwd assumption they have is not a wrapper concern). C4/C5 don’t apply. C6 applies to clippy-all only (called by
full-checkvia$SCRIPT_DIR). C7 applies tollvm-test.shspecifically (Pattern D target for the aot.rs:168 string literal). C8 does NOT apply (no wrapper to test). C9 does NOT apply (no wrapper).
Matrix cells and expected outcomes
-
C1 — direct-root wrapper forwarding (4 cells)
cd /home/eric/projects/ori_lang timeout 150 ./test-all.sh 2>&1 | tail -5; echo "test-all exit: $?" timeout 150 ./full-check.sh 2>&1 | tail -5; echo "full-check exit: $?" timeout 60 ./fmt-all.sh 2>&1 | tail -5; echo "fmt-all exit: $?" timeout 150 ./build-all.sh 2>&1 | tail -5; echo "build-all exit: $?"- All 4 exit 0
-
C1-negative — cleanly-moved scripts MUST NOT exist at root (4 negative-pin cells)
test -f clippy-all.sh && echo "FAIL: clippy-all.sh still at root" || echo "OK: absent" test -f llvm-build.sh && echo "FAIL: llvm-build.sh still at root" || echo "OK: absent" test -f llvm-clippy.sh && echo "FAIL: llvm-clippy.sh still at root" || echo "OK: absent" test -f llvm-test.sh && echo "FAIL: llvm-test.sh still at root" || echo "OK: absent"- All 4 print “OK: absent”
-
C2 — direct invocation of real impl at scripts/dev/ (8 cells)
timeout 150 ./scripts/dev/test-all.sh 2>&1 | tail -5; echo "exit: $?" timeout 150 ./scripts/dev/full-check.sh 2>&1 | tail -5; echo "exit: $?" timeout 60 ./scripts/dev/fmt-all.sh 2>&1 | tail -5; echo "exit: $?" timeout 150 ./scripts/dev/build-all.sh 2>&1 | tail -5; echo "exit: $?" timeout 60 ./scripts/dev/clippy-all.sh 2>&1 | tail -5; echo "exit: $?" timeout 30 ./scripts/dev/llvm-build.sh 2>&1 | tail -5; echo "exit: $?" timeout 30 ./scripts/dev/llvm-clippy.sh 2>&1 | tail -5; echo "exit: $?" timeout 150 ./scripts/dev/llvm-test.sh 2>&1 | tail -5; echo "exit: $?"- All 8 exit 0 (or the script’s expected non-blocking exit code)
-
C3 — cross-directory invocation (PWD assumption test, 4 cells — wrappers only)
Wrappers assume cwd = repo root (test-all.sh writes
target/test-all-results.jsonas a relative path, etc.). Test invocation from a subdirectory for ALL 4 wrappers (TPR-XX-003-codex iteration 2 fix — previous version only exercised test-all and fmt-all):cd /home/eric/projects/ori_lang/compiler timeout 150 ../test-all.sh 2>&1 | tail -10; echo "test-all exit: $?" timeout 150 ../full-check.sh 2>&1 | tail -10; echo "full-check exit: $?" timeout 30 ../fmt-all.sh 2>&1 | tail -10; echo "fmt-all exit: $?" timeout 150 ../build-all.sh 2>&1 | tail -10; echo "build-all exit: $?" cd /home/eric/projects/ori_lang- Expected behavior decision: wrappers invoked from non-repo-root cwd may FAIL (e.g., writing JSON to
compiler/target/test-all-results.jsoninstead of repo-root target/). Per Finding 8, this is an intentional limitation — wrappers are documented as requiring cwd=repo-root. If the test fails with a path resolution error from a subdirectory, that is EXPECTED and does not fail §08. - Document the actual behavior observed — pass/fail — and capture as a known limitation.
- Option: if the invocation from compiler/ fails, this is the documented limitation (Finding 8 chose documentation over anchoring). Proceed.
- Option: if a future plan decides to support cross-dir invocation, the fix is to add
cd "$(git rev-parse --show-toplevel)"to the wrapper template — but not in this section; file as/add-bugfor follow-up if the limitation is judged too painful to live with.
- Expected behavior decision: wrappers invoked from non-repo-root cwd may FAIL (e.g., writing JSON to
-
C4 — symlink invocation (wrapper symlink-safety, 4 cells)
The wrapper template (per §08.3) uses
readlink -f "$0"to resolve through symlinks before computingdirname. This meansln -s $PWD/test-all.sh /tmp/t && /tmp/tworks correctly becausereadlink -freturns the real path in the repo. Test:cd /home/eric/projects/ori_lang ln -sf "$PWD/test-all.sh" /tmp/ori-test-all-symlink timeout 150 /tmp/ori-test-all-symlink 2>&1 | tail -10; echo "test-all symlink exit: $?" rm /tmp/ori-test-all-symlink ln -sf "$PWD/full-check.sh" /tmp/ori-full-check-symlink timeout 150 /tmp/ori-full-check-symlink 2>&1 | tail -10; echo "full-check symlink exit: $?" rm /tmp/ori-full-check-symlink ln -sf "$PWD/fmt-all.sh" /tmp/ori-fmt-all-symlink timeout 60 /tmp/ori-fmt-all-symlink 2>&1 | tail -10; echo "fmt-all symlink exit: $?" rm /tmp/ori-fmt-all-symlink ln -sf "$PWD/build-all.sh" /tmp/ori-build-all-symlink timeout 150 /tmp/ori-build-all-symlink 2>&1 | tail -10; echo "build-all symlink exit: $?" rm /tmp/ori-build-all-symlink- All 4 symlink invocations exit 0 (the wrapper’s
readlink -fmakes symlink invocation transparent) - If any C4 cell FAILS: the wrapper template lost its
readlink -fresolution somewhere in §08.3 — STOP, inspect the 4 wrapper files, verify they containSCRIPT_PATH="$(readlink -f "$0")"andexec "$(dirname "$SCRIPT_PATH")/scripts/dev/<script>.sh". Fix viaEditand re-run C4. - Platform note:
readlink -fis GNU-specific. On macOS with BSDreadlink, the wrapper would fall back to the non-symlink-resolving form and C4 would fail. The Execution Prerequisites in00-overview.mdrequire GNU userland — this cell is one concrete reason that requirement exists.
- All 4 symlink invocations exit 0 (the wrapper’s
-
C5 — lefthook pre-commit context (2 cells — fmt-all, full-check)
lefthook run pre-commit 2>&1 | tail -20; echo "lefthook exit: $?"- Exit 0 — both
./fmt-all.shand./full-check.share invoked by lefthook.yml and succeed - If lefthook not installed: skip with note (will run at actual commit time anyway)
- Exit 0 — both
-
C6 — internal-chain resolution (1 cell — full-check → clippy-all + test-all via $SCRIPT_DIR)
Already covered by §08.6 full-check smoke test, but verify explicitly:
# full-check.sh at scripts/dev/full-check.sh calls: # $SCRIPT_DIR/clippy-all.sh (sibling — cleanly-moved, in scripts/dev/) # $SCRIPT_DIR/test-all.sh (sibling — wrapper target, in scripts/dev/) # Both must resolve when invoked via the root wrapper. timeout 150 ./full-check.sh 2>&1 | grep -E '(clippy-all|test-all)' | head -5 echo "exit: $?"- Output shows both chain steps executing
- Exit 0
-
C7 — test-code error-message string literal (1 cell — compiler/ori_llvm/tests/aot/util/aot.rs:168)
TPR iteration 2 correction: The earlier claim that C7 exercises “live test-code invocation” was WRONG.
compiler/ori_llvm/tests/aot/util/aot.rs:168is inside a panic message string literal that says (pre-sweep):"or use./llvm-test.shwhich builds automatically."— a user-facing error hint, NOT a runtimestd::process::Commandinvocation. The test code doesn’t actually execute the shell script; it only references the name in help text.C7 is therefore a Pattern-D sweep-correctness check, not a runtime invocation test. Verify the Rust file’s string literal was correctly rewritten by Pattern D:
grep -n '\./llvm-test\.sh\|scripts/dev/llvm-test\.sh' compiler/ori_llvm/tests/aot/util/aot.rs # Expected POST-sweep: single hit showing "./scripts/dev/llvm-test.sh" in the error message string # NOT expected: "./llvm-test.sh" (the pre-sweep form) — if found, Pattern D missed it- Single post-sweep reference present in the panic message
- No pre-sweep
./llvm-test.shform remains in the file - The Rust file still parses (compiler hasn’t broken syntax):
cargo check -p ori_llvm 2>&1 | tail -5 - Exit 0 from
cargo check
-
C8 — negative-pin (wrapper exit-code propagation, 1 cell)
Introduce a deliberate failure and verify the wrapper propagates the non-zero exit:
cd /home/eric/projects/ori_lang # Create a temporary failing test at the very tail of a test run by corrupting # a test input (reversible via git checkout). The safest way is to have # scripts/dev/test-all.sh exit non-zero via an explicit injection: # Option A (safer): directly run a failing inner command via the wrapper # Use `false` as a sentinel — shells exec forwarding is tested by any failing exit. # Create a temporary wrapper that execs `false` and verify exit propagation: cat > /tmp/ori-negative-wrapper.sh <<'EOF' #!/usr/bin/env bash exec "$(dirname "$0")/scripts/dev/non-existent-script.sh" "$@" EOF chmod +x /tmp/ori-negative-wrapper.sh /tmp/ori-negative-wrapper.sh actual_exit=$? test "$actual_exit" != 0 && echo "OK: wrapper propagated non-zero exit ($actual_exit)" \ || echo "FAIL: wrapper did NOT propagate non-zero exit" rm /tmp/ori-negative-wrapper.sh- Output shows “OK: wrapper propagated non-zero exit”
- (Optional, if §07 / §06 work allows time) also exercise the real wrapper pattern with a known-failing test injected into
scripts/dev/test-all.sh— reversible via git checkout
-
C9 — PATH invocation (4 cells, documented SKIP per TPR-XX-002-codex fix, 2026-04-11)
Verify the wrapper’s behavior under PATH invocation (user adds the repo root to PATH and runs
test-all.shwithout./from any cwd). Per the §08.3 wrapper documentation, this invocation pattern is UNSUPPORTED becausereadlink -f "$0"treats a bare name as cwd-relative.cd /home/eric/projects/ori_lang # Simulate PATH invocation from a subdirectory PATH="$PWD:$PATH" bash -c 'cd /tmp && test-all.sh --help 2>&1 | head -5' # Expected: wrapper fails to find scripts/dev/test-all.sh (because readlink -f "test-all.sh" # from /tmp resolves to /tmp/test-all.sh which doesn't exist, and the exec target # becomes /tmp/scripts/dev/test-all.sh which also doesn't exist). # This cell MUST be marked SKIP (unsupported) rather than FAIL.- Behavior confirmed: wrapper fails when invoked via PATH from outside the repo
- Mark all 4 C9 cells (test-all, full-check, fmt-all, build-all via PATH) as SKIP — unsupported per §08.3 wrapper documentation
- Document in the retrospective: “C9 PATH invocation is an explicit non-goal of Option C. Aliases to
./test-all.shfrom the repo root work fine; developers who want PATH-style invocation should use an alias likealias test-all='./test-all.sh'rather than putting the repo root on PATH.”
-
Matrix completeness check: total cells = 4 (C1 wrapper exec) + 4 (C1-negative cleanly-moved absent) + 8 (C2 direct scripts/dev) + 4 (C3 cross-dir wrappers — ALL 4 wrappers now per TPR-XX-003-codex iteration 2 fix) + 4 (C4 symlink wrappers) + 2 (C5 lefthook — fmt-all + full-check) + 1 (C6 full-check internal chain) + 1 (C7 pattern-check, not invocation, per TPR iteration 2 reclassification) + 1 (C8 wrapper exit-code — 1 representative test, not per-script) + 4 (C9 PATH SKIP unsupported) = 33 cells.
echo "Matrix cells enumerated: 33 (29 exercisable + 4 SKIP)" echo "Breakdown: C1=4, C1-neg=4, C2=8, C3=4, C4=4, C5=2, C6=1, C7=1, C8=1, C9=4 (SKIP)" echo "Any cell SKIPPED must be documented in the retrospective with reason"- Document every SKIPPED cell. Acceptable skips: C5 partial when lefthook not locally installed (will run at commit time), C9 PATH invocation (all 4 cells — unsupported per TPR-XX-002-codex fix). C7 is a pattern-check (not invocation) and is always exercisable via grep + cargo check.
- Any FAILED cell that is NOT a documented limitation (C3 per Finding 8 on cross-dir PWD assumption, C9 per TPR-XX-002-codex on PATH unsupported) blocks §08.8 commit.
-
Subsection close-out (08.7) — MANDATORY before starting 08.8:
- All applicable matrix cells either PASS or are documented SKIPS or are documented limitations (C3)
- C4 symlink result captured; if failed, wrapper template updated + re-tested OR bug filed
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — the matrix had 33 cells total (29 exercisable + 4 PATH SKIP). Ascripts/dev/verify-invocation-matrix.shhelper that runs all 33 cells (including validating the 4 C9 SKIP cells fail-closed with the correct unsupported-PATH-invocation error rather than accidentally succeeding) and reports pass/fail/skip per cell would be broadly useful for every future script-layout reorg. Build if time permits; otherwise file as/add-bugfor follow-up. Document outcome. - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.8 Atomic Commit
File(s): No edits — commit only
The atomic commit lands here, AFTER the full validation (§08.6) and matrix verification (§08.7) have passed. Splitting validation from commit gives us a clean rollback point: if §08.8 encounters a last-minute issue (e.g., pre-commit hook catches a new problem), we can rollback via §08.N’s rollback strategy without losing §08.6/§08.7 work.
-
Pre-commit worktree cleanliness gate (Phase 2 LEAK finding, 2026-04-11):
git add -Ais banned for this commit — the §08 sweep is broad enough (230+ files) that an indiscriminate stage would absorb any concurrent edit and bury it in the reorganization commit. Before staging, verify the working tree contains ONLY §08’s expected edits:cd /home/eric/projects/ori_lang # Expected dirty paths for §08: # - 8 renamed scripts (R clippy-all.sh -> scripts/dev/clippy-all.sh, etc.) # - 4 new root wrappers (A test-all.sh, full-check.sh, fmt-all.sh, build-all.sh) # - 16+ moved diagnostic scripts (R diagnostics/*.sh -> scripts/diagnostics/*.sh) # - 1 new diagnostic helper (A scripts/diagnostics/_repo-root.sh) # - 7 edited diagnostic scripts (M scripts/diagnostics/_common.sh, check-debug-flags.sh, etc.) # - 1 edited compiler source comment (M compiler/oric/src/llvm_dump/mod.rs) # - Pattern A/D/E sweep edits: CLAUDE.md, CONTRIBUTING.md, .claude/**, .codex/**, # plans/**, docs/**, .claude/rules/diagnostic.md, lefthook.yml, scripts/release.sh # - compiler/ori_llvm/tests/aot/util/aot.rs (Pattern D target) # - plans/project-reorganization/section-08-script-consolidation.md (plan sync) # - plans/project-reorganization/00-overview.md (status updates) # - plans/project-reorganization/index.md (status updates) git status --porcelain > /tmp/section-08-git-status.txt wc -l /tmp/section-08-git-status.txt # Expected: ~240+ lines (huge sweep) # Negative check — ensure NO files outside the expected scope are dirty. # The allow-list for §08 is very broad, so the check is: no unexpected # compiler/ changes beyond llvm_dump/mod.rs, no library/ changes, no tests/ # changes beyond compiler/ori_llvm/tests/aot/util/aot.rs, no .git/ or target/. git status --porcelain \ | grep -E '^.. compiler/' \ | grep -v ' compiler/oric/src/llvm_dump/mod\.rs$' \ | grep -v ' compiler/ori_llvm/tests/aot/util/aot\.rs$' \ || echo "CLEAN — compiler/ changes are scoped to the expected 2 files" git status --porcelain | grep -E '^.. library/' \ || echo "CLEAN — library/ untouched" git status --porcelain | grep -E '^.. tests/' \ | grep -v ' tests/.*/(cow/)?(baseline\.json|valgrind|spec)' \ || echo "CLEAN — tests/ untouched (or only pre-existing baselines)"- All three checks produce “CLEAN — …” messages
- If any unexpected file: STOP, investigate, decide whether it’s (a) a legitimate sweep target missed in enumeration (update allow-list) or (b) a concurrent edit (shelve before committing §08)
-
Commit everything in §08 atomically with EXPLICIT per-file path staging (no
git add -A, nogit add <directory>/).Why explicit per-file staging (TPR-XX-001-codex + TPR-XX-002-gemini fix, 2026-04-11): Both directory-wide adds (
git add .claude/) and awk-based glob adds absorb unrelated concurrent work. The correct allow-list is the set of files §08.5’s sweep patterns ACTUALLY touched — which we already captured intarget/sweep-pattern-a-files.txt,target/sweep-pattern-d-files.txt, andtarget/sweep-pattern-e-files.txtduring Step E.2/E.3. Reuse those files as the staging source of truth. Any file NOT in those lists is either (a) unchanged, or (b) concurrent dirt that must NOT be absorbed into the §08 commit.cd /home/eric/projects/ori_lang # 1. Script moves — already staged by git mv in 08.2, 08.3, 08.4 (explicit, no globs) git add -u clippy-all.sh llvm-build.sh llvm-clippy.sh llvm-test.sh \ test-all.sh full-check.sh fmt-all.sh build-all.sh 2>/dev/null || true # 2. New content — scripts/dev/ (wrapper targets + cleanly-moved files) git add scripts/dev/clippy-all.sh scripts/dev/llvm-build.sh \ scripts/dev/llvm-clippy.sh scripts/dev/llvm-test.sh \ scripts/dev/test-all.sh scripts/dev/full-check.sh \ scripts/dev/fmt-all.sh scripts/dev/build-all.sh # 3. New root wrappers (from 08.3) git add test-all.sh full-check.sh fmt-all.sh build-all.sh # 4. Diagnostic moves (from 08.4 — explicit per-file, no glob, no directory-wide add). # TPR-XX-001-codex iteration 2 fix — fixtures/ sub-adds were the last remaining # directory-level stage. Generate an explicit fixture file list from the pre-move # state and stage per-file. git add -u diagnostics/_common.sh diagnostics/arc-dump.sh diagnostics/bisect-passes.sh \ diagnostics/check-debug-flags.sh diagnostics/codegen-audit.sh \ diagnostics/debug-release-compare.sh diagnostics/diagnose-aot.sh \ diagnostics/disasm-ori.sh diagnostics/dual-exec-debug.sh \ diagnostics/dual-exec-verify.sh diagnostics/ir-diff.sh diagnostics/ir-dump.sh \ diagnostics/rc-stats.sh diagnostics/self-test.sh diagnostics/valgrind-aot.sh \ diagnostics/README.md 2>/dev/null || true # fixtures: enumerate each file explicitly (no git add -u diagnostics/fixtures/) # Generate the list ONCE at §08.1 via `find diagnostics/fixtures -type f > target/sweep-pattern-fixtures-files.txt` if [ -s target/sweep-pattern-fixtures-files.txt ]; then xargs -a target/sweep-pattern-fixtures-files.txt git add -u -- 2>/dev/null || true fi # Staged new locations (post-move) — explicit per-file git add scripts/diagnostics/_common.sh scripts/diagnostics/arc-dump.sh \ scripts/diagnostics/bisect-passes.sh scripts/diagnostics/check-debug-flags.sh \ scripts/diagnostics/codegen-audit.sh scripts/diagnostics/debug-release-compare.sh \ scripts/diagnostics/diagnose-aot.sh scripts/diagnostics/disasm-ori.sh \ scripts/diagnostics/dual-exec-debug.sh scripts/diagnostics/dual-exec-verify.sh \ scripts/diagnostics/ir-diff.sh scripts/diagnostics/ir-dump.sh \ scripts/diagnostics/rc-stats.sh scripts/diagnostics/self-test.sh \ scripts/diagnostics/valgrind-aot.sh scripts/diagnostics/README.md \ scripts/diagnostics/_repo-root.sh # fixtures new locations (post-move) — rewrite the fixture list paths to their # new scripts/diagnostics/ prefix and stage if [ -s target/sweep-pattern-fixtures-files.txt ]; then sed 's|^diagnostics/fixtures/|scripts/diagnostics/fixtures/|' target/sweep-pattern-fixtures-files.txt \ | xargs git add -- fi # 5. Pattern A/D/E sweep targets — stage ONLY files the sweep actually touched, # using the per-pattern file lists generated in §08.5 as the authoritative source. # This is the LEAK fix: no glob adds, no directory adds, no awk scans. if [ -s target/sweep-pattern-a-files.txt ]; then xargs -a target/sweep-pattern-a-files.txt git add -- fi if [ -s target/sweep-pattern-d-files.txt ]; then xargs -a target/sweep-pattern-d-files.txt git add -- fi if [ -s target/sweep-pattern-e-files.txt ]; then xargs -a target/sweep-pattern-e-files.txt git add -- fi if [ -s target/sweep-pattern-e9-files.txt ]; then xargs -a target/sweep-pattern-e9-files.txt git add -- fi # 6. Step E.4 manual Rust comment edit (not captured by any pattern file) git add compiler/oric/src/llvm_dump/mod.rs # 7. Plan sync edits — explicit per-file git add plans/project-reorganization/00-overview.md \ plans/project-reorganization/index.md \ plans/project-reorganization/section-08-script-consolidation.md # Sanity check — show staged count git status --short | wc -l git status --short | head -40- Staged count matches expected scope (~240+ files from the sweep, plus scripts/moves/wrappers/helper + plan sync)
-
Post-stage catch-all gate (TPR-XX-002-gemini fix, 2026-04-11). Verify NO unstaged changes remain — if any appear, they’re either a missed sweep target (must be added to staging) or concurrent dirt (must be shelved before commit). This guards the gap where Pattern A/D could touch a file in
.github/,docker/,editors/, or other directory NOT anticipated by explicit staging:unstaged=$(git diff --name-only) if [ -n "$unstaged" ]; then echo "FAIL: unstaged changes remain after explicit staging:" echo "$unstaged" echo "" echo "Classify each:" echo " - If it's a missed sweep target (rg pattern hit but not in target/sweep-pattern-*.txt):" echo " → add to the corresponding pattern file and re-stage" echo " - If it's concurrent dirt (unrelated to §08 work):" echo " → git stash or git restore to defer it before committing §08" exit 1 else echo "OK: catch-all gate PASS — zero unstaged changes remain" fi- Catch-all gate prints “OK: catch-all gate PASS”
- If FAIL: do NOT proceed to commit. Resolve the unstaged changes first per the classify protocol above.
-
Commit the atomic §08 change:
git commit -m "refactor(plan): consolidate dev scripts into scripts/dev/ + atomic reference sweep
Option C hybrid script model (per 00-overview.md Design Principle 1):
Cleanly moved (4 scripts, root → scripts/dev/):
- clippy-all.sh, llvm-build.sh, llvm-clippy.sh, llvm-test.sh
Hot-path wrapped (4 scripts, real impl in scripts/dev/ + 3-line wrapper at root):
- test-all.sh, full-check.sh, fmt-all.sh, build-all.sh (wrappers are PERMANENT — they preserve muscle memory for daily-use scripts while consolidating implementation. They are NOT temporary shims.)
Diagnostics moved (diagnostics/ → scripts/diagnostics/):
- 16 debug scripts + fixtures/ + README.md
Preserved at root (unchanged):
- install.sh (PUBLIC_API — codex-verified external surface)
- setup.sh (semi-public, CONTRIBUTING.md:14)
Atomic reference sweep across the blast radius:
- Pattern A: 175 refs to ./clippy-all.sh → ./scripts/dev/clippy-all.sh
- Pattern D: 30 refs to ./llvm-test.sh → ./scripts/dev/llvm-test.sh
- Pattern E: ~30 refs to diagnostics/ → scripts/diagnostics/
- Hot-path 4 refs (503+7+29+2=541) unchanged — wrappers preserve the paths
Files touched: CLAUDE.md, CONTRIBUTING.md, all .claude/rules/.md, all .claude/skills//SKILL.md, .claude/commands/*.md, .codex/skills/, plans/** (597+ refs), docs/compiler/design/appendices/, compiler/ori_llvm/tests/aot/util/aot.rs, scripts/release.sh, .claude/rules/diagnostic.md (§Diagnostic Scripts table + header).
Verified: ./test-all.sh green (via wrapper), lefthook pre-commit passes, full-check chain (wrapper → scripts/dev/full-check → $SCRIPT_DIR/clippy-all
- test-all) resolves correctly.
Refs: plans/project-reorganization/section-08-script-consolidation.md”
- [ ] **Verify commit landed**:
```bash
git log --oneline -1
git show --stat HEAD | head -60
-
~240+ file changes in one commit (8 script moves + 4 new wrappers + ~225 sweep edits)
-
Final sanity check — everything still works:
timeout 150 ./test-all.sh 2>&1 | tail -10- Exit 0, all phases PASS
-
Subsection close-out (08.8) — MANDATORY before 08.R:
- Atomic commit landed with ~240+ file changes
- Post-commit test-all.sh green
- Update this subsection’s
statusin section frontmatter tocomplete - Run
/improve-toolingretrospectively on THIS subsection — the explicit-path staging pattern was mechanical but error-prone. Ascripts/dev/stage-section-paths.sh <section-file>helper that parses the section’s “File(s):” frontmatter and stages only those paths would be broadly useful for every future large-atomic-commit section. Build if time permits; otherwise file as/add-bug. Document: “Retrospective 08.8: explicit path staging is mandatory per LEAK finding — helper would automate it; filed for follow-up.” - Run
/sync-claudeon THIS subsection — check whether code changes invalidated any CLAUDE.md,.claude/rules/*.md, orcanon.mdclaims. If no API/command/phase changes, document briefly. Fix any drift NOW.
08.R Third Party Review Findings
-
[TPR-XX-001-codex][high]plans/project-reorganization/section-08-script-consolidation.md:1341— Remove the LEAK in atomic staging scope. Evidence: The cleanliness gate at lines 1276-1316 only excluded unexpected changes undercompiler/,library/,tests/, but the staging block at lines 1341-1356 usedgit add .claude/+git add .codex/+git add .gemini/(directory-wide adds) plus awk-based globs for plans/ and docs/. Livegit status --shortshowed unrelated dirty files at.claude/skills/continue-roadmap/SKILL.md,plans/llvm-verification-tooling/section-03-aims-snapshots.md,plans/test-suite-health/section-03-profiling-infrastructure.md— all would have been absorbed into the §08 atomic commit. Impact: The “atomic” commit would not actually be atomic — it could pull in concurrent contributor work, recreating the LEAK that Phase 3 was trying to eliminate. Required plan update: Narrow staging to an explicit per-file allow-list derived from the paths Pattern A/D/E actually touched. Usetarget/sweep-pattern-{a,d,e,e9}-files.txtas the staging source of truth. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-11. Rewrote §08.8 staging to usexargs -a target/sweep-pattern-a-files.txt git add --(and similarly for Pattern D/E/E9). Removed allgit add .claude//git add .codex//git add .gemini/directory adds. Removed the awk-based globs for plans/ and docs/. Every staged path is now an explicit filename derived from §08.5’s own sweep file lists. -
[TPR-XX-002-gemini][high]plans/project-reorganization/section-08-script-consolidation.md:1318— Stage unbounded Pattern A and D sweep targets in atomic commit. Evidence: Pattern A and D run globally viarg -l. If they modify files in.github/,docker/,editors/, or any other directory outside the §08.8 explicit staging list, those files remain unstaged. The negative check only guardedcompiler/,library/, andtests/, silently leaving unstaged changes in other scopes. Impact: Unstaged sweep changes would be excluded from the atomic commit, breaking both the atomicity and the post-commit worktree cleanliness guarantee. Required plan update: Add a post-stage catch-all gate (git diff --name-only | grep . && echo 'FAIL: unstaged changes remain'). Basis: inference. Confidence: high. Resolved: Fixed on 2026-04-11. Added a post-stage catch-all gate to §08.8 that runsgit diff --name-onlyafter explicit staging and fails if ANY unstaged changes remain. The gate prints a classification protocol: missed sweep target → add to pattern file and re-stage; concurrent dirt →git stashbefore commit. This also composes with the TPR-XX-001-codex fix: together they eliminate both sides of the LEAK (over-broad staging + missed staging). -
[TPR-XX-002-codex][medium]plans/project-reorganization/section-08-script-consolidation.md:224— Close the GAP in wrapper PATH invocation. Evidence: The wrapper template at line 224 usesSCRIPT_PATH="$(readlink -f "$0")". This resolves symlinks when$0is a real path, but does NOT resolve through PATH when$0is a bare name. Fresh probes showedbash -c 'readlink -f "$0"' test-all.shfromcompiler/resolves to/home/eric/projects/ori_lang/compiler/test-all.sh(nonexistent). §08.7’s matrix had no PATH cell. Impact: The wrapper recipe was not robust against PATH-based invocation (aliases, PATH-exported dev paths). Any PATH-style launch from outside the repo root would exec a nonexistentscripts/dev/...target. Required plan update: Either declare PATH invocation unsupported and document the limitation, or change the wrapper to resolve the executable via PATH before callingreadlink -f. Add a PATH cell to §08.7. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-11. Added a block comment to §08.3 wrapper template explicitly declaring PATH invocation as UNSUPPORTED with the rationale (adding PATH resolution would add complexity for an edge case;./test-all.shfrom repo root is the canonical invocation pattern). Added a new matrix cell §08.7 C9 for PATH invocation (4 cells: test-all, full-check, fmt-all, build-all) marked as SKIP — unsupported per the documented limitation, with a verification command that confirms the wrapper fails when invoked via PATH from outside the repo root. Matrix completeness updated to 33 cells (29 exercisable + 4 SKIP). -
[TPR-XX-001-gemini][high]plans/project-reorganization/section-08-script-consolidation.md:834— Add scripts/ directory to Pattern E allow-list. Evidence: The explicit allow-list for Pattern E omittedscripts/entirely. If any existing script outsidediagnostics/(e.g.scripts/release.shorscripts/perf-baseline.sh) invoked a diagnostic script, the reference would not have been updated and the script would break after §08.4’s move. Impact: Missed references inscripts/would cause those scripts to fail when attempting to invoke moved diagnostic scripts. Required plan update: Addfind scripts -maxdepth 1 -type f \( -name '*.sh' -o -name '*.py' \)to the Pattern E allow-list generation in Step E.2. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-11. Addedscripts/top-level*.shand*.pyfiles to the Pattern E allow-list (Step E.2 generation command), with EXCLUSION ofscripts/dev/andscripts/diagnostics/(the former has nodiagnostics/refs by construction; the latter is handled by the new Step E.9 for self-references). Added a defensive check that the allow-list does NOT leakscripts/dev/orscripts/diagnostics/paths. Current verification (2026-04-11) showed zerodiagnostics/references inscripts/*.sh/scripts/*.py, but the defensive inclusion means any future addition auto-sweeps. -
[TPR-XX-004-codex][medium]plans/project-reorganization/section-08-script-consolidation.md:800— Sweep the remaining DRIFT in diagnostics self-references. Evidence: Pattern E’s allow-list explicitly excludedscripts/diagnostics/to avoid scope creep, so moved diagnostic scripts andREADME.mdwould never be swept. The current files contain many legitimate self-references that would go stale:diagnostics/README.md:30-35, 43-58, 165-183,diagnostics/ir-dump.sh:5 and 86,diagnostics/self-test.sh:5 and 11. Impact: After §08, the canonical diagnostics home would change toscripts/diagnostics/but the moved scripts and README would still instruct users to rundiagnostics/.... Documentation and--helpsurfaces would be out of sync with the new canonical path. Required plan update: Add a separate post-move sweep scoped toscripts/diagnostics/**. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-11. Added Step E.9 (Post-move sweep of scripts/diagnostics/ self-references) to §08.5 with explicit scoping toscripts/diagnostics/**, the same regex patterns as Pattern E, a negative-pin (Step E.10) that verifies the E.9 sweep did NOT escapescripts/diagnostics/. Thetarget/sweep-pattern-e9-files.txtoutput is used as an explicit staging source in §08.8’s per-file add loop.
-
[TPR-XX-001-codex][high](iteration 2)plans/project-reorganization/section-08-script-consolidation.md:1441,1451— Remove the remaining LEAK in §08.8 directory staging. Evidence: Iteration 1’s staging fix was incomplete. §08.8 still hadgit add -u diagnostics/fixtures/(line 1441) andgit add scripts/diagnostics/fixtures/(line 1451) — two directory-level adds that iteration 1 missed because they handle thefixtures/subdirectory. Both violated the “nogit add <directory>/” contract that iteration 1 established. Impact: The §08 atomic commit could still absorb concurrent or accidental changes fromdiagnostics/fixtures/during the execution window — the original LEAK risk survived through a narrower but still non-canonical staging path. Required plan update: Replace both directory adds with explicit per-file staging via a newtarget/sweep-pattern-fixtures-files.txtenumeration generated at §08.1. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-11 iteration 2. Updated §08.1 to generatetarget/sweep-pattern-fixtures-files.txtviafind diagnostics/fixtures -type f > target/sweep-pattern-fixtures-files.txtas part of the directory-setup step. Updated §08.8 staging to read the list withxargs -a ... git add -u --(pre-move paths) AND asedpass that rewritesdiagnostics/fixtures/toscripts/diagnostics/fixtures/for the post-move add. Every fixture is now staged per-file. -
[TPR-XX-001-gemini][high](iteration 2)plans/project-reorganization/section-08-script-consolidation.md:multiple— Store sweep file lists in the repository instead of the temporary directory. Evidence: Step 08.5 wrote sweep targets to/tmp/sweep-pattern-a-files.txtetc. and Step 08.8 read them for git staging. Between those two steps, manual verification (08.6 and 08.7) happens. If the system reboots,/tmpis cleared, or another process cleans/tmpduring manual verification, the sweep file lists are permanently lost — but the sed rewrites have already happened. Step 08.8 would then silently skip staging those files (empty xargs input), resulting in a broken half-applied state that’s hard to recover from. Impact: Fragile cross-step state. A hardware reboot during manual verification turns a successful sweep into a permanently broken plan execution. Required plan update: Move all/tmp/sweep-pattern-*.txtfiles to a persistent repo-scoped path that survives reboot.target/is gitignored and persistent untilcargo clean— the natural home. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-11 iteration 2. Usedsed -i 's|/tmp/sweep-pattern-|target/sweep-pattern-|g'to replace all 37 references across §08.5, §08.8, and §08.9 from/tmp/sweep-pattern-*.txttotarget/sweep-pattern-*.txt. Updated §08.1 to ensuretarget/exists before §08.5 writes to it (cargo build dir already exists in practice, but the explicitmkdir -p targetmakes the dependency load-bearing). -
[TPR-XX-002-gemini][medium](iteration 2)plans/project-reorganization/section-08-script-consolidation.md:908— Update Pattern E to match paths enclosed in double quotes. Evidence: Iteration 1’s Pattern E Step E.5 had sed patterns for backtick (`),./, space-prefix, and start-of-line boundaries. Missing: double-quoted path literals like"diagnostics/arc-dump.sh"which appear in JSON fixtures, code blocks with quoted path arguments, and schema examples. These would have been left untouched by Pattern E’s sweep and drift from the canonicalscripts/diagnostics/path. Impact: Documentation examples and JSON fixtures containing"diagnostics/"would drift silently from the canonical paths, causing confusion for developers reading TPR schema examples or test fixtures. Required plan update: Add a fifth sed pattern for double-quoted paths. Basis: direct_file_inspection. Confidence: high. Resolved: Fixed on 2026-04-11 iteration 2. Addedsed -i 's|"diagnostics/|"scripts/diagnostics/|g' "$file"as the 5th sed pattern in Step E.5. Also added the same double-quoted pattern to Step E.9’s scoped self-reference sweep (scripts/diagnostics/**) so JSON fixtures inside the moved scripts are handled consistently. -
[TPR-XX-003-codex][medium]+[TPR-XX-003-gemini][medium](iteration 2)plans/project-reorganization/section-08-script-consolidation.md:1120,1220— Close the GAP between §08.7 matrix claims and executable verification. Evidence (codex): §08.7 claimed C3 is a 4-cell wrapper matrix, but the commands only exercised../test-all.shand../fmt-all.sh(lines 1225-1226), notfull-check.shorbuild-all.sh. It also defined C7 as “live test-code invocation” atcompiler/ori_llvm/tests/aot/util/aot.rs:168, but the actual source at that line is a panic message string literal that mentions./llvm-test.shas a help hint, NOT a runtime script invocation. The matrix verification was overstated and partially non-runnable. Evidence (gemini): The “Cells that apply per script type” rule stated C8 applies to both wrapper and cleanly-moved scripts (implying 8 cells total), and C3 applies to both. But the actual C3 implementation was “4 cells — wrappers only” and C8 implementation was “wrapper exit-code propagation, 1 cell” — the documentation claimed coverage the implementation didn’t deliver. Impact: §08 could not actually prove the invocation surface it claimed to clamp. Executors would either skip the missing cells (undercount) or stall trying to execute nonsensical cells (cleanly-moved scripts have no wrapper to test exit-code forwarding through). Required plan update: Reconcile matrix documentation with implementation. Add missing C3 cells for full-check and build-all; reclassify C7 as a pattern-check (not invocation); fix the “applies to both” rule for C8 to say “wrappers only, 1 representative test”. Basis: fresh_verification (codex) + direct_file_inspection (gemini). Confidence: high. Resolved: Fixed on 2026-04-11 iteration 2. (a) Added the 2 missing C3 cells for../full-check.shand../build-all.sh— C3 now exercises all 4 wrappers from a subdirectory. (b) Reclassified C7 from “live test-code invocation” to “test-code error-message string literal” with updated verification (grep for Pattern D’s post-sweep form +cargo checkto verify syntax) — the Rust file at aot.rs:168 is a panic-message string, verified by reading the actual source. (c) Rewrote the “Cells that apply per script type” rule block to match implementation reality: C3 applies to wrappers only (C3 does NOT apply to cleanly-moved scripts); C8 applies to wrappers only and is 1 representative test (the thin wrapper template is identical across all 4, so one test is load-bearing for all). Updated the matrix completeness totals description with the full breakdown (C1=4, C1-neg=4, C2=8, C3=4, C4=4, C5=2, C6=1, C7=1, C8=1, C9=4 SKIP = 33 total).
-
[TPR-XX-004-codex][medium](iteration 3)plans/project-reorganization/section-08-script-consolidation.md:27,1193,1690— DRIFT: Propagate the 33-cell matrix contract through §08 metadata. Evidence: Iteration 2 fixed the executable matrix body to 33 cells at §08.7 (“33 total”, “29 exercisable + 4 SKIP”), but the frontmatter success criterion at line 27 still said “all 29 cells”, the C3 context row at line 1193 still showedcd compiler && ../../<script>.sheven though the actual commands use../<script>.sh, and the completion checklist at line 1690 still said “all 29 cells”. The section contract, matrix legend, and close-out checklist no longer agreed with the implementation. Impact: Reviewers couldn’t tell whether a 33-cell run was the intended end state or whether C3 was being exercised correctly. Required plan update: Update the frontmatter, the C3 context row, and §08.N to the final matrix shape: 33 total cells, 29 exercisable plus 4 PATH SKIPs, with C3 documented ascd compiler && ../<script>.sh. Basis: fresh_verification. Confidence: high. Resolved: Fixed on 2026-04-11 iteration 3. Updated frontmatter success_criteria line 27 to “all 33 cells (29 exercisable + 4 PATH SKIP)” with the C3 + C9 skip rationale. Updated C3 context row in the matrix legend fromcd compiler && ../../<script>.shtocd compiler && ../<script>.shto match the actual §08.7 C3 implementation. Updated §08.N completion checklist line to match the 33-cell shape. All three metadata locations now agree with the §08.7 body.
08.N Completion Checklist
- 4 cleanly-moved scripts in
scripts/dev/(08.2) - 4 hot-path scripts in
scripts/dev/with wrappers at root (08.3) -
diagnostics/→scripts/diagnostics/(08.4) with canonical_repo-root.shhelper + 9 repo-root resolution patches (Finding 1 fix) - Reference sweep patterns A, B, C, D, E all applied (08.5) with explicit allow-list for Pattern E (Finding 2 fix)
- Hot-path refs preserved (wrappers work)
- Cleanly-moved refs updated (zero stale)
-
diagnostics/path refs updated toscripts/diagnostics/ -
compiler/oric/src/llvm_dump/mod.rs:8comment updated (manual edit, Pattern E Step E.4) - CLAUDE.md §Commands section accurate
- CONTRIBUTING.md accurate
-
.claude/rules/diagnostic.md§Diagnostic Scripts accurate - compiler/ori_llvm/tests/aot/util/aot.rs updated if needed
- Invocation matrix (08.7) complete — all 33 cells (29 exercisable + 4 PATH SKIP) PASS, SKIP (documented), or fail as documented limitation (C3 PWD for cross-dir wrappers; C9 unsupported for PATH)
-
./test-all.shgreen post-sweep (via wrapper) -
./scripts/dev/test-all.shgreen (direct) -
./full-check.shgreen (wrapper → internal chain) -
lefthook run pre-commitgreen - Single atomic commit lands:
refactor(plan): consolidate dev scripts into scripts/dev/ + atomic reference sweep -
git diff --name-only HEAD~1 HEAD | grep '^compiler/'shows ONLYcompiler/oric/src/llvm_dump/mod.rsandcompiler/ori_llvm/tests/aot/util/aot.rs— NO othercompiler/files touched by the sweep (Finding 2 negative-pin regression guard) - Plan annotation cleanup: N/A (no
.rsfiles modified beyond the two explicit edits) - Plan sync — update plan metadata:
- This section’s frontmatter
status→complete -
00-overview.mdQuick Reference table for §08 →Complete -
00-overview.mdmission success criteria “Zero stale root references”, “Lefthook pre-commit passes”, “./test-all.shgreen”, “Hot-path 4 muscle memory preserved” all → checked -
index.mdupdated
- This section’s frontmatter
-
/tpr-reviewpassed — CRITICAL PATH section; review should check: (a) all wrappers exec correctly, (b) no sweep pattern corrupted unintended files (Pattern E allow-list held), (c) lefthook still works, (d) internal $SCRIPT_DIR chain resolves, (e) hot-path preservation works (no regression in daily-use workflows), (f) CI still passes (but CI doesn’t use root scripts so this is guaranteed), (g) diagnostic scripts find repo root correctly from new location -
/impl-hygiene-reviewpassed (AFTER TPR clean) — SSOT (each script has exactly one canonical implementation;_repo-root.shis the single source for repo-root derivation), No Side Logic (wrappers are thin exec-forwarders with no logic), DRIFT (sweep caught everything) -
/improve-toolingsection-close sweep — per-subsection retrospectives (08.1-08.8) should already be committed. Cross-subsection pattern check: theatomic-sweep.shhelper from 08.5 is the most valuable contribution. Verify it was built. Also check: did 08.3’s wrapper template benefit from generating the 4 wrappers programmatically? Probably not — 4 is few enough to hand-write. The big-ticket tooling wins from §08 are:_repo-root.sh(canonical repo-root resolver, broadly reusable across scripts/),atomic-sweep.sh(from 08.5),verify-invocation-matrix.sh(from 08.7). If not built: STOP and build them now. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.
Rollback strategy (Phase 2 Finding 6, 2026-04-11)
§08 touches 230+ files and is the largest single commit in the plan. If post-commit verification fails catastrophically (compiler source corrupted, cargo check broken, ./test-all.sh fails in ways that don’t have obvious 2-minute fixes), “fix forward” is NOT acceptable — per CLAUDE.md §Stabilization Discipline, interference is best handled by reorder-don’t-skip, and mass-corruption is the strongest interference signal.
Rollback triggers (any one of these):
cargo check --workspacefails with errors in any crate that was not expected to be touchedcargo clippy --workspace --all-targetsfails with new errors./test-all.shfails with errors that trace to §08’s edits (path resolution, missing files, script-not-found)git diff compiler/ --statshows any file modified other than the two pre-approved (llvm_dump/mod.rs,aot/util/aot.rs)git diff library/ --statis non-emptylefthook run pre-commitfails in a way that’s not a pre-existing issue
Rollback procedure:
cd /home/eric/projects/ori_lang
# 1. Revert the §08 commit — the commit is the atomic unit, so HEAD~1 is pre-§08 state
git reset --hard HEAD~1
# 2. Clean any untracked files the sweep created (e.g., scripts/diagnostics/_repo-root.sh)
git clean -fd
# 3. Verify baseline state restored
./test-all.sh 2>&1 | tail -10
# Expected: green (same as pre-§08 state)
cargo check --workspace 2>&1 | tail -5
# Expected: compiles cleanly
ls scripts/dev/ 2>&1 || echo "no scripts/dev/ (expected — §08 rolled back)"
ls diagnostics/ 2>&1 | head -5
# Expected: diagnostics/ restored to pre-§08 location
# 4. File a bug for the failure mode — this bug BLOCKS re-running §08 until fixed
# Run /add-bug with:
# title: "§08 reorg failure mode: <specific symptom>"
# severity: high
# repro: "re-apply §08 per plan → reproduce the exact failure observed"
# subsystem: project-reorganization
# 5. Investigate root cause — which step of §08 produced the corruption?
# Common causes:
# - Pattern E allow-list leaked (fix: tighten allow-list in §08.5)
# - Diagnostic repo-root resolver broken (fix: audit _repo-root.sh logic)
# - Wrapper template broken (fix: update template in §08.3)
# - Concurrent edit absorbed (fix: stronger worktree gate in §08.8)
# 6. Update §08 with the lesson learned, amend the plan, then retry from the
# appropriate subsection (NOT §08.1 — only the failed subsection needs redo)
Rollback is NOT acceptable for: minor cosmetic issues, test timeout due to slow machine, rg/grep output formatting differences, known-pre-existing test failures. Rollback is the nuclear option — use it only for actual corruption.
After rollback: §08 must be retried from the beginning of the affected subsection. Do NOT attempt to “fix the partial commit in place” — the whole point of atomic commits is that they are either fully landed or fully reverted.
Exit Criteria: ls /home/eric/projects/ori_lang/ shows exactly 2 public scripts at root (install.sh, setup.sh) plus the 4 hot-path wrappers (test-all.sh, full-check.sh, fmt-all.sh, build-all.sh) — 6 .sh files total, down from 11. scripts/dev/ contains 8 internal scripts. scripts/diagnostics/ contains 16+ debug scripts plus _repo-root.sh. diagnostics/ absent. All 747 references either correctly point to new paths (cleanly-moved) or correctly preserved via wrappers (hot-path). ./test-all.sh, ./full-check.sh, lefthook run pre-commit all green. Single atomic commit documents the entire reorganization’s final-form. Mission success criteria #2, #3, #5, #11 satisfied. Rollback strategy documented for catastrophic failure modes.