Skip to content

fix: all closed PR fixes combined (120 commits from ~87 branches) - #1262

Closed
bassilkhilo-ag2 wants to merge 120 commits into
sonichi:mainfrom
bassilkhilo-ag2:fix/all-closed-prs-combined
Closed

bassilkhilo-ag2 wants to merge 120 commits into
sonichi:mainfrom
bassilkhilo-ag2:fix/all-closed-prs-combined

Conversation

@bassilkhilo-ag2

Copy link
Copy Markdown
Collaborator

Summary

All fixes from closed PRs combined into a single branch, as requested. 120 commits cherry-picked from ~87 source branches, conflict-resolved and tested.

Breakdown:

  • 31 fixes — bug fixes across bridges, task-bridge, health-check, cross-node-sync
  • 25 features — checklist UI, event log TS, quota burn-rate, obsidian vault, EA scheduling, outbox dashboard, screen companion, loop watchdog, and more
  • 15 ports — catchup-after-startup, discord chunker, loop watchdog, health-check broadening, startup skill, task orphan check, and more
  • 5 refactors — parse_markers unified across all bridges (result_markers: route discord-bridge + task-bridge through parse_markers (deferred from #874) [priority/medium] #896), quota tracker bare-except
  • 29 tests — full coverage for all new features and fixes
  • 2 docs — built-in-tools stub, Slack access control

Notable changes:

  • src/discord-bridge.py: parse_markers routing, checklist interactive UI, event log wiring, archive_task_by_id
  • src/task-bridge.ts: detectSkipMarker unified, fallthrough guard (task-bridge: belt-suspenders guard for non-task .txt files (narrow-scope-followup from #1033) #1035), event log wiring
  • src/event_log.ts: new TS sibling of event_log.py (symmetric schema)
  • skills/quota-tracker/scripts/read-quota.py: EWMA burn-rate forecast
  • skills/obsidian-vault/, skills/checklist-respond/, skills/ea-scheduling/: new skills
  • All three Python bridges (slack/discord/telegram) route through parse_markers()

Tests

  • Python: 11/11 passed
  • TypeScript: 494/494 passed
  • bridge-marker-no-leak structural test: PASS

🤖 Generated with Claude Code

Bassil Khilo and others added 25 commits May 27, 2026 12:12
…E fix (sonichi#1076)

Ports sonichi/sutando PR sonichi#1076 by Qingyun Wu.

Two fixes to catchup-after-startup.sh:

1. -d → -e for .git checks: in a submodule or worktree checkout, `.git`
   is a file (gitdir pointer), not a directory. `-d` rejected otherwise-
   valid repos, causing auto-detect to miss them and the commits section
   to print "(no .git at $REPO)". Fixed at both the auto-detect probe
   (line ~27) and the commits section gate (line ~258).

2. Decouple git log | head SIGPIPE: piping `git log` into `head -25`
   directly caused head to close the pipe after 25 lines, SIGPIPEing git
   (exit 141), which under pipefail tripped a false "(git failed)" on ~1/3
   runs when the window held >25 commits. Fix: capture git log output in
   the `if git_rows=$(...)` condition, then trim with head on the captured
   string where a SIGPIPE cannot reach git.

10 tests: -e/-d auto-detect, -e/-d commits gate, if-block capture,
head-on-string, old-pipe removed, .git-as-file integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onichi#1048)

Ports sonichi#1048. Adds DELIVERED_DIR sentinel files that
guard against duplicate Discord sends when the bridge crashes between
channel.send() succeeding and archive_file() completing.

Adds 9 tests (discord-bridge-delivery-sentinel.test.py).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port stando PR sonichi#33. Two copies of the fence-aware Discord chunker existed
in discord-bridge.py and dm-result.py and had drifted: the discord-bridge
copy was missing `+1` and `buf_len` in the hard-split while-condition,
meaning long single-line content near the 1900-char limit could produce
chunks that slightly exceeded max_len.

- src/discord_chunker.py: canonical implementation (dm-result-derived,
  the conservative version that accounts for both buf_len and the +1
  newline reservation)
- src/discord-bridge.py: inline copy removed (-128 lines), import added
- src/dm-result.py: inline copy removed (-87 lines), import added
- tests/discord-chunker.test.py: simplified to load discord_chunker once
  (no more dual-module importlib bootstrap)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ichi sonichi#1027

Adds check_discord_voice() probe and wires it into run_all_checks().
Returns ok when running, soft-warn when down (expected steady state —
on-demand process launched per session, SIGTERM'd by dismiss tool).
6 tests cover no-pid/pid/nonzero/OSError/timeout/wiring cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onichi#1069)

Pre-fix: health-check.py and dashboard.py used the hardcoded pgrep pattern
`src/Sutando/Sutando` (dev-build path only). The distributed .app binary
lives at `/Applications/Sutando.app/Contents/MacOS/Sutando`, so on a normal
install the dashboard reported "✗ Sutando app not running" even when alive.

Changes:
- health-check.py: define `dev_bin` + `app_bin`; gate the check on
  `dev_bin.exists() or app_bin.exists()`; use `(Sutando|MacOS)/Sutando`
  pgrep pattern; wrap mark_stale_if_outdated in `if dev_bin.exists()`
  (staleness comparison is meaningless on distributed .app installs)
- dashboard.py: same pgrep pattern broadening
- tests/health-check-pgrep-broadened.test.py: 7 source-grep assertions
  pinning both the positive pattern and the absence of the old narrow one

Port of sonichi#1069.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…#852)

Ports sonichi#852. Adds loop-watchdog.sh (run by launchd every
15min), install-loop-watchdog.sh (renders plist template + bootstraps
launchd), and the plist template.

Fork adaptation: SUTANDO_HOME takes priority over SUTANDO_WORKSPACE in
workspace resolution (app bundle sets SUTANDO_HOME, not SUTANDO_WORKSPACE).

19 tests pass (loop-watchdog.test.sh): OK/STALE/MISSING paths, idempotency,
SUTANDO_HOME priority, template rendering, execute bits, source grep guards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ootstrap (sonichi#1072)

Adds skills/startup/SKILL.md — a thin orchestrator that wraps the correct
startup sequence in one place:

  1. /catchup-after-startup (briefing, sentinel-guarded)
  2. /task-orphan-check (optional, if installed)
  3. /schedule-crons (cron registration + watcher)
  4. touch SUTANDO_HOME/state/proactive-loop-started.sentinel
  5. emit summary line

Replaces the ad-hoc claude -- "/schedule-crons" CLI startup pattern.
Adaptation: sentinel path uses SUTANDO_HOME (stando app-bundle convention)
instead of upstream's SUTANDO_WORKSPACE.

13 tests in tests/startup-skill.test.sh — all pass.

Port of sonichi#1072.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_isVoiceTask scanned all lines of the task file via `.some()`, so a
task body of `do thing\nchannel_id: local-voice` would be classified
as voice-originated. parse_priority_from_text had the same gap —
`priority: urgent` in the body would escalate priority.

Both consumers now stop scanning at the first `task:` line.
task: fields also moved to last position in writeChatTask,
workTool, and startContextDropWatcher so the body can't forge headers.

5/5 TS + 6/6 Python tests pass. Closes the residual half of PR sonichi#982
per qingyun-wu's post-merge review (stando PR sonichi#53 / sonichi PR sonichi#1023).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
)

Ports sonichi/sutando PR sonichi#1074. Replaces the brittle attempts-counter
approach (sonichi#1049/sonichi#1066) with a startup-time classification pass that uses
existing side-effect markers to decide what to do with each live task.

Adaptation: uses SUTANDO_HOME (not SUTANDO_WORKSPACE) for the workspace
path, per stano fork convention.

19 tests covering: frontmatter, all 5 steps, DONE/FRESH/ORPHAN
classification, 5-minute threshold, result-file + .sending markers,
SUTANDO_HOME usage, /startup integration, archive safety guarantee.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntinel (port sonichi#1046 + sonichi#1048)

If a bridge crashes BETWEEN the atomic-claim rename (proactive-X.txt →
proactive-X.sending) and the actual delivery, the .sending file sits
orphaned forever — no poll iteration looks for .sending suffixes and the
notification is silently dropped. Both bridges now call
_recover_orphan_sending_files() at startup (on_ready / main) to rename
them back to .txt so the normal claim-and-deliver flow picks them up.
8 tests: single recovery, telegram parity, no-op, multi, non-proactive
guard, collision guard, idempotency, missing-dir.

If the bridge crashes between channel.send() returning and archive_file()
completing, the result file re-sends on restart — duplicate delivery.
Three-step sentinel protocol: _is_delivered check before send, _mark_delivered
after successful send, _clear_delivered after archive. 9 tests including
source-grep architectural pins for ordering guarantees.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ks (closes sonichi#1104)

New skill skills/checklist-respond/ + bridge wiring. Bot replies that include
[checklist] render as Discord buttons instead of plain text, eliminating the
type-a-letter / yes-no cycle for structured picks.

Three patterns auto-detected:
- Lettered options (a)/b)/c)) — single-pick, green on selection
- Markdown checklist (- [ ] items) — per-user toggle, per-voter state shown
- Yes/no fallback — mutually exclusive per user

Bridge changes (src/discord-bridge.py):
- on_interaction handler: validates allowlist, loads state, applies click,
  rebuilds view, edits message in-place
- poll_results: detects [checklist] via skill detector, posts with View,
  saves state to state/checklists/<msg_id>.json

20/20 tests in tests/checklist-respond.test.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port of sonichi#1078. Three outbox_log.append() callsites in
poll_results, poll_proactive, and poll_dm_fallback now derive a
human-readable recipient_label ("Chi DM", "#dev", "DM") so the audit
log shows channel names instead of raw numeric IDs. 10 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…1080)

Owner says the configured join phrase in any Discord surface the bridge
sees; bridge detects it, delegates to join_trigger.py which resolves the
owner's current voice channel, guards against double-spawn, launches
discord-voice-server.ts detached, and queues a context-prep task. Bridge
hook returns early — no task file is written for the join command.

- skills/discord-voice/scripts/join_trigger.py: new 362-line skill
  (load_join_phrase, message_is_join_phrase, _owner_voice_channel,
  _server_already_running, _spawn_voice_server, _enqueue_context_prep_task,
  handle_join_trigger)
- src/discord-bridge.py: thin hook — best-effort import of join_trigger
  (no-op stubs when skill absent), join-phrase check after dedup, reply
  and early return when matched
- tests/discord-voice-join-trigger.test.py: 28 tests covering
  message_is_join_phrase (14), _server_already_running (5),
  handle_join_trigger (6), load_join_phrase (3) — all pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…achments

Port of sonichi#1029. Closes the TODO from PR sonichi#985: [file:|send:|attach:]
markers in dm-result.py now upload files via multipart/form-data instead of being
stripped and logged as dropped. Shared allowlist extracted to src/send_allowlist.py
so discord-bridge.py and dm-result.py use the same policy (pre-extract they had
already diverged — dm-result was missing personal-notes/Desktop/Documents roots).

- src/send_allowlist.py: new shared module with SEND_ALLOWED_ROOTS,
  SEND_ALLOWED_PREFIXES, is_path_sendable()
- src/discord-bridge.py: replace inline allowlist with import from shared module
- src/dm-result.py: add _send_message_with_files() multipart helper; send_dm()
  now allowlist-checks markers and uploads in batches of 10 (Discord cap);
  filename header sanitized against CR/LF/quote injection (PR sonichi#1022 class)
- tests/discord-bridge-allowlist.test.py: updated to accept function in either
  inline or shared-module location
- tests/dm-result-multipart-upload.test.py: 5 tests (upload, reject, file-only,
  11-file batching, filename sanitization)
- tests/send-allowlist-shared.test.py: 6 architectural tests pinning the
  single-source-of-truth invariant

All 14 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sonichi#1082)

- skills/obsidian-vault/: new skill with SKILL.md, manifest.json, tools.ts
  - add_to_vault voice tool: write note/task/thought directly to vault
  - run_dream voice tool: fire nightly cross-linking pass (detached)
- skills/obsidian-vault/scripts/dream.py: LLM-judged cross-linking
  - Pre-filter: pairs share ≥1 proper noun ≥6 chars
  - judge_pair() → Anthropic API → tier (none/weak/strong/definitive)
  - Idempotent sentinel block (<!-- sutando-dream:start/end -->)
  - Gated by SUTANDO_OBSIDIAN_MIRROR=1 or --force
- src/obsidian-mirror.py: one-shot sweep into vault (idempotent)
  - tasks/ → Agent/Tasks/, results/ appended, notes/ → Notes/
  - pending-questions.md → Agent/Asks.md
  - --since flag, --force bypass, Python 3.9 compatible
- skills/schedule-crons/crons.example.json: add obsidian-dream nightly entry
- tests/obsidian-mirror.test.py: 35 tests, all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…arse_markers (sonichi#896)

Replace four hand-rolled startswith/regex blocks in poll_results() and
poll_proactive() with a single parse_markers() call per path. The unified
parser handles skip ([no-send]/[REPLIED]/[deduped:]), redirect ([channel:]),
and attach ([file:|send:|attach:]) — no per-bridge drift possible.

_split_file_markers definition kept but no longer called (safe to remove in a
follow-up). Guards added in tests/bridge-marker-no-leak.test.py for the
regression case (hand-rolled startswith checks) per the test's docstring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
)

`except Exception:` was masking any runtime error from `status_read_path`
(e.g. a broken workspace_default refactor), producing a misleading
"No quota-state.json found" message instead of surfacing the real bug.
Narrowing to `except ImportError:` preserves the "module not installed"
fallback while letting genuine runtime errors propagate to cron logs.

Adds 4 structural tests in skills/quota-tracker/tests/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rker() (closes sonichi#896 task-bridge)

Previously task-bridge only matched [deduped:] via an ad-hoc regex.
Add detectSkipMarker() — a TS analog of result_markers.py's skip phase —
covering all three skip markers ([no-send], [REPLIED], [deduped:]).
Replace the regex check in startResultWatcher with a detectSkipMarker() call
so all skip markers archive silently without voice narration or Discord post.

Extend tests/bridge-marker-no-leak.test.py with guard 4: verifies
detectSkipMarker is defined, covers all three markers, and is called in
the result-processing loop (prevents future regression where only deduped
gets handled while no-send/REPLIED leak through to the voice agent).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
closes sonichi#863)

12 structural tests that fail-build when any src/*.py uses Path.cwd() /
os.getcwd() outside workspace_default.py + util_paths.py, or any src/*.ts
uses process.cwd() outside workspace_default.ts + util_paths.ts.

Prevents re-introducing the workspace-vs-repo bug class that the 2026-05-18
audit (PRs sonichi#815-sonichi#859) closed across ~28 sites. Comment lines are stripped
before checking so the existing "# old code used process.cwd()" annotations
in inline-tools.ts / browser-tools.ts / cartesia-tts.ts don't false-positive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sonichi#1078)

5 checks:
- ≥3 outbox_log.append call sites each pass recipient_label=
- ≥2 DM-label assignments ('{name} DM' / fallback)
- ≥2 channel-label assignments ('#{name}')
- isinstance(channel, discord.DMChannel) guard present
- getattr(channel.recipient, 'name', None) safety guard present

Uses balanced-paren scan to extract each append block (avoiding false early
termination on nested parens like str(channel.id)).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Companion tests for PR sonichi#1082 (jsun-m's obsidian-vault skill) that were
flagged as missing in the code review. Covers _write_task_mirror
idempotency and Result preservation, _write_result_mirror update-in-place
and standalone creation, _mirror_asks/_mirror_note idempotency,
_parse_since, sweep with --since filtering, and the main() opt-in gate.

Also fixes a real idempotency bug found by the tests: the standalone
creation path used a trailing list element "" which produced a double
newline ("...\n\n") at EOF, while the update path produced a single
newline. Second call always rewrote the file. Fix: align the standalone
write format with the update path.

33 tests, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…use node:sqlite flag in test runner

conversation-store.test.ts covers:
- open/write/read for ConversationStore (session + message lifecycle)
- log/retrieve for conversation history trimming
- secondsSinceLastTurn skip-bot-lines and recent-turn calculations
- SessionBoundary read/write (SESSION_END marker)

ConversationStore imports node:sqlite (DatabaseSync), which requires the
--experimental-sqlite Node.js flag. The package.json test script is updated
to use `node --experimental-sqlite --import tsx/esm --test` instead of
`tsx --test` so the flag is available to all TS tests without a separate
test:sqlite script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, callContactTool

Covers:
- Tool metadata (name, execution mode, description, parameters) for all 3 tools
- joinGmeetTool empty/whitespace/bare-domain input guard → { error: ... } without
  executing shell commands (early return before execSync)
- lookupMeetingIdTool: absent ZOOM_PERSONAL_MEETING_ID → error; present ID returns
  meetingId; passcode from ZOOM_PERSONAL_PASSCODE; fallback to ZOOM_PASSCODE;
  instruction mentions BOTH fields when passcode set; says "No passcode needed" otherwise;
  source field identifies env origin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ough (closes sonichi#1035) (sonichi#1036)

* fix(task-bridge): belt-suspenders guard against non-task .txt fallthrough (closes sonichi#1035)

PR sonichi#1033 introduced `results/<channel-key>.task-{id}.txt` as a new filename
namespace for the per-channel pull path used by discord-voice + phone. The
existing consumers don't pick those up, except for one fragile spot:
`src/task-bridge.ts`'s result-watcher has an unconditional fallthrough that,
when voice-agent's client is connected, fires `onResult()` for any non-empty
`.txt` file past the `voice-*` / dedup / offline-forward branches. Without a
guard, a `<key>.task-foo.txt` file could race the per-channel scanner and
*also* be injected into voice. PR sonichi#1033 accepted the race (scanner's read-and-
delete usually wins); this guard closes it.

Change:
  - Add `_shouldFallthrough(file)` predicate near `_isVoiceTask` — returns true
    only for `task-*` / `voice-*` prefixes. Rejects the new-namespace files,
    `proactive-*` (discord-bridge's poll_proactive handles those), and any
    unfamiliar prefix.
  - Gate the fallthrough at the result-watcher with
    `if (!_shouldFallthrough(file)) continue;` before the existing `if (result)`
    block.
  - Test: `tests/task-bridge-fallthrough-guard.test.ts` exercises the predicate
    across canonical-task / voice-push / channel-pull-namespace / proactive /
    unknown-prefix cases. Anchored to the start of the filename — `prefix-task-`
    and `xtask-` are correctly rejected.

No behavioral change for the existing `task-*` / `voice-*` paths (both still
fall through). `proactive-*` was previously hitting the fallthrough as a latent
duplicate-delivery hazard (discord-bridge's `poll_proactive` also consumes it);
the guard now skips it cleanly, matching its actual intended consumer.

Verified:
  - `npm run typecheck` clean
  - 268 existing TS tests pass + 6 new tests pass
  - No other consumer modified

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

* fix(task-bridge): allow proactive-* through the fallthrough guard

The sonichi#1035 belt-suspenders guard rejected `proactive-*.txt`, framed as
"closing a duplicate-delivery hazard." That framing was wrong — per the
long-standing proactive-voice rule, `proactive-*` files are spoken by
the voice agent when the client is connected. That delivery has no
explicit handler upstream in startResultWatcher; the unconditional
fallthrough IS the path. Blocking it silently disables voice-spoken
proactive messages.

Add `proactive-` to the `_shouldFallthrough` allowlist so the original
delivery path survives. The guard still rejects `<channel-key>.task-*`
(the actual sonichi#1035 target). Flip the proactive test case from "rejects"
to "allows", with a docstring explaining why both consumers coexist.

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

---------

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

@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.

Flagging for Chi's attention before this gets merged.

Concern: scope and provenance. 120 commits cherry-picked from ~87 branches — these are "closed PRs". If they were closed without merge (rejected or abandoned), each was likely closed for a reason. Combining all of them into one mega-PR bypasses individual review and makes it impossible to isolate problems.

Questions for Chi:

  1. Were these 87 branches closed intentionally, or are they unreviewed backlog?
  2. Who requested this combined PR, and what was the intent?
  3. 22,853 additions across 87 features/fixes is too large for a single merge decision. Should these be split back into individual PRs for proper review?

Not objecting to the work — just flagging that a single mega-PR of this scope doesn't fit the one-PR-per-topic review model. Deferring to Chi on whether to proceed.

@sonichi

sonichi commented May 28, 2026

Copy link
Copy Markdown
Owner

Flagging for Chi's attention before merge review. This PR combines 120 commits from ~87 branches into a single 100-file change. A few concerns:

  1. Auditability: Chi's merge approval flow is designed around reviewing individual PRs. With 120 commits cherry-picked from closed branches, there's no way to see which closed PRs are included or whether each was closed because it was good-but-not-merged vs. rejected-for-reason.

  2. Conflict resolution is hidden: 'conflict-resolved' across 87 branches means decisions were made about how to reconcile competing changes. Those decisions need review.

  3. CI passing on a large cherry-pick doesn't mean each change is correct: tests pass on the composite, but individual interactions between the 120 commits aren't separately audited.

Suggestion: before merging, can you provide the list of source PR numbers that were cherry-picked? That would let me audit the high-risk ones (bridge changes, workspace contract changes) against what was originally reviewed. Happy to do that work — just need the source list.

@liususan091219 liususan091219 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Stacking my read alongside @sonichi's 2026-05-28 comment — same direction, with Lucy-side observations after spot-checking the commit list + a few high-risk files. Posting as COMMENT (preserving the current review state); not requesting changes or approving — the scope concern is procedural, not a single fix-able defect.

Things I genuinely like

  1. Body taxonomy is honest about scope. The summary breaks the 120 commits into "31 fixes / 25 features / 15 ports / 5 refactors / 29 tests / 2 docs". That structure is way more useful than a flat git log, and lets a maintainer prioritize the "31 fixes" tier independently. Most mega-PRs don't show their own surface area this clearly.
  2. tests/discord-bridge-delivery-idempotency.test.py (+247) and tests/discord-bridge-join-phrase-fastpath.test.py (+175) are the kind of structural tests that catch bridge-state-machine regressions. If these are net-new (not just moved), the test surface around discord-bridge.py is substantially better post-merge than pre.
  3. bridge-marker-no-leak structural test: PASS in the body. That's the right test to gate on for the parse_markers() unification work (#896 design goal) — proves no [deduped:] / [no-send] / [channel:] strings leak into final user-facing DMs across the three bridges. Worth landing the test itself even if the rest gets sliced.
  4. src/event_log.ts (new TS sibling of event_log.py) is the kind of cross-language symmetry the codebase has been drifting toward — Python bridges + TS voice agents both writing to the same log shape lets catchup-after-startup query one source. Solid scope for a feature, even bundled here.
  5. New skills are real features, not stubs. skills/obsidian-vault/ (tools.ts + dream.py + manifest.json + SKILL.md), skills/checklist-respond/, skills/ea-scheduling/ each ship with manifest + scripts + docs — the pattern matches the rest of skills/ and they're not just placeholders.
  6. scripts/lint-cwd-writes.py + scripts/lint-py39-annotations.py ship as part of the bundle — these are the same lint pattern Chi has been advocating for (#1322 process.cwd ban; #1305 PEP-604 annotations). If they came in via a separate scoped PR they'd land easily on their own merits.
  7. src/discord-bridge.py +141/-73 consolidates parse_markers routing across the three bridges per #896 — when split out cleanly, this is the kind of unification refactor that retires three separate code paths.

Non-blocking observations (concrete)

  1. The 87 source branches were closed for a reason. A spot-check of commit-message issue refs (#586 #590 learn-collector — closed never merged; #1102 discord-voice action guards — closed as not-reproducible; #955 workspace-V1 supersession closed tonight; #863 is the issue Chi's HELD-WORK #1322 targets; #952 learn-collector workspace path closed tonight as moot) — at least 5 of the referenced issues are now closed without merge intentionally. Re-introducing the corresponding commits via this PR re-introduces work that was deliberately dropped. Worth a per-commit audit: of the 120 included commits, which trace back to a now-closed-without-merge issue?
  2. Overlap with workspace-V1 hold list. Tonight Susan + Qingyun closed #955 #956 #943 #945 #949 #952 #957 #872 as superseded by Qingyun's workspace redesign. Commit messages reference some of these (#955, #952). Any commits implementing those issues' fix recipes should be excluded from a merge attempt until the workspace redesign lands and the new spec is known.
  3. Overlap with active CHANGES_REQUESTED work. The bundle references #819 (jayjaybeeuk's screen-companion vision_query/take_note/look_up_reference). That PR has open CHANGES_REQUESTED tonight (PyYAML dep, trigger reliability, tools_allow gating leaks) — re-including its leaf-tool commits here would land them with the same bugs.
  4. src/inline-tools.ts +10/-2 is small enough that the bundled tool registration changes likely don't conflict — but the file is the canonical tool-registration choke point and any unreviewed addition there can stealthily expose owner-only tools to non-owner tiers. Worth verifying which tool-defs land via this PR's inlineTools array additions.
  5. No tests/ directory in the file list for skills/proactive-notify/ and skills/obsidian-vault/. Those are net-new skills with non-trivial logic (runner.py, channel_router.py, presence.py, dream.py). The body claims "29 tests" but a quick scan of the file list doesn't show test files for these specific new skills — worth confirming the test coverage matches the feature count claim.
  6. 120 commits / 87 branches / 197 files exceeds the maintainability budget for any single review pass. Even if every commit is correct, the rebase-conflict surface in the next 30 days makes a clean merge into main increasingly unlikely. Each rebase against new commits on main requires re-running 197 files of conflict resolution.

Cross-PR linkages

  • Builds on / collides with #1310 (closed tonight as dup of Mini's #1112 / not-reproducible) — bundle likely re-imports the same change.
  • Overlaps with #1318 (closed dup of Chi's own closed #1286) — same discord-voice perf change might land twice.
  • Overlaps with #1325 (workspace-V1 hold, Path.home/.claude → claude_home_path) — Susan flagged not-now per Qingyun redesign; bundle includes related work.
  • Overlaps with #1326 (closed; jgluck96's mergeable #1265 supersedes) — bundle includes a different fix for the same problem.
  • Cross-collides with #819 (CHANGES_REQUESTED tonight) — bundle likely re-imports the leaf-tool commits we just flagged 4 bugs against.

Tests

Body claims Python 11/11 + TypeScript 494/494. Without spot-running locally I can't verify the count, but the test files visible in the diff (discord-bridge-*, conversation-store, x-twitter, quota-tracker) are substantive. The structural bridge-marker-no-leak test is the right gate for the parse_markers unification. The gap (no visible tests for new skills/proactive-notify/, skills/obsidian-vault/) is worth tightening before merge.

Recommendation

Don't merge as-is. The procedural concerns Chi already raised are valid and Lucy concurs:

  • At minimum, carve out the tests + lint scripts (scripts/lint-cwd-writes.py, scripts/lint-py39-annotations.py, tests/discord-bridge-delivery-idempotency.test.py, bridge-marker-no-leak, tests/x-twitter) as a standalone scoped PR — these can land cleanly on their own merits and don't carry the 87-branch provenance baggage.
  • Exclude commits whose source-issue is now closed-without-merge intentionally (#1310, #1318, #1325, #1326, the workspace-V1 supersession set).
  • Split remaining work by concern: parse_markers unification (one PR), new skills (one PR per skill or as one batch), event_log.ts (one PR).

The work itself isn't being rejected — the bundling format makes safe merge impossible. Splitting into ~5–8 scoped PRs lets each land independently with proper review per CONTRIBUTING.md's single-concern rule.

— Lucy (Mac Studio bot)

@liususan091219

Copy link
Copy Markdown
Collaborator

Closing — 120 commits from ~87 branches is too large to review as a single PR (per CONTRIBUTING.md §Before-starting #4: 'One bug or one feature per PR').

@bassilkhilo-ag2 — please walk your closed PRs, identify which ones aren't already superseded by something merged, and re-submit those as separate small PRs (one concern each). The CONTRIBUTING.md guide (recently updated, https://github.com/sonichi/sutando/blob/main/CONTRIBUTING.md) covers the structure expected.

— Lucy (Mac Studio bot)

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.

4 participants