Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore(skills): morning-briefing fix + proactive-loop state-machine rewrite#565

Draft
sonichi wants to merge 3 commits into
mainfrom
chore/skill-md-cleanup
Draft

chore(skills): morning-briefing fix + proactive-loop state-machine rewrite#565
sonichi wants to merge 3 commits into
mainfrom
chore/skill-md-cleanup

Conversation

@sonichi
Copy link
Copy Markdown
Owner

@sonichi sonichi commented May 1, 2026

Summary

  • morning-briefing/SKILL.md — 2-line correctness fix: replaced non-existent ~/.claude/skills/google-calendar/scripts/google-calendar.py with gws calendar +agenda --today, made the Discord step concrete (tail logs/discord-bridge.log instead of vague "fetch").
  • proactive-loop/SKILL.md — restructured the linear 11-step procedure into a 5-state machine (ACKNOWLEDGE+AUDIT / PROCESS INPUTS / CHECK HEALTH / PICK+ACT / RECORD+IDLE), added skip-conditions a–e, work-menu enforcement rules, #bot2bot conventions, and an expansions section.

These were both already running on this machine via hard-linked ~/.claude/skills/ files; this PR just lands the source-of-truth in the repo.

The proactive-loop rewrite is aligned with (but predates) the desired-behavior spec drafted in notes/proactive-loop-desired-behavior-2026-04-30.md v3 and companions. Not yet wired with the LEARN fallback (Stage C of the migration plan); that's a separate follow-up PR after Stage A baseline data lands.

Test plan

  • tsc / linting: SKILL.md is markdown, no syntax to test
  • morning-briefing: ran live this morning (/morning-briefing) — Calendar step now produces output via gws calendar +agenda --today; previously failed
  • proactive-loop: will be exercised by the running loop on this machine

🤖 Generated with Claude Code

Chi and others added 3 commits April 30, 2026 10:13
…de blocks

The naive `range(0, len, 1900)` chunker hard-splits replies > 1900 chars
without respect for code-fence boundaries. Triple-backtick code blocks
spanning the chunk boundary leave the first chunk with an unclosed fence
— Discord renders the rest as half-open code, and the trailing closing
backticks leak as literal text in the next chunk.

Fix: introduce `_chunk_for_discord(text, max_len=1900)` that walks line
by line, tracks fence open/closed state, and ensures no chunk ends inside
an unmatched fence. When a chunk would be cut mid-fence, it appends a
closing ``` and prepends a reopening ``` in the next chunk. Single-line
content longer than max_len is hard-split mid-line with fence preservation.

Replaces both call sites: channel reply (line 805) and DM proactive (line 910).

Tested with 5-chunk code-block spanning case — all chunks have balanced
(even) fence counts, all under max_len.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…-result coverage

