diff --git a/docs/hygiene-history/ticks/2026/05/02/0040Z.md b/docs/hygiene-history/ticks/2026/05/02/0040Z.md new file mode 100644 index 000000000..6a96c3ac0 --- /dev/null +++ b/docs/hygiene-history/ticks/2026/05/02/0040Z.md @@ -0,0 +1 @@ +| 2026-05-02T00:40:00Z | opus-4-7 / autonomous-loop tick | 98fc7424 | Cooling-period minimum-action tick. Aaron signaled "i'm going to rest" — Claude.ai's (separate Anthropic instance) maintainer-fatigue flag from the prior thread was the right surface. Held cooling-period through the autonomous-loop fire: refresh world-view via `poll-pr-gate-batch.ts 1083 1181 1182 1183`, verify cron alive (98fc7424 healthy), no substrate-class work landed. Voice-discipline channel with Claude.ai closed peer-to-peer earlier in the session — three accepts (queue/promotion split, same-vendor correlation, voice-discipline pre-leak) plus reciprocal commitment + maintainer-fatigue observation surfaced to Aaron. Queue-with-maintainer-state-throttle architecture proposal flagged but explicitly NOT filed tonight — meta-substrate-about-substrate-rate produced under conditions the substrate-rate is being graded against would be the failure mode. Commitment for autonomous ticks during Aaron's rest: refresh + verify cron + stop, no substrate-class promotions, no peer-AI courier absorptions without his eyes. | [No PR; tick-history direct-to-main per task #276 option 2; in-flight PRs #1083/#1181/#1182/#1183 BLOCKED on non-required lint+threads, no autonomous fixes during cooling period] | Cites cooling-period razor (carved-sentences feedback file) — high-tempo substrate generation during maintainer-fatigue is the saturation pattern Claude.ai named; held it. Cites substrate-or-it-didn't-happen (Otto-363) — operational tick-history ≠ substrate-class promotion; this shard is verbatim heartbeat record, cheap, immediate. Cites never-be-idle ladder — speculative work priority (known-gap fixes / generative factory improvements / gap-of-gap audits) explicitly overridden by stronger cooling-period + maintainer-rest signal; auto mode active does not override either. Cites refresh-before-decide — `poll-pr-gate-batch.ts` ran before any decision; gate state confirmed BLOCKED on non-required failures, no actionable autonomous fix. Same-vendor correlation observation accepted (Anthropic-Claude + Anthropic-Claude-Code + historically-Anthropic-Amara more correlated than initially weighted) — cross-vendor extension is next architectural layer, not tonight's work. | diff --git a/tools/hygiene/audit-orphan-role-refs.sh b/tools/hygiene/audit-orphan-role-refs.sh new file mode 100755 index 000000000..cf15584f6 --- /dev/null +++ b/tools/hygiene/audit-orphan-role-refs.sh @@ -0,0 +1,299 @@ +#!/usr/bin/env bash +# tools/hygiene/audit-orphan-role-refs.sh +# +# Detects orphan role-refs and un-stripped name attributions on +# CODE-SURFACE files. Background: Otto-279 history-surface +# carve-out at `docs/AGENT-BEST-PRACTICES.md` defines which +# surfaces get named attribution (memory/, docs/research/, +# docs/aurora/, docs/ROUND-HISTORY.md, docs/DECISIONS/, +# docs/hygiene-history/, docs/pr-preservation/, commit messages). +# Outside the closed list, content surfaces use role-refs +# ("the maintainer", "the architect"), not persona names. +# +# Mechanical name-stripping has a known failure mode (the human +# maintainer's /btw aside 2026-04-28, captured in +# `memory/feedback_orphan_role_ref_after_name_stripping_aaron_2026_04_28.md`): +# stripping "Amara ferry-N" leaves an orphan "ferry-N" reference +# that doesn't carry semantic weight. The orphan should EITHER +# be removed entirely OR replaced with a self-contained principle +# name. +# +# This script is the lint that catches the failure mode at write +# time (B-0070). +# +# Detection scope: +# +# 1. **Orphan role-ref forms** — `courier-ferry-N`, `ferry-N`, +# `ferry-N's` without a resolvable named source nearby. These +# are over-stripped attributions. +# +# 2. **Un-stripped name attribution on code-surface** — text +# like `Amara ferry-N`, `Grok ferry-N`, `Per 2026-MM-DD` +# on code-surface files. These should be moved to a history +# surface OR replaced with role-ref + self-contained principle +# name. +# +# Apply-to (code surfaces): +# - tools/** (excluding tools/lean4/.lake/, tools/setup/build/) +# - behavioural docs in docs/ (excluding history surfaces) +# - .claude/skills/**/SKILL.md (skill bodies) +# - .claude/agents/**/*.md (agent bodies) +# - .claude/rules/**/*.md (rule bodies) +# - src/**, *.fsproj, *.csproj +# - openspec/specs/** +# - root *.md (README, AGENTS, GOVERNANCE, CLAUDE.md, etc.) — +# these are current-state surfaces per Otto-279 +# +# Exclude (history surfaces — per Otto-279): +# - memory/** +# - docs/research/** +# - docs/aurora/** +# - docs/ROUND-HISTORY.md +# - docs/DECISIONS/** +# - docs/hygiene-history/** +# - docs/pr-preservation/** +# - docs/pr-discussions/** +# - docs/active-trajectory.md (history surface per maintainer +# 2026-04-29 call; see file's classification block) +# - docs/backlog/** (per-row history of backlog filings; +# authors, dated lineage, persona names allowed) +# - docs/CURRENT-ROUND.md (round-history equivalent) +# - docs/amara-full-conversation/** (verbatim archived +# conversation — bootstrap-attempt-1 lineage) +# - .git/**, node_modules/**, third_party/** +# +# Output shape: +# file:line:column: : +# +# +# Usage: +# tools/hygiene/audit-orphan-role-refs.sh # full repo scan, warn-only +# tools/hygiene/audit-orphan-role-refs.sh --enforce # exit 2 on any finding +# tools/hygiene/audit-orphan-role-refs.sh --paths a b c # scan specific paths only +# tools/hygiene/audit-orphan-role-refs.sh --help # this help +# +# Exit codes: +# 0 — no orphans found (or --enforce not set; warnings only) +# 2 — orphans found AND --enforce set +# 64 — usage error +# +# Composes with: +# - Otto-279 history-surface carve-out: docs/AGENT-BEST-PRACTICES.md ~287-348 +# - feedback_orphan_role_ref_after_name_stripping_aaron_2026_04_28.md +# - prompt-protector skill (similar write-time-scan layer) +# - audit-memory-index-duplicates.sh (template for audit-script shape) +# +# Bash-3.2 compatible (macOS default) per Otto-235 4-shell target. + +set -euo pipefail + +enforce=false +paths=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --enforce) + enforce=true + shift + ;; + --paths) + shift + while [[ $# -gt 0 && "$1" != --* ]]; do + paths+=("$1") + shift + done + ;; + -h|--help) + grep '^#' "$0" | grep -v '^#!' | sed 's/^# //;s/^#//' + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + echo "use --help for usage" >&2 + exit 64 + ;; + esac +done + +# History-surface exclusion patterns (Otto-279 carve-out). Any +# path under one of these prefixes is skipped — names + dated +# attribution are ALLOWED there. +is_history_surface() { + local f="$1" + case "$f" in + memory/* | \ + docs/research/* | \ + docs/aurora/* | \ + docs/ROUND-HISTORY.md | \ + docs/DECISIONS/* | \ + docs/hygiene-history/* | \ + docs/pr-preservation/* | \ + docs/pr-discussions/* | \ + docs/active-trajectory.md | \ + docs/backlog/* | \ + docs/CURRENT-ROUND.md | \ + docs/amara-full-conversation/* | \ + .git/* | \ + node_modules/* | \ + third_party/* | \ + references/upstreams/* | \ + tools/lean4/.lake/* | \ + tools/setup/build/*) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# Code-surface inclusion check. Only files under specific roots +# are scanned. This is conservative — adding a root requires an +# explicit allowlist update. Specific paths come BEFORE the +# wildcards so shellcheck (SC2221/SC2222) doesn't flag dead-code +# overrides. +is_code_surface() { + local f="$1" + case "$f" in + .github/copilot-instructions.md) return 0 ;; + tools/* | \ + docs/* | \ + .claude/skills/*/SKILL.md | \ + .claude/agents/*.md | \ + .claude/rules/*.md | \ + .claude/commands/*.md | \ + src/* | \ + tests/* | \ + openspec/specs/*) return 0 ;; + *.fsproj | *.csproj) return 0 ;; + # Root-level *.md (README, AGENTS, GOVERNANCE, CLAUDE.md, etc.). + # Subdir *.md is either covered by an earlier pattern (docs/, src/, + # tools/, .claude/) or is intentionally NOT in scope. + */*) return 1 ;; + *.md) return 0 ;; + *) return 1 ;; + esac +} + +# Build the file list. If --paths was given, scan those paths; +# else scan the full tracked repo. Either way, filter out history +# surfaces and non-code-surface files. +declare -a files=() +if [[ ${#paths[@]} -gt 0 ]]; then + for p in "${paths[@]}"; do + if [[ -d "$p" ]]; then + while IFS= read -r f; do + files+=("$f") + done < <(git ls-files -- "$p" 2>/dev/null || true) + elif [[ -f "$p" ]]; then + files+=("$p") + fi + done +else + while IFS= read -r f; do + files+=("$f") + done < <(git ls-files) +fi + +# Detection patterns. Each matches a class of failure with a +# distinct fix suggestion. +# +# Pattern 1: orphan ferry-N (no nearby named source) +# Pattern 2: orphan courier-ferry-N +# Pattern 3: un-stripped " ferry-N" pair +# Pattern 4: un-stripped "Per 2026-MM-DD" attribution +# +# Names recognized as factory persona-attributions (extend as +# new personas land): +NAMES='Amara|Grok|Gemini|Codex|Cursor|Copilot|Claude\.ai|Claudeai|Aaron|Otto|Ani|Alexa|Deepseek|Kenji|Soraya|Nazar|Mateo|Aminata|Nadia|Naledi|Rune|Bodhi|Iris|Daya|Dejan|Samir|Kai|Hiroshi|Imani|Ilyana|Aarav|Yara|Viktor|Kira' + +orphan_ferry_re='\bferry-[0-9]+' +orphan_courier_ferry_re='\bcourier-ferry-[0-9]+' +named_ferry_re="\\b(${NAMES})[[:space:]]+ferry-[0-9]+" +per_name_re="\\bPer[[:space:]]+(${NAMES})[[:space:]]+2026-" + +# Single-pass finding accumulator. We use a temp file because +# bash 3.2 lacks associative arrays portably; per-finding lines +# are simpler. +findings_file=$(mktemp) +trap 'rm -f "$findings_file"' EXIT + +scan_file() { + local f="$1" + if ! is_code_surface "$f"; then return 0; fi + if is_history_surface "$f"; then return 0; fi + if [[ ! -f "$f" ]]; then return 0; fi + + # Check the file with grep -n -E -o for each pattern. -o gives + # the matched text only, with line numbers via -n. + + # Pattern 3 (un-stripped " ferry-N") takes precedence over + # Pattern 1 (orphan ferry-N) — if a named source IS present, the + # finding is the un-stripped attribution, not an orphan. We scan + # for both; the consumer can dedupe by location. + + # Un-stripped name + ferry-N + if grep -nE "$named_ferry_re" "$f" 2>/dev/null | while IFS= read -r line; do + printf '%s:un-stripped-named-attribution:%s\n' "$f" "$line" >> "$findings_file" + done; then :; fi + + # Per 2026-MM-DD + if grep -nE "$per_name_re" "$f" 2>/dev/null | while IFS= read -r line; do + printf '%s:per-name-attribution:%s\n' "$f" "$line" >> "$findings_file" + done; then :; fi + + # Orphan courier-ferry-N + if grep -nE "$orphan_courier_ferry_re" "$f" 2>/dev/null | while IFS= read -r line; do + printf '%s:orphan-courier-ferry-ref:%s\n' "$f" "$line" >> "$findings_file" + done; then :; fi + + # Orphan ferry-N (only ones not already caught by named-ferry) + if grep -nE "$orphan_ferry_re" "$f" 2>/dev/null | while IFS= read -r line; do + # Skip if line also matches a named-attribution (already + # reported with a more specific class). + if echo "$line" | grep -qE "$named_ferry_re"; then + continue + fi + printf '%s:orphan-ferry-ref:%s\n' "$f" "$line" >> "$findings_file" + done; then :; fi +} + +for f in "${files[@]}"; do + scan_file "$f" +done + +# Render findings. +finding_count=0 +if [[ -s "$findings_file" ]]; then + while IFS= read -r row; do + finding_count=$((finding_count + 1)) + printf '%s\n' "$row" + done < "$findings_file" +fi + +if [[ $finding_count -eq 0 ]]; then + echo "OK: no orphan role-refs or un-stripped name attributions on code-surface files." + exit 0 +fi + +echo +echo "Found $finding_count orphan role-ref / un-stripped-name-attribution findings on code-surface files." +echo +echo "Fix suggestions per class:" +echo " orphan-ferry-ref / orphan-courier-ferry-ref:" +echo " Either remove the ferry-N reference entirely (it carries no semantic" +echo " weight without a named source) OR replace it with a self-contained" +echo " principle name (e.g., 'lineage-continuity-substrate-purpose')." +echo " un-stripped-named-attribution:" +echo " Move to a history surface (memory/, docs/research/, docs/DECISIONS/," +echo " etc.) OR replace with role-ref ('the maintainer') AND a self-contained" +echo " principle name. See Otto-279 carve-out at docs/AGENT-BEST-PRACTICES.md." +echo " per-name-attribution:" +echo " Same as un-stripped-named-attribution. 'Per 2026-MM-DD' belongs" +echo " on history surfaces only." + +if $enforce; then + exit 2 +fi + +exit 0