WHAT
Purpose and context
- Purpose: clean up + validate the draft store and render (
wiki/sources/draft/*.md+llmwiki/html/*.html) when it bloats: PROMOTE genuinely valuable content to the wiki, ARCHIVE finished drafts (tracked, not deleted), KEEP canonical, then RE-INDEX. - Trigger (when to use):
- At the start of a session you see
🧹 [tidy] draft/ có N file .md tầng gốc (> ngưỡng 10)— hooksession_start.pyasks the user; the user AGREES → run this skill. The user DECLINES → skip it, do not ask again in the session. - The user calls
/tidythemselves when they seedraft/orllmwiki/html/is messy, hard to find things in, and they fear losing valuable content. /lintstep 8c (docs-sprawl pulse) ormedicprobetidyreports a warn.
- At the start of a session you see
- Non-goals: do not delete drafts, do not archive still-live drafts just because they are old, do not "fix to standard" files already in
archive/.
Mental model
Why 3 tiers (not just "merge"): What is VALUABLE in a draft is decisions / lessons / patterns — it must go up to wiki (concept/ADR, travels + gets committed + gets indexed). Drafts that have been fully carried out and rendered to html are history → archive (keep, do not delete). Deterministic tools handle counting + classifying + moving + indexing; the reading-and-understanding needed to promote is the agent's job.
draft/ + html/ → check (đếm) → plan (KEEP · ARCHIVE · PROMOTE?) → agent promote → apply (git mv vào archive/<nhóm>/) → re-index → verify + commit.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | --threshold N / env OVERSTACK_DRAFT_THRESHOLD |
no (default 10) | threshold for the number of files in the .md root tier |
| In | --keep-dates N |
no (default 2) | number of html report date checkpoints to retain |
| In | --root . |
no | used when downstream does not have harness/ |
| Out | plan table | yes | KEEP / ARCHIVE / PROMOTE? with the reason for each file |
| Out | new ADR/concept in the wiki | depending on content | valuable content has been promoted, with ## Origin |
| Out | rename into archive/<proposals/superseded/analysis/reports>/ + index |
yes (when applying) | git sees a rename, not a delete |
Rules and capabilities
- RULE-01 (MUST): PROMOTE before APPLY. Archive is now TRACKED so promote-after is still possible, but do not let valuable content be buried under
archive/without anyone knowing. - RULE-02 (MUST): Archive = MOVE, NOT DELETE. Never
rma draft;git mvkeep history (git log --follow). - RULE-03 (MUST): Age is not a reason to archive a draft still
status: proposed/open/implementing. Only status/task that is done (or PLAN/seq following an archived SPEC) is archived. To clean up a hanging draft → a human decides, changes status, reruns. - RULE-04 (MUST): Canonical (
overstack.html,index.html,wiki-graph.html,*-cheatsheet,*-health-dashboard,problem-tree,line-status) is never archived. Html report has dates: keep the 2 most recent date milestones (--keep-dates N), older archive — html is a render, does not apply to.md. - RULE-05 (MUST): Clear roles: tool = count/classify/move/re-index (deterministic, 0 tokens); agent = promote (read-understand). Do not mix.
- RULE-06 (MUST): A new Wiki entry must have
## Origin+ frontmattertype(R2/R9); updatewiki/index.md+wiki/log.md(R3). - RULE-07 (MUST): Validator R7/R9/R18 and
wiki-health/index_syncskiparchive/(frozen draft) — do not "fix to standard" files in archive, they are history. - Capabilities: read draft/html + frontmatter; move files with git (tracked rename); write wiki (ADR/concept/index/log); run local wiki-health/index_sync checks.
Failure boundaries
checkexit 3 = over threshold → continue the plan; below threshold and user does not invoke it themselves → stop, nothing to clean up.- Hanging draft (
status: proposed/open…) → clarify: ask user whether to continue or reject, do not auto-archive. - Archive destination is
.gitignore→ tool refuses to move (intentional), print the gitignore line that needs removing → blocked for that file. wiki-health --fail-on brokenorindex_syncred → failed, fix before commit.
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | deterministic | draft/ |
Check (0 tokens, does not touch files): tidy.py check |
exit 3 = over threshold | below threshold → stop unless user invokes it themselves |
| W02 | deterministic | draft + html | Plan (dry-run): tidy.py plan, read the reason column carefully |
KEEP/ARCHIVE/PROMOTE table? | hanging draft → B01; outdated flag → B02 |
| W03 | judgment | PROMOTE? items + ARCHIVE with decisions | PROMOTE genuinely valuable content to wiki BEFORE applying | ADR/concept + index + log | already in wiki → archive only |
| W04 | effect | plan | Apply: tidy.py apply — git mv into archive + re-index |
rename + index | destination gitignored → B03 |
| W05 | deterministic | worktree | Verify + commit: git status, wiki-health, index_sync |
commit + log | red → fix, repeat W05 |
Step-by-step details (source of truth for W01–W05, steps 0–4):
- Check (0 tokens, no file touched):
Exit 3 = threshold exceeded (default 10 filespython3 harness/scripts/tidy.py check # downstream không có harness/: python3 ~/.claude/harness/harness/scripts/tidy.py check --root ..mdroot level, subdirectories not counted; change with--threshold Nor envOVERSTACK_DRAFT_THRESHOLD). - Plan (dry-run):
python3 harness/scripts/tidy.py plan→ KEEP / ARCHIVE / PROMOTE table? with reason for each file. Read the reason column carefully:OUTDATED — status done|implemented|…ortask T-… đã done→ draft is done → ARCHIVE.⏱ TREO — status proposed/open…→ still alive, age is not a reason to archive; if you want to clean up, ask the user to decide whether to continue or reject (changestatus:in frontmatter then rerun).PROMOTE?= draft with no status, no task → agent reads and decides.↳ ⚠ nội dung có thể outdatedunder a KEEP draft = the file it cites is dead (claim-receipts) or the code it mentions changed after writing (flagcode-driftinstale.json). A live status does not mean the content is still correct: cross-check with the code, then (a) already done → change status and archive, (b) still doing → fix the wrong sentence, (c) drop →rejectedor moveneed-review/. There are false positives (example paths), so you must read before concluding.
- PROMOTE genuinely VALUABLE items into the wiki — do it BEFORE applying (agent's judgment): for each item
PROMOTE?and each ARCHIVE item that seems to contain a decision/lesson not yet present in the wiki → writellmwiki/wiki/sources/adr/ADR-NNN-<slug>.md(decision) orllmwiki/wiki/concepts/<slug>.md(concept), with correct frontmattertype:+## Originpointing to the draft; updatewiki/index.md+wiki/log.md. Already in the wiki → just archive. Do not promote trivial things (progress reports, duplicate renders). - Apply:
python3 harness/scripts/tidy.py apply— move the ARCHIVE group intoarchive/<proposals|superseded|analysis|reports>/(tracked file →git mv, git sees rename not delete), sort both previously archived files, then re-index (build-docs-indexif available,index_sync --fix,html/archive/INDEX.md).- Tool refuses to move a tracked file when the destination is
.gitignore(moving = deleting from the repo) and prints the gitignore line to remove. This is intentional, not a bug.
- Tool refuses to move a tracked file when the destination is
- Verify + commit:
git statusonly sees rename/archive/INDEX.md;python3 harness/scripts/wiki-health.py --wiki-dir llmwiki/wiki --fail-on brokenclean (wikilink pointing to stem in archive/ = resolved-frozen, not broken);index_syncgreen. Commit includes newly promoted ADR/concept + the renames. Appendwiki/log.md.
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | plan reports ⏱ TREO — status proposed/open… |
keep; if you want to clean up, ask the user whether to continue or reject, change status: then rerun plan |
user does not decide → KEEP | W02 |
| B02 | conditional_required | line ↳ ⚠ nội dung có thể outdated under draft KEEP |
cross-check code: (a) done → change status + archive, (b) still working → fix the incorrect sentence, (c) discard → rejected or move need-review/ |
false positive (example path) → keep as is | W03 |
| B03 | recovery | tool refuses to move because the destination is .gitignore |
notify user of the gitignore line to remove; do not work around it by deleting | user does not remove → that file stays put | W04 |
Validation and stopping
Deterministic: bash harness/tests/tidy-test.sh (Deterministic test section below), wiki-health --fail-on broken clean, index_sync green, git status only sees rename/archive/INDEX.md. Needs review: PROMOTE decision (agent read-and-understand). Stop after W05 commit; do not repeat apply multiple times in one session if the plan does not change.
Examples
- Positive: at the start of the session reports
[tidy] draft/ có 57 file, user agrees → plan: 40 ARCHIVE (status done), 4 PROMOTE?, 13 KEEP → agent writes 1 ADR for the decision not yet in the wiki →tidy.py apply→git statusonly sees rename →wiki-healthclean → commit. - Boundary/failure: draft
status: proposed3 months old → plan marks⏱ TREO, do NOT archive; ask user whether to continue or reject. Destinationarchive/reports/is gitignored → apply refuses to move that tracked file and prints the gitignore line to remove.
Deterministic test
bash harness/tests/tidy-test.sh — 11 assertions: threshold >10 (subdirectories not counted), status done → archive, proposed old → keep, destination gitignored → do not move, destination tracked → git rename, loop closes with exit 0.