Skip to content
Back to skills

Housekeeping

ASecurity

Repair the numbering of a specs/ directory: give name-only specs a stable NNN id, retire terminal specs into .archive/ keeping their number, fix the cross-references the moves break, and optionally rebuild the index in number order. Moves files and commits. Operates on one directory at a time, so it works whether specs sit flat under specs/ or inside a track.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 25, 2026
ai-agentsgobashnodetestinggit

Works with

  • terminal

Security analysis

A100/100

Scanned September 25, 2026

npx -y skills add latere-ai/agent-skills --skill housekeeping --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Housekeeping?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Housekeeping
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/latere-ai-housekeeping/badge)](https://www.skillsdirectory.com/skills/latere-ai-housekeeping)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: housekeeping
description: Repair the numbering of a specs/ directory: give name-only specs a stable NNN id, retire terminal specs into .archive/ keeping their number, fix the cross-references the moves break, and optionally rebuild the index in number order. Moves files and commits. Operates on one directory at a time, so it works whether specs sit flat under specs/ or inside a track.
argument-hint: [--number-only | --index-only] [target-dir]
allowed-tools: Read, Grep, Glob, Edit, Write, Agent, Bash(ls *), Bash(git mv *), Bash(git add *), Bash(git restore *), Bash(git commit *), Bash(git status *), Bash(git log *), Bash(git push *), Bash(grep *), Bash(sed *), Bash(awk *), Bash(perl *), Bash(cat *), Bash(wc *), Bash(sort *), Bash(uniq *), Bash(cut *), Bash(basename *)
---

# Specs housekeeping

Bring one specs directory back to a single invariant: **every spec in it is
`NNN-name.md` in that directory's own number space; active specs stay in place,
terminal specs retire under `.archive/` but keep their number so `depends_on:`
frontmatter still resolves.** Then rebuild the index for that directory in
number order.

The scope is a *directory*, not a repo. Numbering is a per-directory reading
order that composes with track grouping: `specs/local/003-live-serve.md` is
both grouped and numbered, and `specs/local/003-…` and `specs/cloud/003-…` are
separate number spaces that do not collide. Whatever moves here, the dependency
graph is unaffected — `depends_on` paths are repo-root-relative and form one
DAG across the whole tree, so keeping numbers stable across an archive move is
what keeps that graph resolving.

`$ARGUMENTS` may contain a mode flag and/or a target directory:
- default (no flag): do both the numbering pass and the README rebuild.
- `--number-only`: number + archive + fix references; skip the README rebuild.
- `--index-only`: rebuild the README index only; assume numbering is already clean.
- a path: operate on that directory — either another repo's `specs/`, or a
  single track inside one (`specs/local/`).

This skill mutates files, renames with history, and commits. Work on `main` only
if that is the repo's convention (check `git log`); otherwise branch first.

## Step 0: Fix the scope

Decide which single directory to work on. A path in `$ARGUMENTS` names it
directly; otherwise use `specs/` itself. Glob that directory's own `*.md`
(not recursively — sub-directories are separate scopes, and child specs under a
`<parent>/` folder belong to their parent, not to this number space).

Then read the intent already in that directory:
- Some or all specs already carry an `NNN-` prefix → it has adopted numbering;
  proceed to make it consistent.
- No spec carries one → adopting numbering is a new convention, not a repair.
  Say so and confirm with the user before renaming anything.

A directory full of track sub-directories and no specs of its own has nothing
to number. Say which sub-directories are candidates and let the user pick one,
rather than recursing on your own.

If the repository, or a sibling repository you can read, already has a
`specs/README.md`, use it as the reference shape for the index. Otherwise use
this shape: a status-legend line plus one `| # | Spec | Status |` table ordered
by number, active rows linking to the root and archived rows into `.archive/`.

## Step 1: Inventory

Extract `(number, location, filename, status, title)` for every spec. Numbers
come from the `NNN` filename prefix; name-only files have none yet.

```bash
cd <repo>/specs
meta() { local loc="$1"; shift; for f in "$@"; do
  n=$(basename "$f" | grep -oE '^[0-9]+')
  t=$(grep -m1 '^title:' "$f" | sed 's/^title: *//; s/^"//; s/"$//')
  s=$(grep -m1 '^status:' "$f" | sed 's/^status: *//; s/ *$//')
  printf '%s\t%s\t%s\t%s\t%s\n' "${n:-NONE}" "$loc" "$(basename "$f")" "${s:-none}" "$t"
done; }
meta root    $(ls [0-9]*.md 2>/dev/null)      # numbered active
meta root    $(ls *.md 2>/dev/null | grep -vE '^[0-9]|^README')  # NAME-ONLY: the drift
meta archive $(ls .archive/[0-9]*.md 2>/dev/null)
```

The name-only list is the work. For each, read its frontmatter `status`.

## Step 2: Classify — terminal vs live

Status vocab varies by repo; map onto two buckets:

- **Terminal** (archive it): `complete`, `shipped`, `implemented`, `superseded`,
  `abandoned`, `archived`, `stale`, `deferred`. Also treat a `draft`/`drafted`
  spec that carries an **Outcome** section (shipped-but-status-stale) as
  terminal, and flip its status to `complete`, the lifecycle status for shipped
  work, when you move it.
- **Live** (keep at the root, just give it a number if it lacks one): `vague`,
  `drafted`/`draft` with no Outcome, `validated`, `testing`, `in-progress`.

When a name-only spec's status is ambiguous, read the body tail for an
`## Outcome` / "Done"/"shipped" signal before deciding. If still unclear, ask.

## Step 3: Assign numbers

Numbers are **stable IDs**; never renumber an existing spec. Find the highest
number in use across **both** the root and `.archive/`, and append from
`max+1` in created-date order (read each `created:` field). Created-order is
a good default because it keeps a superseding spec after the specs it
supersedes; contiguous same-family numbering is an equally fine alternative.

## Step 4: Cross-reference grep BEFORE renaming (the step that blocks)

A name-only spec is often a **cross-repo companion** — the same bare filename
exists in sibling repos, and its own frontmatter `depends_on`/`affects` may
point across `../`. Renaming can strand references. Grep the whole workspace
for every basename first:

```bash
cd <workspace-root>   # the dir that holds all sibling repos, e.g. ../
for b in <basename1> <basename2> ...; do
  echo "=== $b.md ==="
  grep -rIn --exclude-dir=node_modules --exclude-dir=.git "$b\.md" .
done
```

Triage the hits:
- **Same-repo hard links** — this repo's own frontmatter (`supersedes:`,
  `superseded_by:`, `depends_on:`), README rows, and internal doc links. **You
  must fix every one of these** in the rename commit.
