T-LDWC-package-removal-sweeps-untracked-residue
Status: open/ready · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-20 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
A package removal that only runs git rm -r <dir> leaves gitignored,
untracked residue on disk, so glob-based project discovery keeps finding the
removed package and any acceptance criterion asserting its disappearance
fails. This repo needs a documented — and where possible mechanized —
package-removal recipe that pairs git rm -r <dir> with an untracked-residue
sweep, plus the companion deletions-only lockfile assertion that proves the
removal re-resolved additively. Originating from
T-J5DI-remove-clear-duplicate-augmented-packages in
sksizer/dev.
During T-J5DI,
git rm -r packages/ts/renderleft a gitignored node_modules/ residue behind, so the directory still existed on disk and moon’spackages/ts/*glob kept discovering the project — the AC assertingmoon query projectsno longer listed it failed until a manualrm -rfcleared it.git rmalone does not satisfy a glob-discovery assertion. Document (and where possible mechanize) a package-removal recipe that pairsgit rm -r <dir>with an untracked-residue sweep, and note the deletions-only lockfile assertion (zero additions across bun.lock and Cargo.lock) as the companion proof that a removal re-resolved additively. Touchpoints: the SDLC conventions directory apps/sdlc/conventions/ for the recipe, and apps/sdlc/skills/task-work/SKILL.md if the sweep is briefed to the implementation sub-agent.
| Location | Role today |
|---|---|
apps/sdlc/conventions/ | The canonical home for shared procedural prose that SKILL.md files reference instead of inlining (per apps/sdlc/skills/CLAUDE.md). Holds ten topic docs; none covers package removal |
justfile | The command catalog whose header calls its recipes “the deterministic tails skills and humans share”. Carries release-lib, fmt-markdown, check-markdown, dev, gen-dashboard-client — no removal recipe |
apps/sdlc/scripts/release-lib.ts | The reference shape for a just recipe backed by a Bun script under apps/sdlc/scripts/ |
apps/sdlc/lib/util/command.ts | The command seam (runChecked / spawnRunner) every TypeScript shell-out must route through, per the repo CLAUDE.md |
apps/sdlc/lib/services/git/easy.ts | The typed git client preferred over hand-built argv for git operations |
.moon/workspace.yml | Declares glob-based project discovery (apps/*, packages/ts/*, packages/rust/*, sites/*, tools/*). A directory that still exists on disk keeps matching these globs regardless of git tracking state — the mechanism that made T-J5DI’s AC fail |
apps/sdlc/skills/task-work/SKILL.md | The implementation skill. Step 6 briefs the implementation sub-agent; its ## Notes section is where cross-skill conventions are surfaced as one-line references |
bun.lock | Bun workspace lockfile — one half of the deletions-only assertion |
Cargo.lock | Cargo workspace lockfile — the other half |
Proposed
Section titled “Proposed”A package removal is one documented recipe with a mechanized tail. The
convention doc apps/sdlc/conventions/package-removal.md states the three
phases (tracked delete, untracked-residue sweep, deletions-only lockfile
assertion); just remove-package <dir> executes them; and
apps/sdlc/skills/task-work/SKILL.md points an implementer at the convention
from its ## Notes block so a removal task never re-derives the recipe.
Approach
Section titled “Approach”- Write the convention. Create
apps/sdlc/conventions/package-removal.mdin the house style of the existing conventions (title, why-it-exists, contract, phases). Document three phases in order:- Phase 1 — tracked delete.
git rm -r <dir>removes the tracked files only. - Phase 2 — untracked-residue sweep.
git clean -xdff -- <dir>followed byrm -rf <dir>clears gitignored/untracked residue (node_modules/,target/,dist/) and the now-empty directory itself. State the failure this prevents: glob-based discovery in.moon/workspace.ymlmatches directories that exist on disk, somoon query projectskeeps listing a package whose tracked files are gone. Cite T-J5DI’spackages/ts/rendercase as the motivating incident. - Phase 3 — deletions-only lockfile assertion. After re-resolving
(
bun install,cargo metadata --lockedorcargo check --locked), assertgit diff --numstat -- bun.lock Cargo.lockreports zero added lines. A nonzero additions column means the removal pulled new resolutions in rather than only dropping the removed package’s — the proof the removal re-resolved additively. - Also state the reference sweep: enumerate and remove every remaining citation of the removed path (manifest dependency entries, workspace member lists, docs, CI) before asserting.
- Phase 1 — tracked delete.
- Mechanize the recipe. Add
apps/sdlc/scripts/remove_package.ts— a Bun script taking one or more package directories plus a--base <ref>flag (defaultorigin/main) for the lockfile diff. It runs Phase 1 and Phase 2 per directory, then Phase 3 once across the lockfiles, printing a one-line-per-phase progress log and exiting nonzero on assertion failure. Support--dry-run(print the commands, mutate nothing). All shell-outs go throughrunChecked/spawnRunnerfromapps/sdlc/lib/util/command.ts, with git operations preferringapps/sdlc/lib/services/git/easy.ts; no rawchild_processimport. - Expose it as a deterministic tail. Add a
remove-packagerecipe tojustfilefollowing therelease-libshape — a doc comment naming the three phases, thenbun run apps/sdlc/scripts/remove_package.ts {{dirs}} {{flags}}. - Surface it to the implementer. Add one bullet to the
## Notessection ofapps/sdlc/skills/task-work/SKILL.mdpointing at${CLAUDE_PLUGIN_ROOT}conventions/package-removal.md, matching the existing reference-bullet shape used there forcommit-messages.mdandbranch-naming.md. Do not inline the recipe into the SKILL.md — the no-duplicate-prose rule inapps/sdlc/skills/CLAUDE.mdmakes the convention the single source of truth. - Verify. Exercise the script against a throwaway package directory
seeded with an untracked
node_modules/, confirm the directory is gone andmoon query projectsno longer lists it, then runjust check-markdown.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/conventions/package-removal.md | new | The three-phase package-removal recipe: tracked delete, untracked-residue sweep, deletions-only lockfile assertion |
apps/sdlc/scripts/remove_package.ts | new | Bun script mechanizing the three phases; shells out via the command seam |
justfile | modify | Add the remove-package recipe delegating to remove_package.ts |
apps/sdlc/skills/task-work/SKILL.md | modify | Add one ## Notes bullet referencing the new convention doc |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
apps/sdlc/conventions/package-removal.mdexists and documents all three phases — agit rm -rtracked delete, an untracked-residue sweep, and the deletions-only lockfile assertion — naming.moon/workspace.ymlglob discovery as the reason the sweep is required. - AC-2:
just --listincludes aremove-packagerecipe, and that recipe’s body invokesapps/sdlc/scripts/remove_package.ts. - AC-3: Given a throwaway directory
packages/ts/scratch-removal/containing a tracked file and an untrackednode_modules/subtree, running the recipe against it leavestest ! -e packages/ts/scratch-removaltrue andmoon query projectsprinting noscratch-removalline. - AC-4:
grep -n "child_process" apps/sdlc/scripts/remove_package.tsreturns no match, and the script imports fromapps/sdlc/lib/util/command.tsand/orapps/sdlc/lib/services/git/easy.ts. - AC-5: The script exits nonzero when
git diff --numstat <base> -- bun.lock Cargo.lockreports a nonzero additions column, and exits 0 when the lockfile diff is empty or deletions-only. - AC-6:
apps/sdlc/skills/task-work/SKILL.mdcontains exactly one line matchingconventions/package-removal.md, inside its## Notessection, and the recipe’s prose is not duplicated into that file. - AC-7:
just check-markdownexits 0 on the branch.
Out of scope
Section titled “Out of scope”- Adding a lefthook / CI gate that blocks a commit when residue survives.
The recipe and its
justtail are on-demand; enforcement is a separate decision. - Removing any actual package. This task ships the recipe, not a removal.
- Teaching
sdlc quality runor themoontask graph about the sweep. - Changing the discovery globs in
.moon/workspace.ymlto ignore tracked-file-free directories.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-07-20 UTC from T-J5DI-remove-clear-duplicate-augmented-packages in sksizer/dev.