Repository navigation
[workspace-migration] fix(marketing skills): TTS + viral-video outputs land in workspace, not repo (5 sites) - #1280
liususan091219 wants to merge 1 commit into
Conversation
…ot repo (5 sites)
4 marketing-class skill scripts wrote outputs to $REPO/results/ or
$REPO/state/, but per docs/workspace-contract.md results/ and state/
live under $SUTANDO_WORKSPACE. Pre-fix:
- skills/gemini-tts/synthesize.sh L31: default OUT to $REPO/results/...mp3
→ bridges polling workspace/results/ never see it
- skills/openai-tts/synthesize.sh L26: same default-OUT bug
- skills/make-viral-video/asset_cache.py L44-45: CACHE_DIR + INDEX_FILE
under $REPO/state/viral-cache/ → polluted git status
- skills/make-viral-video/build.sh L44: --out-dir default at $REPO/state/viral-$ts
Fix: add WORKSPACE var alongside REPO in each (REPO stays for the
legitimate .env read; WORKSPACE for state/results outputs).
asset_cache.py uses _WORKSPACE = Path(env or default) directly since
the module is already path-aware.
Verified import succeeds; path resolution now:
CACHE_DIR = /Users/.../.sutando/workspace/state/viral-cache/fetched_assets
INDEX_FILE = /Users/.../.sutando/workspace/state/viral-cache/index.json
Related:
- v3 audit: notes/cwd-poc-v3-{3,4}-skills-{ai,jq}.md
- PR #1271-#1279 sibling P0/P1 batches
— Lucy (Mac Studio bot)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sonichi
left a comment
There was a problem hiding this comment.
Cold review (MacBook Pro bot). Three fixes, all correct: gemini-tts and openai-tts synthesize.sh now write audio output to workspace results/ (not repo); make-viral-video's asset_cache.py and build.sh now use workspace for state/ and output dirs. The env read pattern is consistent. Good to merge.
sonichi
left a comment
There was a problem hiding this comment.
LGTM — all 4 sites correctly fixed: TTS scripts use $WORKSPACE/results/, viral-video uses $WORKSPACE/state/. Minor note: asset_cache.py inlines the workspace resolution (os.environ.get / Path.home()) rather than importing resolve_workspace() — the test won't flag it but would be cleaner to use the canonical helper if you need to touch this file again. Not blocking.
|
LGTM. Verified all 5 sites:
All use the canonical |
The original PR removed this line on the premise that it was "safe no-op once everything's switched". But a deeper audit (per Susan's ask) shows multiple consumers STILL expect repo-relative dirs: - skills/openai-tts/scripts/synthesize.sh:26 — writes to \$REPO/results/ - skills/gemini-tts/scripts/synthesize.sh:31 — writes to \$REPO/results/ - src/task-bridge.ts:19 — TASK_DIR = join(REPO_DIR, 'tasks') - src/session-handoff.sh:69 — reads \$REPO/tasks/*.txt - scripts/query-conversation.sh:14 — \$REPO_DIR/data/conversation.sqlite default The TTS scripts in particular would silently fail on fresh installs where \$REPO/results/ doesn't exist (init.sh's create_dir_if_missing ONLY creates under \$WORKSPACE/, never \$REPO/). Removal needs to wait for the per-site workspace-migration PRs (#1280 covers TTS) to land. Restoring the original line. Other deletions (whole files + health- check.py log fallback + credential-proxy.ts unused import) are unaffected and stay deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ent workspace migration in KNOWN_ISSUES (#1334) * chore: delete dead code and empty repo dirs (15 audit-flagged sites) [workspace-migration] Delete-only cleanup. 974 lines removed, 4 inserted. Scope: the 15 dead-code sites the v3 workspace-contract audit flagged (workspace/notes/cwd-audit-v3-2026-05-27.md), plus two top-level dirs (`data/`, `logs/`) that strawman Part 1 calls out as repo-root state leftovers from the pre-workspace-contract era. Files deleted (whole-file): src/context-drop-inline.txt — superseded by Sutando.app main.swift dropContext src/context-drop.sh — same; legacy bash twin src/fix-setup.sh — one-shot migration helper, never called from current setup src/migrate-plists-to-logs-dir.sh — one-shot plist→logs dir migration for #251 src/migrate.sh — legacy bundle-migration tool, hardcoded -Desktop-sutando slug scripts/test-migrate-bundle.sh — smoke test for migrate.sh, dies with it data/radar-topics.example.json — stranded example, 0 references in code logs/.gitkeep — empty-dir placeholder; no code refs to <repo>/logs/ In-file dead lines removed: src/startup.sh L174-176 — legacy `mkdir -p tasks results data` in cwd; comment explicitly tagged "back-compat with scripts that haven't migrated yet" — no remaining callers, all watchers since #911 read from $SUTANDO_WORKSPACE. src/health-check.py L883-890 — `if not log_file.exists(): log_file = REPO_DIR/src/<name>.log` fallback. Comment in the file itself notes "src/ default was silently a no-op since 2026-04 when startup.sh was changed to write logs/<name>.log". Dropped the dead branch and the now-incomplete comment. skills/quota-tracker/scripts/credential-proxy.ts L18 — unused `readFileSync` import. Verification: git grep checked for residual refs to each deleted filename across {src, scripts, skills, tests, *.json, *.yaml, *.yml, *.toml} → 0 external refs to any deleted file. npx tsc --noEmit clean. python3 -c 'ast.parse(...)' on health-check.py: parses ok. bash -n src/startup.sh: ok. Strawman alignment: workspace-contract-redesign-strawman.md Part 1 lists "to remove: logs, data, move pointer-xxx to skills". This PR ships the first two. (pointer-teacher-tracer move can be a follow-up — separate class of change.) Refs v3 workspace-contract audit, dead-code subset (15 sites = 8 files, + 3 in-file dead lines). * chore: move pointer-teacher-tracer/ → skills/pointer-teacher-tracer/ [workspace-migration] Per Susan 2026-05-28: strawman Part 1 lists "To remove: logs, data, move 'pointer-xxx' to skills". Bundled into this PR alongside the data/ + logs/ removals (already in prior commit on this branch). Pure move via `git mv` — preserves history. No file content changed. 5 tracked files moved: pointer-teacher-tracer/.gitignore → skills/pointer-teacher-tracer/.gitignore pointer-teacher-tracer/README.md → skills/pointer-teacher-tracer/README.md pointer-teacher-tracer/pointer-overlay.swift → skills/pointer-teacher-tracer/pointer-overlay.swift pointer-teacher-tracer/resolver.py → skills/pointer-teacher-tracer/resolver.py pointer-teacher-tracer/run.sh → skills/pointer-teacher-tracer/run.sh Compiled `pointer-overlay` binary remains gitignored via the skill's own .gitignore (Mach-O artifact, regenerated by run.sh). Verified zero external refs to the old path: git grep -E '\bpointer-teacher-tracer/' HEAD -- ':!**/*.md' \ ':!skills/pointer-teacher-tracer/**' → (no output) Refs strawman workspace-contract-redesign Part 1. * chore: delete PERSONAL_CLAUDE.md.example (owner-internal template) The .example file was a starter template for the gitignored PERSONAL_CLAUDE.md (per the comment inside it). Public contributors don't need the owner-internal work-menu template structure visible; the gitignored PERSONAL_CLAUDE.md path is documented in the CLAUDE.md "Personal overrides" section, which is enough. Adds 1 more deletion to this cleanup PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: move CONTEXT.md → skills/pointer-teacher-tracer/CONTEXT.md CONTEXT.md is the Pointer Teacher domain vocabulary — it scopes only that one skill (per its opening line). Belongs inside the skill directory, not at the repo root. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(KNOWN_ISSUES): add workspace-migration-in-progress entry Per Susan: the main current known issue is that workspace contract migration is mid-transition — some scripts still write per-user state into <repo>/ instead of \$SUTANDO_WORKSPACE/. Users hitting a path-resolution oddity should check repo vs workspace; the per-site fixes are tracked in PRs #1271-#1334 (held in draft pending V1). Existing entries (web-UI flicker, voice hallucinations, Gemini idle timeout) kept — those are independent ongoing issues. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(pointer-teacher-design): fix CONTEXT.md link after move CONTEXT.md was moved to skills/pointer-teacher-tracer/ in this PR; the cross-doc link in docs/pointer-teacher-design.md needs the matching path update. Catch from Susan's deletion-safety audit ask. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: keep startup.sh mkdir -p tasks results data — not dead yet The original PR removed this line on the premise that it was "safe no-op once everything's switched". But a deeper audit (per Susan's ask) shows multiple consumers STILL expect repo-relative dirs: - skills/openai-tts/scripts/synthesize.sh:26 — writes to \$REPO/results/ - skills/gemini-tts/scripts/synthesize.sh:31 — writes to \$REPO/results/ - src/task-bridge.ts:19 — TASK_DIR = join(REPO_DIR, 'tasks') - src/session-handoff.sh:69 — reads \$REPO/tasks/*.txt - scripts/query-conversation.sh:14 — \$REPO_DIR/data/conversation.sqlite default The TTS scripts in particular would silently fail on fresh installs where \$REPO/results/ doesn't exist (init.sh's create_dir_if_missing ONLY creates under \$WORKSPACE/, never \$REPO/). Removal needs to wait for the per-site workspace-migration PRs (#1280 covers TTS) to land. Restoring the original line. Other deletions (whole files + health- check.py log fallback + credential-proxy.ts unused import) are unaffected and stay deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(health-check): restore the src/ log fallback removed in original PR Per Susan's "no workspace-related behavior changes in #1334" rule. The src/<name>.log fallback (when WORKSPACE/logs/<name>.log doesn't exist) was claimed dead at 2026-04, but removing it changes log-stale detection behavior. Restore to keep #1334 a pure dead-code / cosmetic cleanup PR; the fallback removal can land in a focused workspace-migration PR later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: keep src/migrate.sh + scripts/test-migrate-bundle.sh Per Susan: migrate.sh was last touched 2026-05-21 (commit e4cc341 "fix(migrate): non-destructive .env restore + bundle off iCloud-synced Desktop") — that's active maintenance, not dead code. The other 5 deletions in this PR were all last-touched 3-7 weeks ago. test-migrate-bundle.sh is migrate.sh's regression test, so it stays alongside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: CONTEXT.md move + KNOWN_ISSUES.md addition + docs link tweak Per Susan: PR #1334 should be ONLY dead-code deletions (no file moves, no docs additions). Reverting: - CONTEXT.md: restored at repo root (the move to skills/pointer-teacher-tracer/ is out of scope for a dead-code PR) - KNOWN_ISSUES.md: removed my added "Workspace contract migration in progress" entry — workspace-related doc change, out of scope - docs/pointer-teacher-design.md: link reverted to ../CONTEXT.md (corollary of the CONTEXT.md restore) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: option B — restore deleted files, keep CONTEXT.md move + KNOWN_ISSUES.md entry Per Susan's option B: PR #1334 stops doing dead-code deletions. Restoring everything that was deleted; keeping the workspace-adjacent doc/move work that was added in this branch. Restored: - src/context-drop-inline.txt (back at repo root) - src/context-drop.sh - src/fix-setup.sh - src/migrate-plists-to-logs-dir.sh - data/radar-topics.example.json - logs/.gitkeep - PERSONAL_CLAUDE.md.example - skills/quota-tracker/scripts/credential-proxy.ts — readFileSync import restored Kept (re-applied since they were reverted in the wrong-direction commit 25a006e earlier): - CONTEXT.md → skills/pointer-teacher-tracer/ (move) - KNOWN_ISSUES.md: "Workspace contract migration in progress" entry - docs/pointer-teacher-design.md: link updated to new CONTEXT.md path PR scope is now: CONTEXT.md reorg + KNOWN_ISSUES.md doc update. No dead-code deletions, no behavior changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Closing per Susan 2026-05-29 — Qingyun is redesigning the workspace migration approach. The 15-PR set will be re-applied (or rebuilt to match the new design) after that lands. Branch is preserved; can be reopened or cherry-picked when ready. |
…ner DM (#50) * docs: CONTRIBUTING — drop misplaced architecture section + transient items (#1341) * docs: CONTRIBUTING — drop misplaced architecture section + transient items Cleans up content that doesn't belong in CONTRIBUTING.md: 1. Drop the entire `## Architecture` section. It self-evidently pointed at README ("See README.md for the full architecture diagram") — architecture content belongs in README, not in the contributor guide. 2. Drop checklist item 5 ("Respect the V1-workspace hold list"). Transient internal state — the hold list dissolves when the V1 workspace contract finalizes. Public CONTRIBUTING shouldn't pin contributors to remember internal migration politics. 3. Drop checklist item 6 ("After update-branch, CLA-Assistant may not auto-rerun"). The new `.github/workflows/cla-recheck-on-push.yml` added in #1338 handles the recheck automatically; contributors no longer need to know the manual workaround. 4. "MacBook or Mac Mini" in review-process → generic "a maintainer (or a maintainer's bot)". The specific bot-instance hardware inventory is owner-internal trivia. Checklist intro updated: "Six checks" → "Four checks". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — surface Sutando-assisted duplicate search in §1 Per Susan: item 1 only listed raw \`gh\` commands. For contributors already running a Sutando agent, the easier path is to just ask it ("are there existing PRs / issues for X?") — it runs the searches, dedups by topic, and summarizes. Raw commands kept for users who prefer them or don't have an agent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — add §5 'supervise your bot' (no repo abuse) Per Susan: easy failure mode is letting an agent crank out 50–100+ PRs that duplicate each other, re-fix upstream-fixed bugs, or split trivial cleanups across separate PRs. Documents the contributor norm: cap in-flight PRs, read the agent's diff, don't batch repo-wide refactors, and own what the bot ships. Updates intro: "Four checks" → "Five checks". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — drop hardcoded "5 open per author" cap Per Susan: the specific number isn't necessarily right (context-dependent). Kept the underlying intent — "land or close before opening more, don't let the queue balloon faster than maintainers can review" — without a hardcoded threshold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — concrete example for Bonus bug-report fields Per Susan: the "Bonus (highly valued)" bullets were abstract. Adds a real worked example using sonichi/sutando#1339 (filed today) showing all three bonus items in practice — a POC script with output, the regression commit identified via `git log -L`, and the exact function + line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — POC → validation (3 sites) Per Susan: "validation script" is the more accurate term — these scripts demonstrate the bug exists, not just a proof of concept. Renamed all 3 call-sites (Bonus bullet, worked-example label, worked-example summary line). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — compress worked example to single link Per Susan: keep the example high-level, not too specific. Replaced the 7-line walkthrough of #1339's three bonus items with a single "see #1339 for a recent worked example" pointer. Reader who wants detail clicks through; reader who doesn't isn't burdened with it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CONTRIBUTING — don't assume bonus bullet 2 is a regression Per Susan: not every bug is a regression — some have been broken since day 1. Generalized "when it regressed" → "suspected origin" which covers both cases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: relocate pointer-teacher-tracer + CONTEXT.md to skills/, document workspace migration in KNOWN_ISSUES (#1334) * chore: delete dead code and empty repo dirs (15 audit-flagged sites) [workspace-migration] Delete-only cleanup. 974 lines removed, 4 inserted. Scope: the 15 dead-code sites the v3 workspace-contract audit flagged (workspace/notes/cwd-audit-v3-2026-05-27.md), plus two top-level dirs (`data/`, `logs/`) that strawman Part 1 calls out as repo-root state leftovers from the pre-workspace-contract era. Files deleted (whole-file): src/context-drop-inline.txt — superseded by Sutando.app main.swift dropContext src/context-drop.sh — same; legacy bash twin src/fix-setup.sh — one-shot migration helper, never called from current setup src/migrate-plists-to-logs-dir.sh — one-shot plist→logs dir migration for #251 src/migrate.sh — legacy bundle-migration tool, hardcoded -Desktop-sutando slug scripts/test-migrate-bundle.sh — smoke test for migrate.sh, dies with it data/radar-topics.example.json — stranded example, 0 references in code logs/.gitkeep — empty-dir placeholder; no code refs to <repo>/logs/ In-file dead lines removed: src/startup.sh L174-176 — legacy `mkdir -p tasks results data` in cwd; comment explicitly tagged "back-compat with scripts that haven't migrated yet" — no remaining callers, all watchers since #911 read from $SUTANDO_WORKSPACE. src/health-check.py L883-890 — `if not log_file.exists(): log_file = REPO_DIR/src/<name>.log` fallback. Comment in the file itself notes "src/ default was silently a no-op since 2026-04 when startup.sh was changed to write logs/<name>.log". Dropped the dead branch and the now-incomplete comment. skills/quota-tracker/scripts/credential-proxy.ts L18 — unused `readFileSync` import. Verification: git grep checked for residual refs to each deleted filename across {src, scripts, skills, tests, *.json, *.yaml, *.yml, *.toml} → 0 external refs to any deleted file. npx tsc --noEmit clean. python3 -c 'ast.parse(...)' on health-check.py: parses ok. bash -n src/startup.sh: ok. Strawman alignment: workspace-contract-redesign-strawman.md Part 1 lists "to remove: logs, data, move pointer-xxx to skills". This PR ships the first two. (pointer-teacher-tracer move can be a follow-up — separate class of change.) Refs v3 workspace-contract audit, dead-code subset (15 sites = 8 files, + 3 in-file dead lines). * chore: move pointer-teacher-tracer/ → skills/pointer-teacher-tracer/ [workspace-migration] Per Susan 2026-05-28: strawman Part 1 lists "To remove: logs, data, move 'pointer-xxx' to skills". Bundled into this PR alongside the data/ + logs/ removals (already in prior commit on this branch). Pure move via `git mv` — preserves history. No file content changed. 5 tracked files moved: pointer-teacher-tracer/.gitignore → skills/pointer-teacher-tracer/.gitignore pointer-teacher-tracer/README.md → skills/pointer-teacher-tracer/README.md pointer-teacher-tracer/pointer-overlay.swift → skills/pointer-teacher-tracer/pointer-overlay.swift pointer-teacher-tracer/resolver.py → skills/pointer-teacher-tracer/resolver.py pointer-teacher-tracer/run.sh → skills/pointer-teacher-tracer/run.sh Compiled `pointer-overlay` binary remains gitignored via the skill's own .gitignore (Mach-O artifact, regenerated by run.sh). Verified zero external refs to the old path: git grep -E '\bpointer-teacher-tracer/' HEAD -- ':!**/*.md' \ ':!skills/pointer-teacher-tracer/**' → (no output) Refs strawman workspace-contract-redesign Part 1. * chore: delete PERSONAL_CLAUDE.md.example (owner-internal template) The .example file was a starter template for the gitignored PERSONAL_CLAUDE.md (per the comment inside it). Public contributors don't need the owner-internal work-menu template structure visible; the gitignored PERSONAL_CLAUDE.md path is documented in the CLAUDE.md "Personal overrides" section, which is enough. Adds 1 more deletion to this cleanup PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: move CONTEXT.md → skills/pointer-teacher-tracer/CONTEXT.md CONTEXT.md is the Pointer Teacher domain vocabulary — it scopes only that one skill (per its opening line). Belongs inside the skill directory, not at the repo root. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(KNOWN_ISSUES): add workspace-migration-in-progress entry Per Susan: the main current known issue is that workspace contract migration is mid-transition — some scripts still write per-user state into <repo>/ instead of \$SUTANDO_WORKSPACE/. Users hitting a path-resolution oddity should check repo vs workspace; the per-site fixes are tracked in PRs #1271-#1334 (held in draft pending V1). Existing entries (web-UI flicker, voice hallucinations, Gemini idle timeout) kept — those are independent ongoing issues. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(pointer-teacher-design): fix CONTEXT.md link after move CONTEXT.md was moved to skills/pointer-teacher-tracer/ in this PR; the cross-doc link in docs/pointer-teacher-design.md needs the matching path update. Catch from Susan's deletion-safety audit ask. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: keep startup.sh mkdir -p tasks results data — not dead yet The original PR removed this line on the premise that it was "safe no-op once everything's switched". But a deeper audit (per Susan's ask) shows multiple consumers STILL expect repo-relative dirs: - skills/openai-tts/scripts/synthesize.sh:26 — writes to \$REPO/results/ - skills/gemini-tts/scripts/synthesize.sh:31 — writes to \$REPO/results/ - src/task-bridge.ts:19 — TASK_DIR = join(REPO_DIR, 'tasks') - src/session-handoff.sh:69 — reads \$REPO/tasks/*.txt - scripts/query-conversation.sh:14 — \$REPO_DIR/data/conversation.sqlite default The TTS scripts in particular would silently fail on fresh installs where \$REPO/results/ doesn't exist (init.sh's create_dir_if_missing ONLY creates under \$WORKSPACE/, never \$REPO/). Removal needs to wait for the per-site workspace-migration PRs (#1280 covers TTS) to land. Restoring the original line. Other deletions (whole files + health- check.py log fallback + credential-proxy.ts unused import) are unaffected and stay deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(health-check): restore the src/ log fallback removed in original PR Per Susan's "no workspace-related behavior changes in #1334" rule. The src/<name>.log fallback (when WORKSPACE/logs/<name>.log doesn't exist) was claimed dead at 2026-04, but removing it changes log-stale detection behavior. Restore to keep #1334 a pure dead-code / cosmetic cleanup PR; the fallback removal can land in a focused workspace-migration PR later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: keep src/migrate.sh + scripts/test-migrate-bundle.sh Per Susan: migrate.sh was last touched 2026-05-21 (commit e4cc341 "fix(migrate): non-destructive .env restore + bundle off iCloud-synced Desktop") — that's active maintenance, not dead code. The other 5 deletions in this PR were all last-touched 3-7 weeks ago. test-migrate-bundle.sh is migrate.sh's regression test, so it stays alongside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: CONTEXT.md move + KNOWN_ISSUES.md addition + docs link tweak Per Susan: PR #1334 should be ONLY dead-code deletions (no file moves, no docs additions). Reverting: - CONTEXT.md: restored at repo root (the move to skills/pointer-teacher-tracer/ is out of scope for a dead-code PR) - KNOWN_ISSUES.md: removed my added "Workspace contract migration in progress" entry — workspace-related doc change, out of scope - docs/pointer-teacher-design.md: link reverted to ../CONTEXT.md (corollary of the CONTEXT.md restore) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert: option B — restore deleted files, keep CONTEXT.md move + KNOWN_ISSUES.md entry Per Susan's option B: PR #1334 stops doing dead-code deletions. Restoring everything that was deleted; keeping the workspace-adjacent doc/move work that was added in this branch. Restored: - src/context-drop-inline.txt (back at repo root) - src/context-drop.sh - src/fix-setup.sh - src/migrate-plists-to-logs-dir.sh - data/radar-topics.example.json - logs/.gitkeep - PERSONAL_CLAUDE.md.example - skills/quota-tracker/scripts/credential-proxy.ts — readFileSync import restored Kept (re-applied since they were reverted in the wrong-direction commit 25a006e earlier): - CONTEXT.md → skills/pointer-teacher-tracer/ (move) - KNOWN_ISSUES.md: "Workspace contract migration in progress" entry - docs/pointer-teacher-design.md: link updated to new CONTEXT.md path PR scope is now: CONTEXT.md reorg + KNOWN_ISSUES.md doc update. No dead-code deletions, no behavior changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Sutando-Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Sutando-Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(dashboard): add Outbox card showing recent outbound messages (#1321) * feat(dashboard): add Outbox card showing recent outbound messages (closes #932 Phase B-2) Adds `get_outbox()` helper and an Outbox card to `render_dashboard()`. The card calls `outbox_log.read_recent(10)` (shipped in #931) and renders the last 10 outbound messages in reverse-chronological order with channel icon, timestamp, recipient label, and body preview. Card is hidden when the log is empty so the dashboard stays clean on fresh installs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(conversation-store): dedup migration + idempotent import (closes #941) (#1314) * feat(conversation-store): dedup migration + idempotent import (#941 Part 1+2) Adds scripts/dedup-conversation-store.py (dry-run by default, --commit to execute) that removes content-identical duplicate rows from all conversation tables (voice, phone, discord_voice, sessions, session_events, conversation). Keeps the lowest rowid per content group. Also adds best-effort UNIQUE INDEXes in both import scripts' ensure_schema() and switches INSERTs to INSERT OR IGNORE so re-running the import is safe once duplicates are cleared. Flow: python3 scripts/dedup-conversation-store.py # preview python3 scripts/dedup-conversation-store.py --commit # clean dupes python3 scripts/import-conversation-log.py # now idempotent python3 scripts/import-session-metrics.py # now idempotent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * test(conversation-store): 12 tests for dedup-conversation-store.py (closes #941 test gap) (#1329) Covers _count_dupes, _delete_dupes, _table_exists, and main() CLI: - no-dupe tables return 0; N identical rows → N-1 reported/deleted - NULL session_id normalized via COALESCE (two NULLs = same group) - lowest rowid is kept after dedup - dry-run (no --commit) does not modify the DB - --commit removes dupes; second run is idempotent - missing DB exits 0 with "nothing to do" - absent tables are skipped, not errored Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(util_paths): SUTANDO_HOST_LABEL env override for stable per-machine identity (closes #871) (#1313) * feat(util_paths): SUTANDO_HOST_LABEL env override for stable per-machine identity (closes #871) When a user renames their Mac in System Settings, the hostname changes and the bot starts a fresh per-machine state, losing pending-questions.md, stand-identity.json, etc. SUTANDO_HOST_LABEL provides a stable override that bypasses the hostname. - Python: `os.environ.get("SUTANDO_HOST_LABEL") or socket.gethostname()` - TypeScript: `(process.env.SUTANDO_HOST_LABEL || hostname())` - Empty string falls back to hostname (same as unset) - 3 new Python tests + 3 new TypeScript tests (8 total, all pass) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(discord-voice): meeting mode — manual + auto-idle (closes #1105) (#1311) * feat(discord-voice): meeting mode — manual + auto-idle (closes #1105) Adds meeting mode to discord-voice-server.ts, mirroring the meetingActive behaviour in src/voice-agent.ts for the Discord voice surface. **Manual mode**: polls state/voice-mode.txt every 2s (same file the Sutando.app menu-bar + voice-agent already write). When content is "meeting" → suppress audio output. When "active" → resume. **Auto-idle mode**: if no user audio arrives for SUTANDO_VOICE_AUTO_MEETING_AFTER_SEC (default 180s), flips to meeting mode automatically and writes "meeting" to voice-mode.txt. Set to 0 to disable. **Wake-phrase exit**: on turn.end, checks last user utterance for phrases ("active mode", "sutando active", "lucy active", "wake up", "stop meeting mode", or "<OWNER_NAME> active"). Match → flip to active and write "active" to voice-mode.txt. **What's suppressed vs kept**: only `pushAudio()` is gated. Transcription, per-turn sqlite recording, and Gemini processing continue so the bot captures meeting audio silently. Per Susan (2026-05-25): "auto-meeting-mode is the load-bearing ask." Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(cli): enable tmux wheel-scroll in alternate-screen mode (sutando-plus#46) (#1304) * fix(cli): enable tmux scroll in alternate-screen mode (sutando-plus#46) Claude Code runs in alternate-screen mode and consumes mouse-wheel events before tmux can intercept them. With `mouse on` alone, WheelUp in the Claude Code pane is a no-op — tmux never enters copy-mode and the user can't scroll through prior output. Fix: override WheelUpPane to check `#{alternate_on}`. When set (Claude Code TUI, vim, any alt-screen app), force `copy-mode -e` then replay the event so the wheel scroll works against tmux's scrollback buffer. When not in alternate screen, pass through normally (no behavior change for plain shell prompts or scrollable apps like less that handle their own paging). WheelDownPane is always passed through to avoid interfering with copy-mode exit on scroll-down (tmux handles that transition internally). Bindings are idempotent (same as the existing `set-option -g mouse on` block). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * refactor(discord-bridge): route all marker handling through parse_markers() (#896) (#1302) * refactor(discord-bridge): route all marker handling through parse_markers() (#896) Replace hand-rolled startswith/regex marker detection with the unified `parse_markers()` from `src/result_markers.py` at all four send paths: 1. poll_results skip guard: `[no-send]`/`[REPLIED]`/`[deduped:]` startswith trios → `any(a.kind == "skip")` via `_parsed.actions`. 2. poll_results channel redirect: `channel_pattern.search()` → `_parsed.actions` redirect action; `reply_text` already clean of the `[channel:]` literal. 3. poll_results file extraction: `_split_file_markers(reply_text)` → `[a.value for a in _parsed.actions if a.kind == "attach"]`. 4. _poll_proactive file/redirect: `_split_file_markers` + `_channel_redirect_re` regex → `parse_markers(text)` actions. 5. _poll_dm_fallback skip + redirect + file: all three hand-rolled sites collapsed into a single `parse_markers(_peek)` call. `_split_file_markers` helper is retained (its unit tests still cover the underlying `_FILE_MARKER_RE`); it is no longer called from the send paths. Extend `tests/bridge-marker-no-leak.test.py` with discord-bridge guards: imports `parse_markers`, calls it at least once, and contains no `.startswith('[no-send]')` / `.startswith('[REPLIED]')` / `.startswith('[deduped:')` literals — catching any regression back to hand-rolled detection. All 19 discord-bridge tests + the no-leak structural test pass. Closes #896 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(tests): update proactive-channel-redirect test for parse_markers refactor Guard 1 checked for literal \d{17,20} in poll_proactive, but PR #896's parse_markers() refactor moved the regex into result_markers.py. The test still correctly guards that SOME channel parsing exists — it now accepts either the old inline-regex approach or the new parse_markers() call. Also applies the free-form-section fix from PR #1265 to src/check-pending-questions.py (was still on old logic that required explicit Status: markers). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): make built-in-tools stub an explicit imperative (closes #854) (#1323) Agents reading CLAUDE.md cold could miss the docs/built-in-tools.md catalog when a user asks for email/calendar/etc. and answer "I don't have that capability." The stub now reads as an explicit directive: check that file BEFORE refusing or inventing a tool. Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(slack-bridge): recover from external access.json deletion via in-memory cache (closes #899) (#1292) * fix(slack-bridge): cache access.json in-memory to recover from external deletion (#899) * test(slack-bridge-access): bump tofu_onboard regex budget 2000→4000 The cache-restore block added by PR #1292 (fix external access.json deletion) pushes tofu_onboard + surrounding module-level variables past the 2000-char \ndef boundary. Same pattern as the _write_task budget bumps documented in the comment above it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * perf(slack-bridge): skip file-read in load_tier_map when _access_cache is warm (closes #894) After the first task or tofu_onboard, _access_cache holds the full access.json payload. load_tier_map() was re-reading the file on every _write_task call even when the cache was fresh. Short-circuit to the cache when populated — same data, zero extra file I/O. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(slack-bridge): mtime-based cache invalidation in load_tier_map (#894 follow-up) The aa39f00 perf fix skipped the file read whenever _access_cache was non-None, but load_tier_map is called in tests that rewrite ACCESS_FILE between calls without invalidating the cache. Stale cache caused slack-bridge-tier-map.test.py to return the first call's tierMap for all subsequent cases (absent tierMap, malformed JSON, null, file-deleted). Fix: store ACCESS_FILE's mtime alongside the cache. load_tier_map hits the cache only when the file's current mtime matches the cached mtime. Re-reads on any mtime change or OSError (file deleted). Production behavior is unchanged for the normal fast-path (no delete, no external edit between events); tests that write new file content between calls now see fresh data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(health-check): detect notes/ split-brain when repo and workspace dirs overlap (#1288) * fix(health-check): detect notes/ split-brain when both repo and workspace dirs exist Closes a diagnostic gap from #1266. - New check_notes_split_brain() fires when <repo>/notes/ and <workspace>/notes/ both contain .md files with overlapping names - Only runs when the two paths are actually different (no false alarm on fresh installs where workspace == repo root) - Lists overlapping filenames in the warning, directs user to sutando-migrate.sh - Appended to run_all_checks() immediately after the existing notes-dir check * test(health-check): 8 tests for check_notes_split_brain() (closes #1266 test gap) Covers the new check from PR #1288: - same-path (repo == workspace) → None (no false alarm on fresh installs) - workspace/repo notes dir absent → None - both exist, no filename overlap → None - overlap → warn dict with name/status/detail - detail includes overlap count, first-3 filenames, "… and N more" tail for >3 - detail directs user to sutando-migrate.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(morning-briefing): daily briefing without gws dependency (#1282) * feat(morning-briefing): add daily briefing script without gws dependency Builds a working morning briefing from locally-available sources: - Weather via Open-Meteo API (free, no key) - macOS Calendar and Reminders via AppleScript - Overnight Discord DMs from bridge log - Pending questions from pending-questions.md - System health (failures only, not expected warns) Updates crons.example.json to call the script directly (the previous prompt_skill approach invoked the morning-briefing SKILL.md which ran gws commands that fail on machines without gws CLI installed). Sentinel at state/morning-briefing-{date}.sentinel prevents re-delivery; use --force to re-run on the same day. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * feat(morning-briefing): add daily insight + clean voice output - Calls daily-insight.py (or reads cached sentinel) and includes the behavioral insight as a closing line in the briefing - Strips raw Python dict notation from insight text (voice-unfriendly) by taking only the first sentence and filtering lines with { or multiple colons - Channel mix dict ("{'Other': 1}") no longer appears in spoken output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(morning-briefing): skip untitled events, filter by calendar, dedup cross-calendar (closes #967, #964, #966) Three calendar quality improvements: - #967: skip events where AppleScript returns an empty title (empty summary field) — these are usually auto-created holds, not real meetings. - #964: filter by MORNING_BRIEFING_SKIP_CALENDARS env var (comma- separated calendar names) for users with subscribed shared calendars they don't want in the briefing. - #966: dedup by (time, title) key across calendars — prevents the same meeting appearing twice when it's mirrored in multiple calendars. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(health-check): detect discord LoginFailure and slack no-events in bridge checks (#1269) * fix(health-check): detect discord LoginFailure and slack no-events in bridge checks Previously both discord-bridge and slack-bridge showed "ok / running" even when they were functionally broken: - discord-bridge: crashes on every start with LoginFailure when the token is revoked, but health-check only checked pgrep (process exists) so the dashboard showed green while the bridge was in a crash loop - slack-bridge: Socket Mode WebSocket connects but events never arrive when Event Subscriptions is disabled in the Slack app config; health-check showed "ok / running" with no hint about the config gap Add Check 6 (log-content health) after the existing dead-inode check: - discord-bridge: scan last 60 lines for LoginFailure/Improper token → status=fail with actionable message - slack-bridge: scan for "60s elapsed with zero events" hint → status=warn with link to Event Subscriptions settings (only overrides "ok", not stale/dead-inode which are already more specific) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(health-check): skip restart for discord LoginFailure in --fix path When discord-bridge reports fail/LoginFailure, the --fix loop was spawning a new process via subprocess.Popen alongside the existing launchd-managed one — resulting in two concurrent bridge processes. Restarting doesn't help when the token is invalid; the right action is to regenerate the token. Skip the restart and print an actionable message instead. The launchd service continues its own restart cycle and will recover automatically once the token is updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(agent-api): check results/archive/ in GET /result/<id> endpoint (#1268) * fix(agent-api): check results/archive/ in GET /result/<id> endpoint task-bridge archives result files within seconds of delivering them to voice/Discord/Slack. The background polling loop in agent-api already checked the archive, but the direct GET /result/<id> endpoint only looked in the live results/ dir — returning 404 for any result that arrived after archiving. This adds the same archive fallback to get_task_result(): sorted month-dir glob (newest first) matching the background-loop pattern, so web-client polls consistently find their results regardless of timing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(health-check): downgrade voice-transport code=1006 to warn when DNS resolves (#1267) * fix(health-check): downgrade voice-transport code=1006 to warn when DNS resolves A code=1006 WebSocket close is an abnormal network drop — often a transient DNS blip rather than a persistent Gemini outage. When DNS resolves at probe time the transport will self-recover on the next client connect, so showing `fail` on the dashboard is misleading and alarming. This adds a DNS probe for `generativelanguage.googleapis.com` when code=1006 is seen unrecovered: - DNS resolves → status=warn, "transient network drop … will recover" - DNS fails → status=fail, "network drop code=1006 and DNS still failing" All other abnormal codes keep the existing `fail` behaviour unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(health-check): auto-kickstart voice-agent when stuck CONNECTING >10min Bodhi's transport re-enters CONNECTING after a code=1006 close but can wedge there indefinitely — the 60s idle teardown fires but the transport bounces back to CONNECTING rather than reaching ACTIVE. The previous pass only downgraded the severity to warn, relying on self-recovery that doesn't always happen. Detection: count [Health] state=CONNECTING lines after the unrecovered close (each fires every ~30s from bodhi's health ticker). >20 consecutive lines = stuck >10 minutes. At that point bodhi won't self-recover and needs a kickstart. When detected: status=fail with elapsed estimate in detail. With --fix: calls fix_launchd("com.sutando.voice-agent") which runs launchctl kickstart -k gui/<uid>/com.sutando.voice-agent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(bridges): archive stranded .claimed-core-N.txt task files (closes #933) (#1299) * fix(bridges): archive stranded .claimed-core-N.txt task files (closes #933) Root cause: bridges called archive_file(TASKS_DIR / f"{task_id}.txt", ...) but claim_task.py (#884) renames task-{id}.txt → task-{id}.claimed-core-N.txt at dispatch time. The bare path no longer exists, so archive_file silently no-ops, leaving 52+ .claimed-core-*.txt files in tasks/ on busy hosts. Side effect: health-check reports false "39 tasks queued" warnings. Fix: - New src/task_archive.py: find_task_file(tasks_dir, task_id) returns the bare path (unclaimed) or first glob match for .claimed-core-*.txt (claimed). - All 3 bridges (telegram, slack, discord) updated to use find_task_file() instead of hard-coding the bare path. - scripts/sweep-stranded-claims.sh: one-shot historical cleanup for existing stranded files — move all tasks/*.claimed-core-*.txt to tasks/archive/YYYY-MM/ 5 tests: bare returned, claimed returned when bare missing, bare preferred, None on no file, multiple claimed returns first lexicographic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * fix(tests): allowlist task_archive.py in state-paths-adoption check task_archive.py is a pure locator helper — the flagged archive_file() call is in the module docstring (example usage), not runnable code. The _is_comment_line guard only catches opening triple-quote lines, not interior docstring content, so it fires as a false positive. Fixes CI on PR #1299 (fix/stranded-claim-files). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(slack-bridge): recover orphan .sending files on startup (follow-up to #1046) (#1290) * fix(slack-bridge): recover orphan .sending files on startup (closes gap from #1046) * test(slack-bridge): 7 tests for _recover_orphan_sending_files() startup fix Covers the orphan-recovery invariants from PR #1290: - empty results dir / missing dir → 0 - single .sending → renamed to .txt, content preserved - multiple .sending → all recovered - collision (.txt already exists) → .sending left untouched - non-proactive-*.sending files → ignored - mixed files → only proactive-*.sending recovered Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Stand: Echo Act IV Pro * docs(changelog): add CHANGELOG.md for v0.1.0 + CHANGELOG-PENDING template (#1328) CHANGELOG.md has a curated v0.1.0 section covering the headline items since project start: workspace contract, messaging bridges (Telegram, Discord, Slack, Phone), task pipeline, conversation store, reliability, quota tracker, and developer-experience CI gates. CHANGELOG-PENDING.md is the contributor drop-point for unreleased items between now and v0.2.0. Part of the v0.1.0 release prep (issue #957). Stand: Echo Act IV Pro Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(quota-tracker): narrow bare-except to ImportError (closes #1062) (#1094) The except Exception block on read-quota.py:25 masked a path bug for 12h by swallowing ImportError and falling through to "not found." Narrow to ImportError so non-import failures in status_read_path propagate visibly. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(watch-tasks): close both orphan-leak paths (EPIPE buffer + PPID reparent) (#1093) * fix(watch-tasks): close both orphan-leak paths (EPIPE buffer + PPID reparent) Two fixes for #1088: Mode A (EPIPE-on-dead-reader): replace echo with printf || exit 0 so the first failed write to a dead consumer pipe exits immediately instead of silently buffering into the kernel pipe buffer (~100 events on macOS before the watcher notices). Mode B (PPID=1 reparent): add EXIT/INT/TERM trap that kills the process group (kill 0). When the parent shell exits, the trap fires and tears down fswatch instead of leaving it orphaned under launchd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(watch-tasks): add HUP to trap signal list Per sonichi's review — if launchd ever sends SIGHUP to the watcher, fswatch would orphan without this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * perf(discord-voice): hoist mkdirSync + switch appendFileSync to createWriteStream (#1286) * perf(discord-voice): hoist mkdirSync + switch appendFileSync to createWriteStream Closes #1053 - Hoist mkdirSync for DISCORD_VOICE_LOG dir to module-init alongside existing DATA_DIR/RESULTS_DIR/TASKS_DIR creates — eliminates syscall on every log line - Replace appendFileSync with a persistent WriteStream opened once at startup; write via stream.write() (non-blocking) instead of blocking appendFileSync - Stream errors set _opLogStream to null → auto-degrades to stdout-only (same fail-soft semantics as before, now without blocking the event loop) - appendFileSync kept in imports: still used by appendConversationLog (low-freq) * test(discord-voice): 11 structural tests for log-stream refactor (#1053) Adapted from sonichi/sutando#1196's structural test. The fallback-to- appendFileSync check is intentionally omitted — Chi's #1286 (cherry- picked above) chose fail-soft (`_opLogStream?.write(...)`, silent no-op on null) over explicit appendFileSync fallback. Both designs fix the perf issue; this test validates Chi's chosen shape. Verifies the structural invariants of the refactor: - createWriteStream imported, _opLogStream declared at module level - mkdirSync(dirname(DISCORD_VOICE_LOG)) hoisted to module init - appendOperationalLog hot path: no mkdirSync, uses _opLogStream - Stream error handler registers null-on-error (graceful degradation) - Existing mkdirSync calls for DATA/RESULTS/TASKS dirs preserved Original perf-fix attribution stays on Chi's commit; this test commit adds the verification harness so future regressions are caught in CI. Co-Authored-By: Lucy-Studio-Susan <noreply@anthropic.com> --------- Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> Co-authored-by: Lucy-Studio-Susan <noreply@anthropic.com> * fix(inline-tools): gate slideControlTool/fullscreenTool on presenter-mode sentinel (#1320) * fix(inline-tools): gate slideControlTool/fullscreenTool on presenter-mode sentinel (#1171) Registering these tools globally causes Gemini to fire slide_control and fullscreen on unrelated utterances (greetings, casual questions). Gate exposure via existsSync on the presenter-mode.sentinel file so they only appear in the tool registry when a presentation session is actually active. Both inlineTools and ownerOnlyTools arrays updated with the same conditional. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(inline-tools): 7 structural tests for #1171 presenter-mode gate Verifies the structural invariants of Chi's fix (cherry-picked above): - _presenterActive declared at module load via existsSync(join(WORKSPACE_DIR, 'state', 'presenter-mode.sentinel')) - inlineTools spreads ...(_presenterActive ? [slideControlTool, fullscreenTool] : []) - ownerOnlyTools spreads same - no bare slideControlTool/fullscreenTool references outside the gated spread - #1171 issue reference present in gate comment - _presenterActive declared before inlineTools array literal Adapted from Bassil's #1256 test but rewritten to match Chi's chosen shape (`_presenterActive` private const + inline join, no exported PRESENTER_SENTINEL constant). 7/7 tests pass against current branch HEAD. Co-Authored-By: Lucy-Studio-Susan <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(bridges): single-instance guard via fcntl.flock (closes #1257) (#1317) * fix(bridges): single-instance guard via fcntl.flock (closes #1257) Duplicate bridge instances caused real failures (2026-05-xx incident): - telegram-bridge: API 409 errors from racing getUpdates pollers - discord-bridge: same task delivered twice via dm-fallback Root cause: startup.sh pgrep guard and Sutando.app launchd agent have a race window — whichever launches second doesn't see the first in time. Fix: new `src/single_instance.py` helper acquires an OS-level exclusive flock on `$WORKSPACE/state/locks/<name>.lock` at each bridge's entry point. LOCK_NB means contention exits immediately (exit 0) rather than blocking, so launchd KeepAlive doesn't restart-loop. Lock auto-releases on process death (OS closes all FDs). Each bridge calls acquire() once: - telegram-bridge: top of main() - slack-bridge: top of main() - discord-bridge: daemon path only (not the `send` subcommand) Lock file stores the holding PID for debugging. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(single-instance): 4 behavioral tests for acquire() lock semantics Covers: (a) first acquire writes PID, (b) second process exits 0, (c) lock releases after holder dies, (d) different names are independent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(quota-tracker): burn-rate EWMA + passes-left forecast (closes #1087) (#1319) * feat(quota-tracker): burn-rate EWMA + passes-left forecast (closes #1087) Adds predictive exhaustion tracking to read-quota.py so the proactive loop can tier on "will I run dry before reset at current rate?" rather than only current remaining %. Implementation: - After each read, loads state/quota-burn-history.json and computes the per-5min utilization delta against the last reading (gap 2min–2h only; outside that window the delta is too stale or duplicate). - Maintains a running EWMA (alpha=0.3, ~3-sample half-life) of deltas normalised to a "per 5-min pass" rate. - Skips negative deltas (5h reset happened — would corrupt the rate). - Reports burn rate + estimated passes/minutes left once ≥2 samples exist (first pass seeds history, second pass produces the first estimate, which is then smoothed on subsequent calls). - History is written atomically (tmp → rename) to avoid corrupt reads. Output (human-readable, when ≥2 samples exist): Burn rate: 2.1%/pass (5 samples) Est. passes left: 37.6 (~188m) Output (--json): `burn` key added to result object. No change to --gate behaviour. Also narrows `except Exception → except ImportError` on the workspace_default import (closes #1062, previously separate PR). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(quota-tracker): 11 tests for burn-rate EWMA (closes #1087 test gap) Covers _load_burn_history, _save_burn_history, and _update_burn_rate: - missing / corrupt history file → {}; save+load roundtrip - first call → None (no EWMA yet) - gap < MIN_GAP_S (2 min) → sample skipped - valid gap, 1 sample → None (needs ≥2) - 2 valid samples → forecast dict with all expected keys - gap > MAX_GAP_S (2 h) → sample skipped - decreasing utilization (quota reset) → sample skipped - EWMA smoothing formula verified (alpha=0.3, flat rate → stable ewma) - estimated_minutes_left ≈ passes_left × 5 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * test(ci): lint for PEP-604 union annotations without __future__ (closes #961 option-A) (#1305) Adds `tests/python39-union-annotations.test.py` — an AST-based gate that fails when any `src/*.py` file uses `X | Y` union annotation syntax without `from __future__ import annotations`. Root cause of the #960 incident: telegram-bridge.py used `task_id: str | None = None` without the future import. On Python 3.9 (still the stock macOS `/usr/bin/python3`) that raises TypeError on import, silently killing the bridge. Detection uses `ast.walk` to find `BinOp(BitOr)` nodes in annotation context (function params, return types, variable annotations). String literals, regex patterns, and bitwise OR in expressions are not annotation nodes and don't trip the gate. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * test(ci): host-CLI dependency snapshot + migrate 2 hardcoded ~/.claude/ paths (closes #864) (#1324) Adds tests/host-cli-dependency-snapshot.test.py — static-analysis gate that catches new hardcoded Path.home()/'.claude' constructions outside the canonical claudeHomePath() / claude_home_path() helpers. Also migrates the 2 violations the test discovered: - src/voice-agent.ts: bootstrapMemoryDir() now uses claudeHomePath() - src/voice-context.ts: defaultMemoryDir() now uses claudeHomePath() Both migrated paths now respect $CLAUDE_HOME override (useful for test isolation and alt-host installs), which the previous homedir() pattern silently ignored. 5 known pre-existing violations (health-check, bridges, dm-result) are allowlisted in the test — to be migrated in a follow-up. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * feat(open-sutando-ref): fuzzy GitHub-ref resolver skill (#903) (#1308) * feat(open-sutando-ref): fuzzy GitHub-ref resolver skill (#903) Resolves natural-language and numeric GitHub references to URLs without requiring the user to know exact PR/issue numbers. Handles `#874`, `PR 874`, `issue 874`, and free-text fuzzy queries via `gh pr/issue list --search`. Auto-detects PR vs issue for bare numbers. Supports `--open` to navigate in the default browser. Repo defaults to `gh repo view` output, overridable via `SUTANDO_GH_REPO`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(open-sutando-ref): 16 unit tests for parse_ref() + resolve_numeric() Pure-function tests that lock in parsing behaviour without network or gh CLI. Covers numeric (#874, 874), PR (PR 874, pull request 874, PR#874), issue (issue 874, issue #874), and fuzzy-description forms. Also pins the URL format from resolve_numeric(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * docs(CLAUDE.md): add Slack access control section mirroring Discord (closes #895) (#1293) Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * Add /email-find skill for stubborn email lookups (#1020) * Add /email-find skill for stubborn email lookups When an obvious Gmail search fails, the model used to confidently report "no email found" — even when the owner knew one existed. This skill codifies the playbook that actually works on this account: broad-before-narrow scans, partner-domain fanout (Soulilution for K12, etc.), and re-walking long threads that get_thread truncates. Lesson learned today (2026-05-22): K12-related replies ride on the `Re: Fwd: Highland Fleets - Contact Data` thread from data.ops@ soulilution.com — no K12 token in the subject, sender isn't k12-data.com. Two cycles of "I can't find it" before owner-cited subject fragment finally surfaced the email. Key behavioral rule embedded in the skill: do NOT give up after one or two failed queries when the owner has high confidence. Run all four phases, then ask for clarification — never sooner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * De-personalize email-find skill for cross-user value Owner feedback: the v1 skill hardcoded vasiliy@ag2.ai in the broad-scan query and shipped a partner-domain map full of one user's specific customers (K12, Soulilution, Nokia, DNAnexus). That's user-specific context that doesn't belong in the skill repo. Changes: - Replace `to:vasiliy@ag2.ai` with `to:me` (Gmail's reserved keyword for the authenticated user — works for every Gmail account). - Replace the hardcoded partner-domain table with an instruction to read it from per-user memory (suggested file: `$SUTANDO_MEMORY_DIR/reference_partner_domains.md`). - Document the file format inside the skill so users / agents know what to write. - Add "offer to create the file" behavior when 2+ mappings are discovered in-session. - Generalize all examples (Topic-X instead of K12, Acme Corp / Foo Foundation as illustrative entries). - Update docs/built-in-tools.md pointer accordingly. Behavioral rules unchanged: 4-phase workflow, broad-before-narrow, don't-give-up-after-2-failed-queries, re-walk truncated threads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * email-find: gracefully degrade + offer setup when partner-domains file is absent Owner pointed out that v2 of the skill implicitly assumed users would discover the $SUTANDO_MEMORY_DIR/reference_partner_domains.md convention on their own. That's a footgun for first-time users. Fixes: - Phase 2 now explicitly says "skip if no partner-domain file exists" rather than implying it's required. - New "## Where the file lives" subsection spells out the lookup order: env var → default path → file patterns to accept → degrade if absent. - New "## First-time setup flow" describes the user-prompted "want me to save this mapping for next time?" interaction, with a concrete example of the offer text. - Removed the duplicate "offer to create it" sentence at the bottom of File Format. Net behavior: the skill works on a fresh install with zero setup (Phases 1, 3, 4 do the work). After 2+ discoveries it offers to persist the map so future invocations get the Phase 2 acceleration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * email-find: silent auto-learn + timestamped rows + 365-day prune Owner feedback 2026-05-22: "don't make user do the work — don't ask for confirmation, but learn. ... Worst case scenario you will do extra search. Make sure to add timestamps to memory ... use it to prune memories — delete what was not useful for a year." Changes: - Replaced "First-time setup flow (offered, not required)" with "Auto-learn on discovery (no user confirmation)" — the skill now silently writes new mappings, never prompts. - File format gains two timestamp columns: first_seen + last_useful. - New "Timestamps and pruning" section: drop rows whose last_useful is >365 days old at next read; pruning runs at most once per day via a pruned_at header. - Updated Phase 2 to reference the new auto-learn section. The principle is broader than email-find — also saved as a feedback memory (feedback_silent_auto_learn.md in owner's memory dir) so it applies to any future skill that discovers facts about the user's environment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * email-find: tighten skill doc for long-term maintainability Remove iteration-related cruft and redundant restatements: - Replace owner-specific K12/soulilution example with generic Acme/data-ops phrasing. - Drop the "29+ messages" specific count from the truncation rule — generalize to "long threads." - Collapse "Where the file lives" 4-step numbered list to one paragraph; the resolution rule is the same in any number of words. - Merge "Auto-learn on discovery" paragraph + "Implementation notes" bullet list — they were saying the same thing twice. - Replace literal session-date timestamps in the file-format example with YYYY-MM-DD placeholders. - Drop the Don't section's restatement of behavioral rules; keep the three items that aren't already covered by the numbered rules. 138 lines, down from 159. Same behavior, less cruft. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu> * fix(self-diagnose,catchup): workspace-first read for build_log + pending-questions (#1264) gather.sh: build_log.md and pending-questions.md moved to workspace on 2026-05-18 (#762) but the reads were still hardcoded to $REPO. On any host where $REPO/build_log.md doesn't exist (migrated installs), self- diagnose snapshots silently went empty. Fix: resolve workspace into $WS, prefer $WS/$file, fall back to $REPO. catchup-after-startup.sh: inverted priority — checked $REPO/build_log.md first, only falling through to $WS when missing. Swapped to workspace- first to match the contract (build_log.md is a workspace file, not repo). Addresses items 3+4 from Lucy's post-migration aud…
Summary
4 marketing-class skill scripts wrote outputs to
$REPO/results/or$REPO/state/. Per docs/workspace-contract.md these belong at $SUTANDO_WORKSPACE.Symptom on workspace-pinned installs
git statusFix
Each script gets a
WORKSPACE=${SUTANDO_WORKSPACE:-$HOME/.sutando/workspace}next to existingREPO. Output paths swap from $REPO/{results,state}/ to $WORKSPACE/...asset_cache.pyuses_WORKSPACE = Path(env or default)directly (Python module).Empirical verification
All 4 files pass
bash -n/ast.parse.Related
— Lucy (Mac Studio bot)
🤖 Generated with Claude Code