- **Sibling-repo index/prose listings** (another repo's README pointing at
  `thisrepo/specs/name.md`). These are usually already stale ecosystem-wide and
  are that repo's housekeeping, not yours. Do **not** edit sibling repos; note
  them for the user instead. (If the ecosystem convention is that every repo
  numbers its own copy, your rename matches it — say so.)

## Step 5: Move with `git mv` and fix same-repo references

```bash
git mv name.md .archive/NNN-name.md   # history follows; one per file
```

Then, in the same working set, fix every same-repo reference found in Step 4:
- `superseded_by:` / `supersedes:` / `depends_on:` frontmatter paths that
  pointed at the old location (use the repo's path convention — usually
  repo-root-relative `specs/.archive/NNN-name.md`).
- README table rows and internal `docs/**` markdown links.
- Flip any shipped-but-`draft` status to `complete`.

Leave live-but-newly-numbered specs at the root: `git mv name.md NNN-name.md`.

## Step 6: Verify the moves (do this before committing)

```bash
# a) every same-repo depends_on / supersede target still resolves
for f in specs/[0-9]*.md specs/.archive/[0-9]*.md; do
  awk '/^(depends_on|supersedes):/{c=1;next} /^[a-z_]+:/{c=0}
       c&&/^ *- /{gsub(/^ *- */,"");gsub(/"/,"");print}
       /^superseded_by:/{v=$2;gsub(/"/,"",v);print v}' "$f" \
  | while read p; do case "$p" in specs/*) [ -f "$p" ] || echo "BROKEN $f -> $p";; esac; done
done
```
Any `BROKEN` line must be fixed before you commit. Optionally run
`/spec:validate` for the full document-model check.

