Imported from Sheldon-92/TAD (
.claude/skills/tad-maintain/SKILL.md). Install upstream withnpx skills add Sheldon-92/TAD --skill tad-maintain. Copyright stays with the author.
TAD Maintain Command
When this command is used, perform document health check and synchronization.
平台绑定交互决策(cross-harness binding):本文件及其 references 中所有 AskUserQuestion 调用是「交互决策契约」而非具体工具——当前 harness 有该工具 (Claude Code)→ 直接调用;无该工具(Codex 等)→ 以编号纯文本列出全部选项 (1. … / 2. … / 3. …)并停止等待用户输入,用户以编号或自由文本作答; 禁止代答、禁止把选项折叠成默认值继续执行。SAFETY 门控的调用点(人工审批 / 归档确认 / 权限升级确认类)无论何种 harness 都必须获得真人作答后才能继续。 非交互执行模式(如 codex exec)→ 视为无人可答,按 blocked 停止并上报, 不得自选默认值;已按 YOLO/预授权模式运行且该决策点有书面预授权记录 → 按其协议处理,不适用本条 blocked 分支。
Mode
- Default (no argument or
full): FULL mode - comprehensive check + sync - When called with
checkcontext from agent activation: CHECK mode - read-only - When called with
synccontext from *accept: SYNC mode - scoped write operations
CHECK mode: MUST NOT modify any files. Read-only scan and report only.
SYNC mode: Scoped to the specific handoff being accepted + NEXT.md cleanup.
SYNC mode parameter: target_slug - the canonical slug of the handoff just accepted.
When provided, Step 2 Handoff Lifecycle Audit processes ONLY the matching handoff (skip others).
When not provided (fallback), process all active handoffs (same as FULL mode behavior).
FULL mode: CHECK + broad SYNC across all documents.
Step 1: Gather Current State
Read these to establish baseline:
.tad/version.txt→ current version string.tad/config.yaml(first 10 lines) → config versionls .tad/active/handoffs/→ list active handoff filesls .tad/archive/handoffs/→ list archived filesls .tad/active/epics/→ list active epic filesls .tad/archive/epics/→ list archived epic filesNEXT.md→ count lines, read contentPROJECT_CONTEXT.md→ check if exists
Step 1.5: Drift Detection (Phase 1 P1.2, 2026-04-24)
Run drift detection BEFORE the handoff lifecycle audit (Step 2). Drift findings are advisory (smoke alarm only) — they never auto-modify files; they surface patterns that need human review.
Invocation
bash .tad/hooks/lib/drift-check.sh check-all
The script snapshots .tad/active/handoffs/ ONCE at entry and runs 4 subchecks
serially against that snapshot. Output on stdout is one JSON line per finding;
stderr has human-readable status lines [drift-check] {subcheck} {handoff} {status}.
Subchecks Run
| Subcheck | What it detects | Evidence source |
|---|---|---|
slug_consistency |
handoff filename slug not in Required Evidence Manifest paths | 某应用项目 2026-04-11 + toy layer2-audit FN 2x in 8d |
zombie_handoffs |
git commit found for slug + COMPLETION archived + handoff still active | 某应用项目 code-quality.md:36 |
supersedes_chains |
Supersedes: field points to a handoff still in active/ |
Next Guest 3 same-day handoffs |
ghost_tasks |
housekeeping/sync/cleanup slug missing grounded_state frontmatter |
toy 2026-04-24 |
Report Inclusion
Aggregate drift findings in the CHECK-mode report as a new top-level section
before RECOMMENDED ACTIONS. Example:
DRIFT FINDINGS (advisory — human review required)
[icon] slug_consistency: {N} drift | {N} info | {N} ok
[icon] zombie_handoffs: {N} drift | {N} info | {N} ok
[icon] supersedes_chains: {N} drift
[icon] ghost_tasks: {N} drift
TOP DRIFTS:
- {handoff}: {subcheck}: {message}. Suggested: {suggested_action}
- ...
Modes
- CHECK: Run drift detection, report findings (read-only). Do NOT auto-archive supersedees or move zombies — Criterion C/D-style interactive confirmation still applies in SYNC/FULL modes via the Step 2 Handoff Lifecycle Audit.
- SYNC: Drift findings are displayed alongside lifecycle audit, but SYNC does not auto-apply drift suggestions. User may run Step 2 actions separately.
- FULL: Same as SYNC + include drift findings.
Config
.tad/config-workflow.yaml → drift_check: controls:
zombie_window_days(default 60) — how far back git log is scannedghost_task_prefixes(default[housekeeping, sync, rsync, cleanup, maintenance, audit, refresh])
Failure isolation
- If git is unavailable →
zombie_handoffsemitsstatus:error, the other 3 subchecks still run to completion. - jq missing → the whole tool fails with exit 1 (jq is required for safe JSON emission).
- Any subcheck internal exception → caught by the dispatch's
|| _emit errorsafety net.
Step 2: Handoff Lifecycle Audit
For each file in .tad/active/handoffs/:
(SYNC mode with target_slug: skip files whose extracted slug does not match target_slug)
Step 2a: Extract Canonical Slug
Different naming formats exist. Extract a slug for matching:
| Format | Example | Extracted Slug |
|---|---|---|
HANDOFF-{date}-{slug}.md |
HANDOFF-20260126-multi-platform-init.md |
multi-platform-init |
{date}_{time}_{version}_{taskid}_{slug}_{type}.md |
20260126_0043_v1.0_TASK-20260126-001_blake-ralph-fusion_design.md |
blake-ralph-fusion |
COMPLETION-{date}-{slug}.md |
COMPLETION-20260126-blake-ralph-fusion.md |
blake-ralph-fusion |
Extraction rules:
- Strip file extension
.md - If starts with
HANDOFF-orCOMPLETION-: remove prefix and date, remaining = slug - If long format: extract the segment between task-id and type (e.g., between
TASK-YYYYMMDD-NNN_and_design) - Normalize: lowercase, keep hyphens
Step 2b: Match Against Archive
For each active handoff's slug, search .tad/archive/handoffs/ for:
Criterion A (COMPLETED): A file matching COMPLETION-*{slug}* exists in archive.
→ Action: Mark as COMPLETED.
Criterion B (SUPERSEDED): A file with the same slug but higher version number exists in archive. → Action: Mark as STALE.
Criterion C (AGE_STALE): Handoff has been active longer than handoff_lifecycle.stale_age_days (default 7).
→ Calculate age: extract date from filename (YYYYMMDD), compare with today.
→ If age > threshold AND Criterion A/B did not match: Mark as POTENTIALLY_STALE.
Criterion D (TOPIC_SUPERSEDED): An archived handoff's topic overlaps with this active handoff. → For each active handoff not matched by A/B/C:
- Read the active handoff's first 15 lines to extract title and Executive Summary keywords.
- Scan archived handoffs (created within
handoff_lifecycle.cross_reference_window_days, default 30 days). - Read each archive candidate's first 15 lines, compare title/summary keywords.
- Read
common_words_excludelist from.tad/config-workflow.yaml→document_management.handoff_lifecycle.common_words_exclude. If significant keyword overlap found (≥2 shared topic words, excluding words in thecommon_words_excludelist): Mark as POTENTIALLY_SUPERSEDED and record the matching archive file.
No match found by any criterion: Keep as ACTIVE (in progress).
Step 2c: Actions (SYNC/FULL mode only)
Write safety - two-phase approach:
- First: Verify destination in
.tad/archive/handoffs/does not already have a file with the same name - If name conflict: append
-dup-{timestamp}suffix to avoid overwrite - Move file: copy to archive first, then delete from active
- If copy fails: abort, report error, do NOT delete source
Actions by status:
- COMPLETED → move to
.tad/archive/handoffs/ - STALE → verify archive has newer version (re-read archive directory to confirm), then delete from active. If verification fails, keep in active and report error.
- POTENTIALLY_STALE or POTENTIALLY_SUPERSEDED (FULL mode only) → interactive confirmation required:
- Use
AskUserQuestionto present findings to the user:- Show handoff filename, age in days, and slug
- For POTENTIALLY_SUPERSEDED: show the matching archived handoff filename
- Options: "Archive" (move to archive), "Keep" (leave as active), "Delete" (remove, work was absorbed elsewhere)
- If user says Archive → move to
.tad/archive/handoffs/(two-phase safety) - If user says Keep → leave in place, no action
- If user says Delete → delete from active (no archive copy needed)
- In CHECK/SYNC mode: only report as finding, do NOT prompt or take action
- Use
- ACTIVE → no action (leave in place)
Prohibitions
- NEVER use file modification time (mtime) to determine handoff age. Always extract date from filename (YYYYMMDD format). File mtime is unreliable (git operations, copies, editor saves change it).
- Criterion C and D MUST NOT auto-archive. They always require interactive user confirmation via AskUserQuestion. Only Criterion A (COMPLETED) and B (SUPERSEDED) may auto-archive in SYNC mode.
Step 2d: Idempotency Check
Before any move/delete, verify the source file still exists in active/. If already moved (e.g., by a concurrent run), skip silently.
Step 2e: Epic Lifecycle Audit
For each file in .tad/active/epics/:
Step 2e-i: Parse Epic
- Read the Epic file
- Extract Phase Map table
- For each phase, determine status (⬚ Planned / 🔄 Active / ✅ Done)
- Check if phase has a linked handoff filename
Step 2e-ii: Run 6 Check Types
Check 1 - STALE: All phases are ✅ Done but Epic is still in active/epics/.
→ Detection: Parse Phase Map, count statuses. If all ✅ → STALE.
→ Action (SYNC/FULL): Move to .tad/archive/epics/ (two-phase safety).
Check 2 - ORPHAN: No linked handoffs AND Epic age > stale_age_days.
→ Detection: Phase Map has no handoff filenames filled in, AND file date > threshold.
→ Action (FULL only): AskUserQuestion - "Epic {name} has no linked handoffs and is {N} days old. Archive, keep, or delete?"
Check 3 - DANGLING_REF: Phase Map references a handoff file that doesn't exist.
→ Detection: For each handoff filename in Phase Map, check if file exists in active/handoffs/ or archive/handoffs/.
→ Action (all modes): Report WARNING. Do not auto-fix.
Check 4 - BACK_REF_MISMATCH: A handoff has an **Epic** field referencing this Epic, but the Epic's Phase Map doesn't list that handoff.
→ Detection: Scan active handoffs AND recently archived handoffs (within cross_reference_window_days) for Epic field, cross-reference with Phase Map.
→ Action (all modes): Report WARNING. Do not auto-fix.
Check 5 - STUCK: A phase is 🔄 Active but its linked handoff was created > stale_age_days ago, OR an Active phase has no linked handoff at all.
→ Detection: Find Active phases, check handoff creation date from filename. If Active phase has no linked handoff (dash or empty), treat as STUCK immediately (an Active phase without a handoff is abnormal).
→ Action (all modes): Report WARNING. Remind user to check progress.
Check 6 - OVER_ACTIVE: More than 1 phase in the same Epic is 🔄 Active. → Detection: Count Active phases per Epic. If > 1 → violation. → Action (all modes): Report ERROR. This violates concurrent control rules.
Check 7 - OVER_LIMIT: More than max_active_epics (default 3) Epics in active/epics/.
→ Detection: Count Epic files in .tad/active/epics/ (excluding .gitkeep). If > 3 → warning.
→ Action (all modes): Report WARNING. Suggest completing existing Epics before creating new ones.
Step 2e-iii: Actions (SYNC/FULL mode only)
- STALE → move to
.tad/archive/epics/(two-phase safety: copy first, verify, then delete source) - ORPHAN → FULL mode only, interactive confirmation via AskUserQuestion
- All others → report only, do not auto-fix
Step 3: NEXT.md Maintenance
Step 3a: Read Thresholds from Config
Read .tad/config.yaml section next_md_maintenance.size_limits:
warning_threshold(default 400)max_lines(default 500)
Step 3b: Check
- Count total lines in NEXT.md
- Parse sections by
##headers - Classify each section:
| Section Pattern | Classification | Archive? |
|---|---|---|
## 已完成 (DATE) or ## Completed (DATE) |
Completed | Yes, if DATE > 7 days ago |
## 今天 or ## Today |
Active | Never |
## 本周 or ## This Week |
Active | Never |
## In Progress |
Active | Never |
## 待定 or ## Pending |
Active | Never |
## 阻塞 or ## Blocked |
Active | Never |
## vX.X 变更摘要 |
Reference | Archive with its parent completed section |
## Recently Completed |
Completed | Yes, if oldest entry > 7 days ago |
Any other ## section |
Unknown | Flag in report, don't auto-archive |
- Check if total lines >
warning_threshold
Step 3c: Actions (SYNC/FULL mode only)
Only if total lines > max_lines (from config):
- Identify archivable sections (completed > 7 days + their reference tables)
- Read
.tad/templates/history-md-template.mdfor archive format - Create or append to
docs/HISTORY.md:- If
docs/HISTORY.mddoesn't exist, create it with template header - Append archived sections under
## Week of {date}heading
- If
- Write HISTORY.md first, verify write succeeded
- Only then: remove archived sections from NEXT.md
- Update any stale version references in NEXT.md
If between warning_threshold and max_lines: report WARNING but do not auto-archive.
Step 4: PROJECT_CONTEXT.md Sync
Check:
- File exists?
- If exists: version matches
.tad/version.txt?
Actions (SYNC/FULL mode only):
If missing, create with this template (use actual project values):
# Project Context - {project_name}
## Current State
- **Version**: {from .tad/version.txt}
- **Last Updated**: {today}
- **Framework**: TAD v{version}
## Active Work
{list each file in .tad/active/handoffs/ with status}
## Recent Decisions
{from last 3 archived handoffs - 1 line summary each}
## Known Issues
{any flagged items from health check}
## Next Direction
{from NEXT.md active sections - top 3 items}
If exists but outdated: update version and Active Work section only. Keep under 150 lines.
Step 5: Document Consistency Check
Checks (all modes):
.tad/version.txtvalue ==config.yamlversion:field- No orphaned design files in
.tad/active/designs/(designs whose slug has no matching active handoff) - Evidence check (FULL mode only):
.tad/evidence/reviews/files reference existing archived gates
Actions (FULL mode only):
- Report all inconsistencies
- Auto-fix: if version.txt and config.yaml disagree, report which is newer (by file modification date) and suggest fix, but do NOT auto-fix version mismatches
Step 6: Health Report Output (Terminal)
CHECK mode output:
=== TAD Health Check | {date} ===
HANDOFFS
[icon] {N} active | {N} completed (not archived) | {N} stale | {N} potentially stale (age>{threshold}d) | {N} potentially superseded
EPICS
[icon] {N} active | {N} stale | {N} orphan | {N} stuck | {N} over-active | {N} dangling refs
DOCUMENTS
[icon] NEXT.md: {lines} lines {status}
[icon] PROJECT_CONTEXT.md: {exists/missing}
[icon] Version: {version.txt} / config: {config version}
PAIR TESTING (.tad/pair-testing/)
[icon] TEST_BRIEF.md: {exists/none}
[icon] PAIR_TEST_REPORT.md: {exists (unprocessed!)/none}
[icon] screenshots/: {exists ({N} files)/none}
{if issues found:}
RECOMMENDED ACTIONS
1. Run `/tad-maintain` to sync documents
2. {specific recommendations}
{if PAIR_TEST_REPORT.md exists:}
N. Run *test-review in Alex to process pair test report
HEALTH: {OK/WARNING/CRITICAL}
===
SYNC/FULL mode output:
=== TAD Maintain Report | {date} | MODE: {sync/full} ===
HANDOFFS
[icon] {N} properly archived
[icon] {N} completed -> archived (this run)
[icon] {N} stale -> cleaned (this run)
[icon] {N} user-confirmed -> archived/deleted (this run)
[icon] {N} active (in progress)
EPICS
[icon] {N} properly archived
[icon] {N} stale -> archived (this run)
[icon] {N} orphan -> user-confirmed (this run)
[icon] {N} active (in progress)
[icon] {N} warnings (dangling refs, stuck, back-ref mismatch)
DOCUMENTS
[icon] config.yaml: v{version}
[icon] version.txt: v{version}
[icon] NEXT.md: {lines} lines ({action taken or status})
[icon] PROJECT_CONTEXT.md: {created/updated/ok}
PAIR TESTING (.tad/pair-testing/)
[icon] TEST_BRIEF.md: {exists/none}
[icon] PAIR_TEST_REPORT.md: {exists (unprocessed!)/none}
[icon] screenshots/: {exists ({N} files)/none}
CONSISTENCY
[icon] Version alignment: {pass/fail}
[icon] Active directory: {clean/N orphans}
ACTIONS TAKEN
1. {description}
2. {description}
...
{if any errors:}
ERRORS
1. {what failed and why}
HEALTH: {OK/WARNING/CRITICAL} - {summary}
===
Icons: use text markers [OK] [WARN] [ERR] [INFO] for terminal compatibility.