feat(capitalize): merge /close into /capitalize + TODO reconcile

Two-mode capitalize: default pre-wipe flush, --ritual adds the 3-question
end-of-session reflection (now deduped, unlike legacy /close). New STEP 2B
reconciles .claude/tasks/TODO.md — PASS A done-detection (only on an
unambiguous task<->commit map), PASS B explicit-only capture with an
anti-noise filter (never track commit/deploy/push/release/tag) and BDR
routing for orientation directives. STEP 3 gate gains a separate TODO block;
journal/handoff report TODO ops. /close becomes a thin alias for
/capitalize --ritual (zero duplicated logic).

Built via superpowers:writing-skills TDD: RED baseline (no skill) folded a
push/tag parasite into the TODO, invented a subtask, and wrote with no gate;
GREEN re-run on the same fixture stops at the gate, drops both dups (footer
shows existing IDs), logs one learning, checks only the cleanly-done task,
ignores the parasite, and routes the GraphQL directive to BDR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3e8LaH2vymmxyh36h3jFU
This commit is contained in:
Bastien Chanot
2026-06-19 18:00:51 +02:00
co-authored by Claude Opus 4.8
parent a4e0425581
commit 9dc2b83f0e
3 changed files with 209 additions and 263 deletions
-61
View File
@@ -1,61 +0,0 @@
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME ".claude" }
$Flag = Join-Path $ClaudeDir ".caveman-active"
if (-not (Test-Path $Flag)) { exit 0 }
# Refuse reparse points (symlinks / junctions) and oversized files. Without
# this, a local attacker could point the flag at a secret file and have the
# statusline render its bytes (including ANSI escape sequences) to the terminal
# every keystroke.
try {
$Item = Get-Item -LiteralPath $Flag -Force -ErrorAction Stop
if ($Item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { exit 0 }
if ($Item.Length -gt 64) { exit 0 }
} catch {
exit 0
}
$Mode = ""
try {
$Raw = Get-Content -LiteralPath $Flag -TotalCount 1 -ErrorAction Stop
if ($null -ne $Raw) { $Mode = ([string]$Raw).Trim() }
} catch {
exit 0
}
# Strip anything outside [a-z0-9-] — blocks terminal-escape and OSC hyperlink
# injection via the flag contents. Then whitelist-validate.
$Mode = $Mode.ToLowerInvariant()
$Mode = ($Mode -replace '[^a-z0-9-]', '')
$Valid = @('off','lite','full','ultra','wenyan-lite','wenyan','wenyan-full','wenyan-ultra','commit','review','compress')
if (-not ($Valid -contains $Mode)) { exit 0 }
$Esc = [char]27
if ([string]::IsNullOrEmpty($Mode) -or $Mode -eq "full") {
[Console]::Write("${Esc}[38;5;172m[CAVEMAN]${Esc}[0m")
} else {
$Suffix = $Mode.ToUpperInvariant()
[Console]::Write("${Esc}[38;5;172m[CAVEMAN:$Suffix]${Esc}[0m")
}
# Savings suffix: on by default. Opt out via CAVEMAN_STATUSLINE_SAVINGS=0.
# Reads a pre-rendered string written by caveman-stats.js. Refuses reparse
# points and strips control bytes (matches statusline.sh hardening). Until
# /caveman-stats has run at least once, the suffix file is absent and nothing
# is rendered — safe default for fresh installs.
if ($env:CAVEMAN_STATUSLINE_SAVINGS -ne "0") {
$SavingsFile = Join-Path $ClaudeDir ".caveman-statusline-suffix"
if (Test-Path $SavingsFile) {
try {
$SavingsItem = Get-Item -LiteralPath $SavingsFile -Force -ErrorAction Stop
if (-not ($SavingsItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and
$SavingsItem.Length -le 64) {
$Savings = (Get-Content -LiteralPath $SavingsFile -Raw -ErrorAction Stop).TrimEnd()
$Savings = ($Savings -replace '[\x00-\x1F]', '')
if ($Savings.Length -gt 0) {
[Console]::Write(" ${Esc}[38;5;172m$Savings${Esc}[0m")
}
}
} catch {}
}
}