Skip to content

feat(claude-config): import-UX policy — --migrate→--import rename + copy+warn + weight-reduction excludes - #1429

Merged
qingyun-wu merged 6 commits into
staging-workspace-revampfrom
feat/import-ux-rename-and-exclude-policy
Jun 3, 2026
Merged

qingyun-wu merged 6 commits into
staging-workspace-revampfrom
feat/import-ux-rename-and-exclude-policy

Conversation

@qingyun-wu

Copy link
Copy Markdown
Collaborator

Follow-up to PR #1424 (merged) implementing the import-UX policy locked in the 2026-06-03 owner thread. Targets staging-workspace-revamp per owner directive.

Summary

5 commits implementing:

  1. --migrate → --import rename with 1-release deprecation alias.
  2. Copy + warn policy for channel secrets (revert PR fix(post-1415): Bucket-B rollup — closes 7 follow-up issues (#1416–#1422) #1424 baseline excludes + add explicit vault-sync stderr warning).
  3. Weight-reduction excludes for the heavy claude-code state artifacts that have no Sutando dependency (~150 MB / 3700+ files saved per import on a typical profile).
  4. Regression test additions — 9 new assertions, 31 total in tests/sutando-shell-setup-argparse.test.sh.

The 5 commits

SHA Concern
091025e Rename --migrate → --import. INVOKED_AS variable threads the literal flag the user passed into status messages. --migrate kept as a deprecated 1-release alias that prints a stderr warning. Per feedback_import_not_migrate.
354d8f5 Revert PR #1424's channels/*/*.env + channels/*/access.json.bak* excludes. Add a post-import stderr warning enumerating each imported secret file with its mode, plus an explicit "add an exclude rule for these in your M2 vault sync policy BEFORE the next sync" guidance. Option (B) from the owner thread.
642885f Smoke-caught regression to 354d8f5: the warning loop used bash glob channels/*/*.env which doesn't match dot-prefixed names (rsync's * does; bash's doesn't). Result: warning missed the canonical channels/discord/.env etc. and only listed relay-client.env + *.bak* files. Fixed by enumerating 3 explicit patterns: channels/*/.env + channels/*/*.env + channels/*/access.json.bak*.
4e44d84 Add weight-reduction excludes: shell-snapshots/ (auto-regen per zsh invocation), history.jsonl (rebuilds), file-history/ (102 MB / 3618 files of past-session edit-undo blobs, no Sutando dependency).
99e35cd 9 new test assertions in the argparse suite: rename + deprecation (4), weight-reduction exclude presence (3), copy + warn reverse-assertions (2). Now 31 ✓ / 0 ✗.

What this PR is NOT

  • Auto-trigger UX (wire into src/startup.sh first-time prompt-once-per-host) — held for a separate PR per "single concern per PR". Owner's auto-trigger directive from the same thread is the next step.
  • Pruning previously-imported shell-snapshots/ / history.jsonl / file-history/ from existing target trees. Current --import is rsync's default (add-only). --delete semantics would also wipe user customizations under CLAUDE_DIR. Held for a separate design call.
  • Updates to tests/sutando-shell-setup.test.sh (the older 11-assertion suite) — those tests still pass because --migrate works as an alias. Cosmetic-only — can update in a follow-up if reviewers want.

Decisions from the owner thread (locked-in policy)

Path Decision
channels/*/*.env (bot tokens) COPY + warn
channels/*/access.json COPY (current; never excluded)
channels/*/access.json.bak* COPY (per owner — audit-trail value)
shell-snapshots/* EXCLUDE
history.jsonl EXCLUDE
file-history/* EXCLUDE
projects/<other-slug>/* EXCLUDE (PR #1424 baseline)
debug/ / plugins/*/cache/ / statsig/ EXCLUDE (PR #1424 baseline)
*.sock / *.pid EXCLUDE (PR #1424 baseline)

Test plan

  • bash -n clean
  • bash tests/sutando-shell-setup-argparse.test.sh → 31 ✓ / 0 ✗
  • Live smoke: bash scripts/sutando-shell-setup.sh --import runs end-to-end, prints the new vault-sync warning with all 7 secret-class files (4 bot tokens + relay-client + 2 auth-backups), exits 0.
  • Live smoke: bash scripts/sutando-shell-setup.sh --migrate prints the deprecation warning + still runs the import flow.
  • Live smoke: bash scripts/sutando-shell-setup.sh --repair-paths --from=/ still rejects (PR fix(post-1415): Bucket-B rollup — closes 7 follow-up issues (#1416–#1422) #1424 validation untouched).
  • Pre-existing tests/sutando-shell-setup.test.sh runs cleanly with --migrate still routing via the alias (CI).
  • CI on the PR.

Reviewer notes

  • The --migrate → --import rename is intentionally NOT a deprecation-and-removal. Both keep working for one release; status messages echo whichever the user typed. After we observe nobody's hit by the deprecation in real usage, follow-up PR drops --migrate entirely.
  • The 642885f smoke-caught regression is filed inline (not as a separate issue) because it's a fix to code that's only on this branch — the prior commit broke and this one fixed before the PR opened. Per feedback_per_file_pattern_audit: the dotglob gotcha is documented in the helper to prevent future regressions.

🤖 Generated with Claude Code

qingyun-wu and others added 6 commits June 2, 2026 23:07
…import_not_migrate)

Owner directive 2026-06-03 in the PR #1424 thread: end-user-facing commands
that non-destructively copy state should say "import" not "migrate."
"Migrate" implies one-way + irreversible; the actual semantic is a
non-destructive rsync with source preserved. Captured in
feedback_import_not_migrate memory.

## Changes

- **`--import`** is the new canonical flag. Routes to MODE="import" + the
  renamed case-arm.
- **`--migrate`** kept as a deprecated 1-release alias. Routes to the same
  case-arm (MODE="import") + sets DEPRECATED_FLAG_MIGRATE=1 so the case-arm
  can print a single stderr deprecation warning at the top of its flow:
  > ⚠ --migrate is deprecated, use --import instead. (Behavior is identical.)
  > The --migrate alias will be removed in a future release.
- **`INVOKED_AS` variable** holds the literal flag the user passed so all
  status messages echo back what they typed (e.g. `--migrate` users still
  see "sutando-shell-setup --migrate: done." not the canonical name they
  didn't use). Status messages: 5 echo lines + the post-migrate echo.
- **Header docs** updated: --import is the listed primary, --migrate moved
  to a "Deprecated aliases (kept for one release)" section with citation
  to feedback_import_not_migrate.
- **Comment + docstring updates**: `_rewrite_runtime_paths()` docstring,
  the legacy 'and for --migrate' comment, the 'Re-running --migrate would
  be heavy' comment, the 'run --migrate first' user-facing hint in
  --repair-paths — all updated to --import.

## Backward compat

`--migrate` keeps working with identical behavior; only differences are the
stderr deprecation warning + status messages echo "--migrate" (matching
what the user invoked). One release deprecation per the pattern used for
CLAUDE_HOME → CLAUDE_CONFIG_DIR in util_paths.ts.

## Smoke verification

- `bash scripts/sutando-shell-setup.sh --import` runs the import flow ✓
- `bash scripts/sutando-shell-setup.sh --migrate` runs with stderr warning ✓
- `bash scripts/sutando-shell-setup.sh --repair-paths --from=/` rejects with
  the existing validation ✓ (no regression)
- `bash -n` passes ✓

Test fixture updates in `tests/sutando-shell-setup.test.sh` come in commit
4 of this PR (alongside other test additions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…warning (import-UX policy)

Owner directive 2026-06-03 (PR #1424 import-UX thread, option (B) Copy +
warn): bot tokens + auth backups should be COPIED post-import so bridges
work immediately, with an explicit stderr warning identifying the secret
files that landed so the user can add them to their vault-sync exclude
before M2 ships.

This reverts 2 of the 4 secret-safety excludes from PR #1424 (Mini review
#4 fix):

- `channels/*/*.env`               — bot tokens (Discord/Slack/Telegram).
- `channels/*/access.json.bak*`    — stale auth-list backups.

Kept excluded: `*.sock`, `*.pid` (true runtime artifacts; copying them is
nonsense).

## What's now in the post-import output

After rsync + path-rewrite, if `${CLAUDE_DIR}/channels/*/*.env` files
exist (= the import included them), print:

```
⚠ Imported host-specific secrets — review before vault sync:
    channels/discord/.env  (mode 600)
    channels/slack/.env    (mode 600)
    channels/telegram/.env (mode 600)
    channels/discord/access.json.bak.20260529-add-susan
    ...

  These files contain bot tokens / stale auth state and are host-coupled.
  If you sync this workspace to a remote (M2 vault), add an exclude rule
  for channels/*/*.env + channels/*/access.json.bak* in your sync policy
  BEFORE the next sync to avoid leaking secrets remotely.
```

Mode display uses `stat -f '%Mp%Lp'` (BSD/macOS) with fallback to
`stat -c '%a'` (GNU/Linux). Works on both stock macOS sed-cohort env and
linux CI.

The old "MANUAL re-rehome cp loop" copy-instruction block is removed — the
new policy means users don't need that workaround.

## Tradeoff acknowledged

The vault-sync coordination burden moves from the import script to the
user's M2 sync configuration. Mini's review #4 originally chose
secret-safety baseline; owner picked the UX layer on top. The warning
makes the deferred coordination explicit + actionable (it lists the
specific paths to exclude).

## Smoke verification

- `bash scripts/sutando-shell-setup.sh --import` runs end-to-end, prints
  the new warning block at the end (5 .env files + 4 .bak files listed). ✓
- `bash -n` passes ✓
- No change to runtime-path rewrite or `_rewrite_runtime_paths()` allowlist
  behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… shape (smoke-caught regression to 354d8f5)

Smoke-test of the prior commit (354d8f5) revealed the new vault-sync
warning loop only listed `relay-client.env` and the `.bak*` files — it
missed the actual bot tokens `channels/discord/.env`, `channels/slack/.env`
etc. because bash's `*` does NOT match dot-prefixed names by default
(unlike rsync's `*` which does).

This commit:

1. Removes the `compgen -G` gate (only fires for non-dotfile files; same
   bug class).
2. Iterates explicitly over THREE patterns combined: `channels/*/.env`
   (dotfile form, the standard bot-token shape) + `channels/*/*.env`
   (other .env variants like `relay-client.env`) +
   `channels/*/access.json.bak*` (auth backups).
3. Lazy-prints the warning header on first match so the block only
   appears when secrets actually got imported.
4. Adds `?` fallback for `stat` mode in case both BSD + GNU forms fail.
5. Updates the "add an exclude rule for" guidance to list all three
   patterns (was 2; missed `channels/*/.env` like the original code did).

Smoke verification:
- All 4 `channels/*/.env` bot-token files now listed in the warning ✓
- `channels/ag2space/relay-client.env` still listed ✓
- 2 `access.json.bak*` files still listed ✓
- Mode column shows `0600` for all secret-class files ✓

Per-file-pattern audit: the same dotglob gotcha could bite the rsync
exclude side IF we ever needed to exclude `channels/*/.env` (rsync's `*`
actually matches dotfiles, so no bug there). Documented in the helper
comment to prevent future regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (shell-snapshots, history.jsonl, file-history)

Owner directive 2026-06-03 (PR #1424 import-UX thread): exclude heavy
claude-code state artifacts that have NO Sutando dependency and either
auto-regenerate or are rarely-used past-session data. Saves ~150 MB and
~3700 files on a typical owner profile (file-history alone is ~102 MB /
3618 files on the author's MBP).

Added excludes:

- `shell-snapshots/`  — zsh snapshots auto-regenerate per shell invocation.
  Sutando has no dependency. Per host this can be 30-50 MB across hundreds
  of snapshot-zsh-*.sh files. Source code stays intact.
- `history.jsonl`     — per-session command history. Rebuilds for new
  sessions in the new location. Sutando has no dependency. Single file,
  can be MBs.
- `file-history/`     — claude-code's per-session edit-undo snapshots.
  3618 files / 102 MB on author MBP. Sutando has zero references. Only
  cost: loses Claude-Code's "rewind an edit from a past session" capability
  (rare in practice — new-session edits write fresh blobs to the new
  location). Per-session UUID-keyed so cross-host import would conflate
  contexts anyway.

NOT added: `projects/<other-slug>/*` (already excluded since PR #1395),
`debug/`/`plugins/*/cache/`/`statsig/` (already excluded), `*.sock`/`*.pid`
(already excluded from PR #1424). The 4 secret-safety reverts from commit
354d8f5 (channels/*/*.env + access.json.bak*) are NOT excluded — that's
the (B) copy + warn UX.

## Smoke

`bash -n` passes. End-to-end --import flow runs cleanly. Disk savings will
materialize on the next fresh import (current target tree already has
these files from a prior --import run, so rsync's mtime-skip won't
re-delete them — that's a separate prune step the user can run after
verifying).

## Reviewer note

If you want the import to ALSO prune already-imported `shell-snapshots/`,
`history.jsonl`, `file-history/` from the target tree, that's a `rsync
--delete` semantic we'd need to add carefully — currently --import is
add-only (rsync default), and `--delete` would also wipe user customizations
the user added under CLAUDE_DIR. Holding for a separate design call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…precation, weight excludes)

Extends `tests/sutando-shell-setup-argparse.test.sh` with 9 new
assertions covering this PR's behavioral changes per
`feedback_e2e_tests_for_contributions`:

## New "Import-UX" section

**Rename + deprecation:**
- `--import` invocation does NOT print the deprecation warning ✓
- `--migrate` invocation DOES print "--migrate is deprecated" ✓
- `--migrate` invocation still routes through to the import flow (status
  line "sutando-shell-setup --migrate" present) ✓
- `--import` invocation echoes "--import" in the status line (proves
  the INVOKED_AS variable threads correctly) ✓

**Weight-reduction excludes:**
- `--exclude='shell-snapshots/'` present in RSYNC_FILTERS ✓
- `--exclude='history.jsonl'` present in RSYNC_FILTERS ✓
- `--exclude='file-history/'` present in RSYNC_FILTERS ✓

**Copy + warn policy (reverse-assertion: things that should NOT be excluded):**
- `--exclude='channels/*/*.env` ABSENT (was excluded in PR #1424 baseline;
  reverted here per option (B)) ✓
- `--exclude='channels/*/access.json.bak` ABSENT (was excluded in PR #1424
  baseline; reverted here per option (B)) ✓

## Implementation note

Filter assertions grep the SOURCE-level RSYNC_FILTERS+=(...) block, not
the rsync runtime output. Reason: the dry-run preview is `head -50` so
some filter lines may be truncated; the source-level block is the
authoritative declaration.

## Total coverage now

31 assertions in `tests/sutando-shell-setup-argparse.test.sh` (22 prior
from PR #1424 + 9 new from this PR). All ✓, exit 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…review nit)

@liususan091219's cold-review nit: the vault-sync warning block uses
`echo` (stdout), while source-missing / rsync-missing errors above use
`>&2`. The vault-sync warning is arguably the most important line in the
entire script (data-loss / secret-leak prevention), so it should go to
stderr for parity.

Bonus property: with the warning on stderr, `sutando-shell-setup --import
> install.log` still surfaces the warning to the user's terminal even
when stdout is captured. Critical for the "user pipes the install" case.

## Touchpoint

`scripts/sutando-shell-setup.sh:729-746` — added `>&2` to every line of
the secret-warning block (8 `echo` lines + the blank-line separators).

## Verification

- `bash scripts/sutando-shell-setup.sh --import 2>/dev/null` → warning gone,
  status line stays (warning was on stderr) ✓
- `bash scripts/sutando-shell-setup.sh --import 1>/dev/null 2>&1` → only
  the warning + stderr-side messages show (stdout dropped) ✓
- `bash -n` clean ✓

## No test update needed

The existing test (assertion: pattern presence in RSYNC_FILTERS source
text) is unaffected. A stderr-vs-stdout assertion would require capturing
both streams separately, which adds complexity for a behavioral check
already documented in the commit message. Holding for follow-up if needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

@qingyun-wu
qingyun-wu merged commit 963694c into staging-workspace-revamp Jun 3, 2026
5 checks passed
@qingyun-wu
qingyun-wu deleted the feat/import-ux-rename-and-exclude-policy branch June 3, 2026 06:21

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mergeable: MERGEABLE/UNSTABLE (will probably go CLEAN once CLA re-checks). Cold review — LGTM at code level + a few suggestions. Verified each policy claim per-line at HEAD 99e35cd.

  • --migrate → --import rename done correctly. The deprecation alias path is: --migrate sets MODE="import" + DEPRECATED_FLAG_MIGRATE=1 + INVOKED_AS="--migrate". Single warning fires from the case-arm; status messages echo $INVOKED_AS so the user sees their typed flag, not the canonical. Clean separation.

  • Copy-+-warn policy reversal vs PR #1415 review #4 baseline is the intentional trade-off. I want to flag this explicitly: this PR REVERSES the security-conservative default that #1415 review #4 added. The old policy (exclude channels/*/*.env + access.json.bak*) made the post-import bridge configuration explicit + manual; the new policy makes bridges work zero-friction at the cost of a stderr warning the user might miss. The warning text IS clear and lists the exact globs to add to vault-sync exclude policy. Acceptable if (a) the warning lands on the terminal where the user is looking, and (b) the M2 vault sync engine doesn't auto-sync silently. Worth a CHANGELOG note flagging the reversal so anyone tracing PR history sees the trade-off explicitly.

  • Glob enumeration fix (642885f) is correct. Bash globs don't match dot-prefixed names by default. channels/*/*.env matches channels/foo/bar.env but MISSES channels/discord/.env (the canonical bot-token shape). The 3-pattern enumeration channels/*/.env + channels/*/*.env + channels/*/access.json.bak* covers both shapes explicitly. Safer than shopt -s dotglob because dotglob has surprise interactions with other globs in the same loop. Good self-catch.

  • Weight-reduction excludes (shell-snapshots, history.jsonl, file-history) verified safe. Grepped src/ + skills/ for these paths — no Sutando dependency. shell-snapshots regenerates per-invocation, history.jsonl is per-session claude-code state, file-history is past-session edit-undo. The 150 MB / 3700 files saving is real.

  • Tests cover argparse + filter-block presence/absence. Good positive + negative assertions: (a)-(c) verify deprecation warning fires only for --migrate, INVOKED_AS echoes correctly; (d) excludes present; (e) NOT-excludes absent. The "absence" assertion is especially good — without it, an accidental re-add of the exclude would silently revert the policy.

  • suggestion — add an E2E test for the post-import secret-warning block. Tests currently verify the rsync-FILTERS block via static grep; they don't actually exercise the post-rsync warning loop. Quick add: create temp $CLAUDE_DIR/channels/discord/.env fixture, source the warning loop (or extract to a helper function), assert the warning text fires with the exact path. Catches a future glob-pattern regression that static checks would miss.

  • suggestion — CHANGELOG / PR body should call out the security trade-off more prominently. Right now the trade-off lives in a code comment ("option (B) ... accepting the vault-sync coordination burden") and the PR body's "Copy + warn policy". Anyone reading git log for security review will see "feat(claude-config): import-UX policy" and not necessarily realize this REVERSES the secret-exclude baseline that #1415 review #4 established. A CHANGELOG line like "SECURITY-relevant policy change: channels//.env are now COPIED by --import (was EXCLUDED in #1415). User must add to vault-sync exclude policy before next sync" would surface the change for downstream auditors.

  • nit — stat -f vs stat -c fallback in mode display. stat -f '%Mp%Lp' || stat -c '%a' || '?' works on both BSD and GNU. Already correct; flagging for the record that this matches the feedback_mac_bsd_vs_gnu_coreutils memory pattern.

LGTM at code level. MERGEABLE/UNSTABLE (CLA recheck). Owner's merge call.

— Echo Act IV (Mini)

qingyun-wu added a commit that referenced this pull request Jun 3, 2026
…-only) (#1430)

* feat(relay): new /relay skill + catchup-after-startup read-and-archive integration (Phase 1)

Owner directive 2026-06-03 (PR #1429 thread): add a "relay" skill that
writes narrative continuity for the next Sutando session. Where
`session-handoff.sh` writes structured facts (recent commits, open PRs,
pending Qs), `/relay` writes intent + judgment — what was just in flight,
what to check first, what might break, and the implicit context the
structured snapshot can't carry.

## Design decisions (owner-locked in the thread)

| Decision | Choice |
|---|---|
| Skill name | `relay` |
| Trigger model | Manual + auto (Phase 2 for auto; this PR is manual-only) |
| Coexists with `session-handoff.sh` | Yes — handoff.sh writes facts, relay writes narrative; sister skills |
| Folder vs single-file | Folder + archive (per Sutando's `tasks/` + `results/` pattern) |
| Read order in catchup | FIRST (section 0, before the structured snapshot) |
| Quality-gate (refuse-on-thin-note) | Deferred to Phase 2 |

## Folder layout

```
workspace/relay/
├── relay-{epoch}.md          # pending — written by /relay
└── processed/
    └── relay-{epoch}.md      # archived by catchup after read
```

Mirrors `tasks/` + `tasks/archive/` and `results/` + `results/archive/`
patterns. Multiple relay-files per session allowed; `--append` writes to
the latest unprocessed file instead of creating a new one.

## What this PR ships

**1. `skills/relay/SKILL.md`** — new user-invocable skill.

- `/relay` writes a fresh `workspace/relay/relay-{epoch}.md`.
- `/relay --append` appends to the latest unprocessed file.
- Narrative content guidance (active work / first action / known risks /
  implicit context) with bad-example vs good-example contrast.
- Phase 1 scope (manual-only, no quality-gate) + Phase 2 ideas (auto-refresh
  via `/proactive-loop`, quality-gate, PreCompact hook integration) called
  out explicitly so future authors don't re-litigate.

**2. `skills/catchup-after-startup/SKILL.md` — extended.**

- New §0 in "What it pulls together" — relay notes read FIRST, before the
  structured snapshot.
- New "Relay-note read-and-archive flow" section codifying:
  - mtime-ordered consumption (oldest first)
  - per-file `mv` to `processed/` after read (atomic drain pattern, prevents
    double-consumption by concurrent catchups)
  - graceful absent-note handling with onboarding nudge
- Existing Step 2 updated to "Read the relay note FIRST and treat it as the
  narrative ground truth before validating against the structured snapshot."

## What's NOT in this PR (Phase 2)

- **Auto-refresh from `/proactive-loop`** — write/update the relay file if
  it's mtime-stale (> 30 min) AND substantive work has happened since.
  Lean: ship Phase 1 first, observe how owners actually use manual
  invocation, then decide auto-refresh cadence.
- **`scripts/catchup-after-startup.sh` implementation update** — the SKILL.md
  documents the read-and-archive flow; the bash script needs the matching
  implementation. Holding for a follow-up commit on this PR if reviewers
  prefer, OR a sibling PR.
- **Quality-gate** — refuse-on-thin-note. Deferred per the decision table.
- **PreCompact hook integration** — would prompt the LLM to write a relay
  note before compaction discards context. Promising but complex; Phase 2.

## Test plan

- [x] `skills/relay/SKILL.md` written; covers required sections per the
  user-invocable skill contract.
- [x] `skills/catchup-after-startup/SKILL.md` updated; the read-and-archive
  flow is codified in prose.
- [ ] `scripts/catchup-after-startup.sh` implementation update for the
  relay-read flow. (Separate work; not blocking this PR's documentation
  surface.)
- [ ] `/relay` invocation E2E: write a note, run `/catchup-after-startup`,
  verify the note appears in the briefing + gets moved to `processed/`.
- [ ] CI on the PR.

## Reviewer notes

Per `feedback_design_quality` + the new 2026-06-03 "creators of Sutando"
framing: this PR introduces a STRUCTURAL primitive (relay-as-folder),
not a band-aid. It composes with existing patterns (tasks/, results/
consumption-via-archive) so future maintainers don't have to learn a new
mental model. The Phase 2 ideas section is intentional — telegraphing
where this evolves so the next author knows the design has room to grow
without re-architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(proactive-loop): bake relay-trigger judgment into step 7 (lighter than periodic auto-refresh)

Owner directive 2026-06-03 (PR #1430 thread): the lighter alternative to a
separate auto-refresh subsystem. After step 7 updates build_log.md, the
LLM also asks "did THIS pass surface anything the next session would
NEED to know that isn't already in build_log or pending-questions?"

## Why this shape

- **Event-triggered, not time-triggered.** Owner pushed back on "constantly
  during proactive-loop" — at */5 min cadence that's 288 writes/day, mostly
  no-op since most loop iterations are sentinel-skip / idle. Signal-to-noise
  catastrophic.
- **Reuses existing step.** Step 7 already prompts the LLM to "note what's
  next" — same beat point, just adds the relay judgment.
- **Lean conservative.** The instruction explicitly says "better one good
  relay note per substantive pass than five thin ones." Most passes
  produce no relay; only natural events (PR open/merge, design decision,
  blocker lift, judgment-shaped insight) trigger a write.
- **Append-vs-new heuristic** — if latest relay note < 30 min old AND this
  pass extends the same thread, --append; else new file. Keeps catchup's
  briefing focused (one note per coherent thread).

## Touchpoint

`skills/proactive-loop/SKILL.md` step 7 — ~10 lines added after the
existing "Update build_log.md" instruction.

## Test

Live: this commit IS itself a substantive event the next session would
want to know about (PR #1430 amended with auto-trigger). After this push,
I'll follow the step 7 protocol manually and write/append a relay note.

## Relationship to PR #1430

This is a follow-up commit on the same PR branch
(feat/relay-skill-handoff-continuity). Owner's "A1+A2" directive: ship
the manual `/relay` skill (PR #1430 base scope) AND the auto-trigger
in the same PR vs sibling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(catchup-after-startup): bash impl for relay-note read-and-archive flow

The Phase 1 SKILL.md (commit 033bfd5) documented the read-and-archive
protocol; this commit ships the matching bash implementation. Without
it, the SKILL.md instruction would be doc-only and catchup wouldn't
actually surface relay notes to the next session.

## What changes

`skills/catchup-after-startup/scripts/catchup-after-startup.sh` — new
section §0 (before §1 "Last session checkpoint") that:

1. `mkdir -p` workspace/relay/processed (idempotent — handles first-ever
   invocation when the folder doesn't exist).
2. Enumerates `relay-*.md` files in mtime order (oldest first) via
   `find -maxdepth 1`. Sort gives stable ordering; find handles
   absent-folder gracefully.
3. For each file: print under a clearly-labeled "📡 Relay notes" header,
   then `mv` to `processed/` after read.
4. If no unprocessed files: print onboarding nudge "(no unprocessed relay
   notes — consider invoking /relay before ending sessions going forward)".

## Atomicity

The per-file `mv` happens immediately after each file's `cat`, not
batched. This matches the result-watcher drain pattern (read → archive
per-item) and prevents two concurrent catchups from both consuming the
same relay note. If `mv` fails (cross-fs, permission), the file stays
in place + next catchup retries; stderr reports the failure for
visibility.

## Live-verified

Smoke-test run of the updated script against the current workspace:
- 1 relay note (`relay-1780468996.md`, 5.7 KB) printed verbatim under
  the new §0 header ✓
- File moved to `workspace/relay/processed/` after read ✓
- Subsequent run shows "(no unprocessed relay notes — consider invoking
  /relay ...)" ✓ (assuming no new relay written between)

Owner about to restart their session to E2E-verify catchup-from-fresh-
session works; this commit unblocks that test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(catchup): atomic claim-by-mv for relay-note consumption (Lucy #1430)

Per Lucy's PR #1430 review (2026-06-03 06:42Z): the prior implementation
listed → read → mv'd each relay note, which is TOCTOU-racy under two
concurrent catchup invocations. Both list, both read, both attempt the
mv — the mv has the right atomicity (rename(2)), but the READ duplicates.

Fix: claim-by-mv FIRST, then read the moved file. Only the catchup whose
mv succeeds reads the note; the loser's mv fails silently (concurrent
catchup already consumed it — expected, not an error). Single rename(2)
syscall on one filesystem is the atomic primitive; the read is the
critical-section consequence.

Also folds in two of Lucy's yellow minors:

1. Age annotation in the relay-note header (`(written 53m ago)`,
   `(written 3d ago — likely stale)`) — stale-note guard so a relay
   note that sat undrained for days isn't taken as current context.

2. Name-sort over mtime-sort + explicit comment: /relay --append
   updates mtime but keeps the original filename, so name-sort
   preserves the chronological creation order; mtime-sort would
   shuffle appended notes to "newest" position.

The third minor (Phase 2 cross-host fleet-sync race) is documented in
SKILL.md as a Phase 2 caveat — flagged so the next author writing the
fleet-sync impl doesn't accidentally re-introduce a cross-host claim
race when syncing pending relay-*.md files (vs only processed/).

Smoke-tested on the live workspace: injected a backdated relay note,
ran the catchup script, confirmed it was claimed + read + archived with
correct age annotation. Side effect — the smoke-test also drained an
owner-written real relay note (relay-1780471438.md from earlier PID-stamp
E2E testing) into processed/; contents preserved there as audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants