feat(reply-drafts): prepare replies you owe across all channels#175
feat(reply-drafts): prepare replies you owe across all channels#175yustme wants to merge 8 commits into
Conversation
Scout now detects open conversational loops where the user owes someone a reply and prepares a ready-to-send draft into drafts/<TAG>.md. Two loop types: direct-debt (someone asked, no reply yet) and promise-answered (promised -> asked elsewhere -> answer arrived). Reviewed in /scout-work; the user always sends himself — Scout never sends or creates native drafts. - phases/core/reply-drafts.md: detection + drafting synthesis phase (mode: briefing+consolidation, runs after action-items) - templates/drafts/README.md.tmpl: draft file contract, seeded into vault - bootstrap: scaffold drafts/ + drafts/archive/, seed README - commands/scout-work.md: Reply item type (show draft, sent/edit/skip/dismiss, no send tool ever) - tests: assembly + scaffolding coverage
Email/Slack/WhatsApp draft bodies are now plain sendable text (no markdown, no headings/bullets/backticks, no HTML comments) so they look right when sent as-is; markdown stays allowed only for linear/github. Recipients/CC/subject live in frontmatter — adds a cc: field to preserve the thread's other recipients instead of dropping them or listing them inline.
…ompts Documents that each [TBD: ...] marker is surfaced as a fill-in field in the app and /scout-work, so the phase must write each as a standalone actionable prompt (one fact per marker) placed where the value should land in the sentence.
Each draft now carries an AI summary and the relevant thread messages after a <!-- scout:context --> marker (## Summary + ## Thread with '- [date] Sender: line' entries). It lives after the body marker so it is never sent or copied; the app renders it as two collapsible sections under the draft.
…he plugin Adds /scout-reply: the plugin-side equivalent of the app's Reply Drafts view. Review the replies you owe, chat with the AI about a specific topic (it has the summary + thread and can re-read the live thread/KB), fill in [TBD: ...] blanks, refine wording, and mark sent/dismissed — all in the conversation, never sending. Operates on the same drafts/<TAG>.md files as the app, so the full experience is available without the native app.
Adds explicit, user-initiated delivery to /scout-reply: a slack draft can be sent into its thread (slack_send_message, after confirming the recipient), an email draft can be turned into a Gmail draft (create_draft, never auto-sent). Autonomous runs still never send — delivery only happens on an explicit instruction. Updates the command menu, phase, and drafts README accordingly.
The README delivery note changed 'never sends' -> 'never send anything'; update the seeds-readme assertion to 'never send' so it tracks the live wording.
jordanrburger
left a comment
There was a problem hiding this comment.
Review — reply-drafts
Strong feature — the never-send constraint is enforced consistently (phase + both commands + anti-patterns + tests) and the verify-before-draft gate is genuinely rigorous. Two things I'd resolve before merge, plus a lockfile and a cross-PR note. Most of this surfaced diffing against a real (mature) vault, not from the diff alone.
🔴 Anonymization leak (inline on scout-reply.md). Real bank names (SLSP = Slovenská sporiteľňa, ČSAS = Česká spořitelna), the employer name, and real-person-shaped client names with Slovak subject text, in a public-repo command file. Needs stand-ins per the repo's anonymization rule — see inline.
🔴 drafts/ collides with existing vaults. drafts/ is not an established engine path (nothing on main references it), but a mature vault already uses drafts/ as a free-form drafting workspace — long-form proposals, weekly updates, PR reviews, long replies, none using this PR's tag/channel/status frontmatter. This PR claims the folder as its <TAG>.md namespace and seeds a drafts/README.md that declares "This directory holds prepared reply drafts," which mischaracterizes it. No data loss — the Step 0 archive bash only moves sent/dismissed files, so no-frontmatter files are skipped — but /scout-reply and /scout-work would scan a folder mixing two different "drafts" notions, and the README is wrong for it. Suggest namespacing the feature (reply-drafts/ or drafts/replies/), or making rollout aware of an existing drafts/. This is the "facet built greenfield, deployed onto a mature vault" pattern — captured in #178.
🟡 uv.lock (933 lines) looks unrelated. It's new (not on main) with no pyproject.toml change behind it — ~65% of the diff. Looks accidental, or like a first-time lockfile commit; either way it belongs in its own infra PR rather than riding in a feature PR.
🟡 Cross-PR with #176. Both PRs edit the same _INSTALL_ONLY_TEMPLATES and _CAT1_DIR_LAYOUT tuples → merge conflict for whichever lands second. And on main, upgrade() calls neither _stage_create_dirs nor _stage_install_only_seeds, so on an existing vault /scout-update assembles the phase but does not seed drafts/README.md or pre-create drafts/archive/. The feature depends on #176's upgrade-replay to fully reach existing vaults.
🟡 Autonomy surface. /scout-reply is the first place Scout gains a real send path — slack_send_message + Gmail create_draft. It's well-gated (interactive only, explicit ask, confirm recipient; autonomous runs never send; /scout-work's Reply variant is hard-no-send), but it's a real expansion worth conscious sign-off given how much weight the escalate-vs-handle contract carries.
✓ Verified correct: assembly routing (mode: [briefing, consolidation] → SKILL only, after action-items, excluded from DREAMING/RESEARCH); bootstrap wiring is correct (README in _INSTALL_ONLY_TEMPLATES, drafts/archive in the dir layout); both CLI deps exist (action-items new-prefix, parser name_lookup); 19/19 changed-area tests pass; the Step 0 archive bash is safe on no-status files.
The leak and the greenfield-drafts/ assumption are both the back-port-hygiene gap we're already discussing on #176/#178 — a standardized scrub + mature-vault-diff step in the back-port procedure would have caught both.
| draft. Otherwise show a compact list of `status: draft` items: | ||
| ``` | ||
| Owed replies with a prepared draft: | ||
| 1. [#S2DA6B] Lucia Hallonová (SLSP) — Re: Zmeny Keboola rolí · email |
There was a problem hiding this comment.
Real-world identifiers on a public surface. SLSP (Slovenská sporiteľňa) and ČSAS (Česká spořitelna) are real banks; combined with the employer name, real-person-shaped names, and Slovak subject text, this reads as lifted vault data rather than an example. Same on line 33 (/scout-reply SLSP).
Per the repo's CLAUDE.md anonymization rule (and the "no vault content on public surfaces" principle), these need stand-ins — the way templates/drafts/README.md.tmpl already does it (Jan Novák <jan@firma.cz>, Rozpočet Q3). Suggest: neutral names (Alex / Priya), a generic vendor noun instead of a named bank, and PROJ-####-style tags.
Folds two findings from the PR #175 review into the facet open-questions: - Problem 5 gains a second flavor — namespace collision: reply-drafts claims a `drafts/` directory that a mature vault already uses as a free-form workspace; the engine has no registry of reserved vault paths. - Problem 4 gains the hygiene angle — #175 shipped unanonymized real vault data into a public command file because the back-port had no scrub gate. - Open question 5 now names the two missing back-port gates (scrub + mature-vault diff) and a possible reserved-paths registry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scout now prepares draft replies for conversations where you owe an answer, on the background runs, so you can review them in
/scout-work(or the macOS app), lightly edit, and send them yourself. Scout never sends and never creates a native Gmail/Slack draft — a text file in the vault is the only output.What it detects
Two loop types, across every enabled channel (Gmail, Slack, Linear, GitHub, WhatsApp/Messages):
How it works
phases/core/reply-drafts.md(briefing + consolidation) — runs after the connector scans and the action-items list, verifies the debt is real (reads the thread tail, applies the cold-outreach / leave-state filters), then writes onedrafts/<TAG>.mdper loop and ensures a matching action-item row with a(reply drafted → [[drafts/<TAG>]])pointer.drafts/<TAG>.mdfrontmatter is the contract the app +/scout-workkey on:tag, channel, loop_type, to, thread_ref, subject, status, created, context_answer_ref. Status vocabulary isdraft | sent | dismissed.templates/drafts/README.md.tmpldocuments the contract and is seeded into the vault;drafts/+drafts/archive/are scaffolded on install./scout-workgains a Reply item type: it shows the full drafted text and acceptssent/edit: …/dismiss/skip. It never calls a send tool.Hard constraint
Scout only ever writes draft text and flips
status:. No sending, no native drafts — that stays the user’s action.Tests
SKILL.md(briefing + consolidation), after action-items, and is excluded fromDREAMING.md/RESEARCH.md.drafts/+drafts/archive/+drafts/README.mdare scaffolded on install.Companion
Paired with the macOS app PR (Raven-Scout/Scout) that adds a Reply Drafts section rendering these files and flipping their status.