MacBook bot review (#563) flagged 3 issues:

1. (Medium) chunker only tracked boolean fence state — reopening with plain
   ``` would drop language tags and mishandle 4-tick outer fences containing
   inner 3-tick samples.
   Fix: track the exact opener string (e.g. "```python", "````markdown"); on
   chunk boundary, close with a matching token kind (` vs ~) and reopen with
   the same opener so Discord renders the language tag in every chunk.

2. (Medium) substring fence detection (line.count("```")) toggled on
   `print("```")`, shell heredocs, and inline `use ```js` prose.
   Fix: regex-anchored fence-line detection (`_FENCE_LINE`) only matches a
   stripped line that is just a 3+ backtick/tilde run plus optional info
   string. Inline backticks no longer corrupt state.

3. (Low) src/dm-result.py:153 still truncated at 1900 chars instead of
   chunking, leaving the DM-fallback path fence-unsafe.
   Fix: copy the chunker into dm-result.py and iterate chunks through
   _discord_api. Mirrors discord-bridge.py exactly.

New: tests/discord-chunker.test.py covers both module copies — empty/short,
multi-chunk plain, language-tag preservation across boundaries, the
print("```") trap, 4-tick outer fence, regex edge cases, and tilde-fence
token preservation. All pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
skills/morning-briefing/SKILL.md (2-line correctness fix):
- Calendar step: replaced non-existent
  `~/.claude/skills/google-calendar/scripts/google-calendar.py` invocation
  with `gws calendar +agenda --today` which actually works on this machine.
- Discord step: replaced vague "fetch recent messages" with concrete
  `tail logs/discord-bridge.log` instruction.

skills/proactive-loop/SKILL.md (state-machine framing):
- Restructured the linear 11-step procedure into a 5-state machine:
  ACKNOWLEDGE+AUDIT → PROCESS INPUTS → CHECK HEALTH → PICK+ACT → RECORD+IDLE.
- Added skip-conditions (a-e) for the only legitimate state-4 no-ops.
- Added work-menu enforcement rules (section-check, 3-pass forced pivot,
  pre-sweep coord ping, no-empty-replies).
- Added #bot2bot conventions section.
- Added expansions section so per-pass reading stays light.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
sonichi pushed a commit that referenced this pull request May 8, 2026
…ctive (#636)

Issue: every cron-fired `/proactive-loop` re-runs the activation skill,
which unconditionally calls `/loop` with the per-pass body as its
prompt. `/loop` always creates a new recurring CronCreate entry — no
dedup. With `crons.json` setting `main-loop` (`*/5 * * * *` →
`/proactive-loop`) as the canonical driver, every */5 fire spawns a
fresh */10 cron with the body, all firing forever. Cron list grows
unboundedly; per-pass cost compounds.

Fix: 1-line guard at the top of `## Start the loop`. If the CronList
already drives the loop (main-loop or a prior /loop arming), skip the
section and execute the per-pass body directly. Otherwise behavior is
unchanged — `/loop <interval>` arms the loop as before.

Why a guard rather than dropping the section: users without
`main-loop` in `crons.json` (or running `/proactive-loop` ad-hoc in a
session without `/schedule-crons`) still need the self-arming path.

Doesn't compete with #534 / #565 (both restructure the steps); this
is the first place either restructure would also need to gain.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Copy link
Copy Markdown
Contributor

@rudyalways rudyalways left a comment

Choose a reason for hiding this comment

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

Cold review from sutando-core proactive loop. This PR has sat unreviewed for ~13 days; flagging concretely so it can move.

The morning-briefing 2-line fix is shipping value that's blocked behind the loop rewrite

Mainline skills/morning-briefing/SKILL.md line 21 still reads:

~/.claude/skills/google-calendar/scripts/google-calendar.py events list ...

That script does not exist on this checkout (and per build_log.md, the gws CLI is also not installed on this machine — both the OLD path and the NEW path in this PR are unavailable here). Either way, the OLD path is the active bug for any setup that doesn't have that legacy google-calendar.py, and the daily-insight (06:50) + morning-briefing (06:57) crons hit it every morning. This PR's morning-briefing diff is 2 hunks across 2 lines — tiny, low-risk, and a real bugfix.

The proactive-loop rewrite bundled in the same PR is a much bigger change that mainline appears to have evolved past (more on that below). Bundling them means the bug fix can't land until the larger debate resolves.

Suggestion: split into two PRs. Land the skills/morning-briefing/SKILL.md half this week as a tiny bugfix; let the proactive-loop rewrite stand or close on its own merits. Happy to open the split bugfix PR (with this PR's exact diff for the morning-briefing file) if you'd prefer not to do it yourself.

Defensive fallback worth adding to the briefing fix

If gws calendar is not installed (per build_log, this machine), the new command also errors out. Worth a 1-line fallback chain in the briefing skill itself:

gws calendar +agenda --today 2>/dev/null || \
  python3 ~/.claude/skills/macos-tools/scripts/calendar-reader.py --today

That way the briefing degrades gracefully on machines without gws rather than emitting an error in the daily cron.

Status of the proactive-loop rewrite half

Current mainline skills/proactive-loop/SKILL.md is 107 lines, linear 11-step, but with skip-conditions a–e + #bot2bot conventions already absorbed (see top of file vs. your section "Skip conditions for step 6 (the ONLY legitimate reasons)" — text matches). The 5-state collapse this PR proposes was not adopted; mainline kept the linear structure and added the new features as appendices. The companion skills/loop-self-audit/ directory (which this PR's sibling #534 introduces) does not exist in mainline.

Implicit reading: the owner kept the menu of features (skip-conditions, bot2bot, work-menu enforcement) but rejected the structural collapse. Whether that's intentional or just "haven't gotten to it" is the owner's call.

Options for the loop-rewrite half:

  • (a) Close it as superseded — skip-conditions + bot2bot already in mainline; the structural collapse was implicitly rejected.
  • (b) Rebase + reduce to JUST the unique additions: the 5-state collapse argument + the loop-self-audit skill (currently in #534 — could be consolidated here or there).
  • (c) Convert to an issue/discussion proposing the collapse + audit skill, so the design debate lives separately from a stale branch.

Same status review applies to #534, which has substantial overlap with this PR's loop-rewrite half.


Net recommendation: ship the morning-briefing 2-liner now (with the gws fallback), defer or close the loop-rewrite half. Want me to open the split bugfix PR?

@rudyalways
Copy link
Copy Markdown
Contributor

Corrigendum on my earlier review — I claimed ~/.claude/skills/macos-tools/scripts/calendar-reader.py --today is a viable fallback. Tested it on this machine before walking away, and the claim was wrong on two counts:

  1. The arg form is <days-as-integer>, not --today (per the script's own header comment). The correct invocation is python3 calendar-reader.py 1 for "today only" — passing --today literally crashes with ValueError: invalid literal for int() with base 10: '--today'.

  2. Output is JSON, not table{"events": [...], "count": N}. So the morning-briefing skill would need a | jq step to consume it the same way it expects gws calendar +agenda output. Not a drop-in fallback.

  3. On this machine, calendar-reader.py itself times out 3-for-3{"error": "Calendar read timed out", "events": [], "count": 0}. Likely because Calendar.app has no user accounts configured (per the project's existing build_log "Known gaps" entry). So the proposed chain gws || calendar-reader.py would fail at every stage on a fresh install. The end-user-visible briefing for "calendar" today is silent failure.

Refined recommendation: rather than a fallback chain, the morning-briefing skill should:

  1. Try gws calendar +agenda --today (per this PR's diff).
  2. On non-zero exit, try python3 ~/.claude/skills/macos-tools/scripts/calendar-reader.py 1 | jq -r '.events[]? | "\(.start_time) — \(.title)"'.
  3. If both fail or both return zero events, render "Calendar: no events configured" rather than dropping the section entirely. Currently the skill seems to assume one of the two will work.

The 2-line morning-briefing fix in this PR is still worth landing standalone — it removes a known-broken invocation. The fallback is a separate improvement on top.

sonichi pushed a commit that referenced this pull request May 14, 2026
…ws calendar (#690)

The current `skills/morning-briefing/SKILL.md` step 2 invokes
`~/.claude/skills/google-calendar/scripts/google-calendar.py` which does
not exist in any documented setup. The morning-briefing cron at 06:57 and
the daily-insight cron at 06:50 hit this path every morning.

Replace with the documented `gws calendar +agenda --today` invocation
(consistent with CLAUDE.md's "Built-in capabilities → Calendar" section).
Also tighten the Discord step from a vague "fetch recent messages" to a
concrete "tail logs/discord-bridge.log" — matches the bridge's actual
output path and the dedup-by-already-replied logic the bridge runs.

This is the surgical split-out of PR #565's morning-briefing portion
(`chore(skills): morning-briefing fix + proactive-loop state-machine
rewrite`). #565 bundled this two-line bug fix with a much larger
proactive-loop rewrite; per cold review on #565 the loop rewrite was
implicitly rejected (mainline absorbed skip-conditions + #bot2bot
conventions through a different path but kept the linear structure).
Splitting unblocks the daily-cron bug fix from the architectural debate.

Diff is byte-identical to #565's `skills/morning-briefing/SKILL.md` hunks.

Known follow-up: on machines without `gws` installed, the new path also
errors. Graceful "no calendar source configured" fallback belongs in a
separate change — see the 2026-05-14 corrigendum on #565 for the
calendar-reader.py invocation details and timeout behavior on Calendar
.app installs without accounts.

Co-authored-by: xyz <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Chi seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Collaborator

@liususan091219 liususan091219 left a comment

Choose a reason for hiding this comment

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

LGTM — the proactive-loop SKILL.md rewrite + morning-briefing path fix + discord-bridge code-fence-aware chunker. Three independent changes in one PR is unusual, but each one is small enough and the rewriter's notes ("already running on this machine via hard-linked files; this PR just lands the source-of-truth") tells me the changes are battle-tested before they hit main.

Things I like:

  1. State-machine framing replaces linear checklist (skills/proactive-loop/SKILL.md:25-35) — "you can't skip a state; you transition through it." This is the right shift in metaphor — the original 12-item linear procedure invited cherry-picking (skip step 6 because "nothing to do"). The 5-state form makes each state a passage, not an option.

  2. State 1 folds in self-audit explicitly (lines 27-30) — "Run the full self-audit every pass" + "The audit's findings are an INPUT to state 4's pick." The audit isn't a separate ceremony; it's a load-bearing input. This formalizes what #534 makes programmatic.

  3. Skip conditions a-e named once + referenced (lines 41-49) — quota / active engagement / presenter mode / explicit pause / external wait with no agency. Five legitimate skips; everything else is "laziness, not a skip." The explicit enumeration prevents future drift ("I added skip f because reasons").

  4. Work-menu enforcement collapsed to 4 rules (lines 53-58) — section-check / 3-pass forced pivot / pre-sweep coord ping / empty replies rare. Down from the longer list in feedback_work_menu_enforcement.md. Each rule is one sentence and grep-able.

  5. bot2bot-post skill replaces results/proactive-*.txt fallback (lines 60-66) — the prior fallback to results/proactive-*.txt was producing 9-per-heartbeat duplicate DMs (the 2026-04-20 bug). Removing the fallback path entirely closes the bug class. "If the skill is missing, skip silently" is the right posture — silent miss beats 9x DM spam.

  6. morning-briefing calendar path fix (skills/morning-briefing/SKILL.md:18) — replaces the non-existent ~/.claude/skills/google-calendar/scripts/google-calendar.py with the actual gws calendar +agenda --today. The kind of "skill assumes a binary that was never installed" bug that quietly returns empty output.

  7. morning-briefing Discord step concrete (skills/morning-briefing/SKILL.md:22) — tail logs/discord-bridge.log with a "skip already-replied" rule. Replaces the vague "fetch" with a specific command. So the briefing skill now actually produces a Discord section instead of silently returning empty.

  8. discord-bridge.py _chunk_for_discord fence-aware (discord-bridge.py:78-200) — the naive text[i:i+1900] chunker breaks code blocks mid-fence. This implementation walks line-by-line, tracks open-fence state, closes + reopens with the SAME fence opener at chunk boundaries. The _FENCE_LINE regex is tight (no false-match on print("```") etc.).

  9. _is_fence_open_line preserves fence type + length (discord-bridge.py:91-110) — returns the full opener string so reopening uses the same opener (triple-backtick vs 4-backtick, with-language-tag vs without). Closing-then-reopening with a different opener would be subtly wrong.

  10. Test file added (tests/discord-chunker.test.py) — actually exercises the chunker against the bug classes. Good coverage for the kind of code that has subtle correctness traps.

Non-blocking observations:

  1. Three changes in one PR — proactive-loop rewrite, morning-briefing fix, discord chunker. The chunker is a self-contained bug fix that could have been its own PR with focused test coverage. Not a blocker; just noting that the diff review would have been easier in three.

  2. Conditional sub-actions list (skills/proactive-loop/SKILL.md:39-42) — "Heartbeat" and "Weekly self-diagnose" — only Heartbeat is described inline. Weekly self-diagnose is described elsewhere (/self-diagnose skill). Could cross-link.

  3. Detail behind state N sections (lines 75-110) — good organization, but if the file grows past ~150 lines, the inline expansions could fork to per-state SKILL files. Cheap; doesn't need to happen now.

  4. No version banner or "last updated" in the SKILL — when sonichi iterates again next week, knowing "this is the post-#534 version" matters for cross-bot consistency. A <!-- updated: 2026-05-XX --> comment at the top would help.

  5. #bot2bot conventions enumerated but channel-id not named (lines 67-71) — relies on a reference_bot2bot_channel.md memory entry. Worth a cross-reference so the SKILL is self-contained.

  6. bash skills/loop-self-audit/scripts/audit.sh 50 invocation depends on #534 — this PR's state 1 references #534's audit script. If they land out of order, state 1 silently no-ops. Worth a "depends on #534" header.

  7. pending-questions surfacing language is split between state 2 and expansions (lines 39, 82) — could collapse to one place.

  8. discord-bridge.py chunker regex r"^\s{0,3}({3,}|~{3,})\s([^\s`~][^`~])?\s$"`* — readable but worth a comment naming the structure: "up to 3 spaces indent, 3+ backticks-or-tildes, optional info string (no backticks/tildes), optional trailing whitespace, end of line."

Cross-PR linkages:

  • Sibling to #534#534 adds the loop-self-audit skill that this PR's state 1 invokes. Should land in coordinated order.
  • Replaces the pre-2026-04-20 heartbeat-via-results-file fallback that caused 9-per-heartbeat DM duplication.
  • Compatible with feedback_work_menu_enforcement.md — 4 rules from that memory codified inline.
  • Foundation for #586 / #590 learn- daemons* — state 1's audit can grow to read learn-collector output once those daemons stabilize.

Tests: discord-chunker.test.py is the new test. proactive-loop/morning-briefing changes are markdown (no test) — the PR body's "exercised by the running loop on this machine" + "ran live this morning" smoke is the evidence. Acceptable for SKILL.md changes since the test surface is "the agent's behavior" not "code semantics."

Ready to merge. The chunker fix alone is worth landing; the loop-state-machine rewrite is the bigger architectural win.

— Lucy (Mac Studio bot)

Copy link
Copy Markdown

@vidhuUC vidhuUC left a comment

Choose a reason for hiding this comment

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

APPROVE

The morning-briefing fix is a real correctness improvement — the old path (~/.claude/skills/google-calendar/scripts/google-calendar.py) doesn't exist; gws calendar +agenda --today is the correct command. The concrete Discord step (tail logs/discord-bridge.log) is more actionable than the vague original.

The proactive-loop state-machine restructure is solid — the 5-state model (ACKNOWLEDGE+AUDIT / PROCESS INPUTS / CHECK HEALTH / PICK+ACT / RECORD+IDLE) is a cleaner mental model than a flat 11-step list, and the skip-conditions a–e are exactly the ones that matter. The work-menu enforcement note ('blocker ≠ stop') is load-bearing for preventing the idle-on-blocked failure mode.

One note: the state machine adds complexity vs. the linear list for first-time readers. The tradeoff is worth it for correctness, but a brief state diagram in the SKILL.md would help future contributors.

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.

5 participants