## Step 7: Commit the numbering pass (atomic)

One commit for renames + cross-reference fixes, README excluded:

```bash
git add -A && git restore --staged specs/README.md
git commit -m "specs: number the N name-only specs into .archive/ (NNN-MMM)"
```

Follow the repo's commit conventions (check `git log`): small scoped diffs, no
Co-Authored-By trailer unless the repo uses one, no em dashes in the message if
the repo avoids them.

## Step 8: Rebuild README as a clean index (skip if `--number-only`)

Generate the table **mechanically** from the inventory; do not hand-type ~100
rows or invent descriptions (use each spec's `title` as the topic).

```bash
cd <repo>/specs
{ meta root $(ls [0-9]*.md); meta archive $(ls .archive/[0-9]*.md); } \
 | sort -t$'\t' -k1,1n \
 | awk -F'\t' '{
     n=$1; loc=$2; file=$3; st=$4; title=$5
     link=(loc=="root")?file:".archive/" file
     if(loc=="root"){
       d=(st=="drafted"||st=="draft")?"drafted":
         (st=="in-progress")?"in progress":
         (st=="implemented")?"✅ implemented":
         (st=="complete")?"✅ complete":
         (st=="superseded")?"superseded": st
     } else {
       d=(st=="abandoned")?"📦 archived (abandoned)":
         (st=="superseded")?"📦 archived (superseded)":
         (st=="archived")?"📦 archived":"📦 archived (shipped)"
     }
     printf "| [%s](%s) | %s | %s |\n", n, link, title, d
   }' > /tmp/spec-index-table.md
```

Assemble the README from three parts (write head + foot with the Write tool,
then `cat head table foot > README.md`) so you never paste the long table by
hand:

- **Head** — the repo's existing intro + a folder-layout paragraph (drop any
  now-false "unnumbered drafts sit at the root" sentence) + a status legend
  matching the badges above + the `## Specs` heading and table header
  (`| # | Spec | Status |`).
- **Table** — the generated `/tmp/spec-index-table.md`.
- **Foot** — the **institutional-memory** sections a flat table cannot encode.
  Preserve, do not delete: in-progress state, deferral triggers ("un-defer
  when…"), pending external/tunable decisions, locked decisions, and
  closed-scope ("do not re-litigate") lists. Fold any older per-track or
  per-tier status tables into the one index, which now carries their rows, and
  remove links to index files that no longer exist.

Non-numbered archive files (`*-README.md`, spikes, reconciled drafts) stay
unlisted in the table; mention them once in the folder-layout paragraph.

## Step 9: Verify the README, then commit

```bash
# every markdown link in the README resolves (skip http and ../ cross-repo)
grep -oE '\]\(([^)]+\.md)\)' specs/README.md | sed -E 's/\]\(//; s/\)$//' | sort -u \
 | while read p; do case "$p" in http*|../*) ;; *) [ -f "specs/$p" ] || echo "MISSING $p";; esac; done
```
Fix any `MISSING`, then commit the README as the second atomic diff:

```bash
git add specs/README.md
git commit -m "specs: rebuild README as a single NNN-ordered index"
```

Push if the repo pushes to main directly (check convention).

## Report

Tell the user: how many name-only specs were numbered and their new
`NNN-name.md` targets; which were kept live at the root vs archived; every
same-repo reference fixed; any **sibling-repo** listings left stale on purpose
(with paths, so they can fix those repos); and whether the README was rebuilt.

## Gotchas

- **The `.archive` grep undercounts the max number.** Active specs at the root
  usually hold the highest numbers. Compute `max` over root **and** `.archive/`.
- **Cross-repo companions look local but aren't.** A spec covering one
  workstream that spans several repositories often carries the same bare
  filename in each of them. Grep `../` before renaming; expect sibling READMEs
  to reference the old name and leave them alone.
- **A `draft` status can be a lie.** A spec with an `## Outcome` section shipped;
  treat it as terminal and correct the status on the way to `.archive/`.
- **Never renumber.** Gaps in the sequence are fine and expected (folded/reverted
  specs). Only ever append at `max+1`.
- **Don't delete institutional memory.** "Clean index" means one ordered table
  *plus* the decision-record prose, not a table that erased why things were
  deferred or ruled out.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.