diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-12 15:49:56 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-12 15:49:56 -0500 |
| commit | d8c38b0ac4d4b8885fe96230a234761187bd3f58 (patch) | |
| tree | 4a5488754bdbca621424f79b255e0ac354c27843 | |
| parent | 13256aad033f84c0854f2d562685ea808a5ec619 (diff) | |
| download | rulesets-d8c38b0ac4d4b8885fe96230a234761187bd3f58.tar.gz rulesets-d8c38b0ac4d4b8885fe96230a234761187bd3f58.zip | |
chore: drop the Signal triage-intake plugin
Remove the triage-intake Signal source plugin and de-list Signal from the engine's plugin enumeration. I'm rebuilding the Signal client (signel + signal-cli) from scratch, so the plugin would scan against an unstable client. The signal MCP server and its README setup stay. Re-add the plugin when the client is stable.
| -rw-r--r-- | .ai/workflows/triage-intake.org | 4 | ||||
| -rw-r--r-- | .ai/workflows/triage-intake.signal.org | 116 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/triage-intake.org | 4 | ||||
| -rw-r--r-- | claude-templates/.ai/workflows/triage-intake.signal.org | 116 |
4 files changed, 4 insertions, 236 deletions
diff --git a/.ai/workflows/triage-intake.org b/.ai/workflows/triage-intake.org index b804de1..b257f2d 100644 --- a/.ai/workflows/triage-intake.org +++ b/.ai/workflows/triage-intake.org @@ -48,7 +48,7 @@ The engine has no sources baked in. It discovers them by globbing *two* director ls .ai/workflows/triage-intake.*.org .ai/project-workflows/triage-intake.*.org 2>/dev/null #+end_src -- =.ai/workflows/triage-intake.*.org= — *general* source plugins, template-synced (personal Gmail, personal calendar, cmail/Proton, Signal, Telegram, personal GitHub PRs). +- =.ai/workflows/triage-intake.*.org= — *general* source plugins, template-synced (personal Gmail, personal calendar, cmail/Proton, Telegram, personal GitHub PRs). - =.ai/project-workflows/triage-intake.*.org= — *PROJECT-SPECIFIC* source plugins, never synced, owned by this project (e.g. a work project's Linear, work Gmail, work Slack, enterprise-GitHub PRs). ⚠ *THE #1 FAILURE MODE — read this twice.* Globbing only =.ai/workflows/= and silently missing every project plugin. If you skip =.ai/project-workflows/=, the sweep runs with *half its sources* and Craig never learns what it dropped — the omission is invisible, because a missing source looks identical to a quiet source in the output. There is no error, no empty block, no warning. The sweep just lies by omission. *Glob both directories. Always.* @@ -327,7 +327,7 @@ The sentinel is checked into git, but git tracks content, not mtime — so an mt Craig, via the work project's same-day handoff: "we only need to report if anything's changed when we do triage intake." Sweep summaries report deltas only — a new invite, a new/moved/cancelled event, a new message needing attention. Unchanged sources get no block (the "Calendar — quiet" roll-call is retired), and an all-quiet sweep renders as a single "HH:MM sweep: no changes" line. Failures keep their loud banner (never folded into the no-change line) and the suggested-actions line stays when actions are queued. Same ruling: the telegram plugin's dev-community group traffic is dropped from reports entirely unless Craig asks (see that plugin's 2026-06-11 note). **** 2026-06-10: Loud failure surfacing (Phase C item 0 + Common Mistake 9) -Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the signal and telegram plugins' failure paths point at this rule. +Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. **** 2026-05-26: Refactor into engine + source plugins Split the monolithic workflow into a source-agnostic engine (this file) and per-source plugins named =triage-intake.<source>.org=. The engine carries the anchor/sentinel logic, the four-bucket model, the Phase A-D orchestration, the todo.org persistence convention, and the exit criteria. Each source's scan/classify/render/action knowledge moved to its own plugin. General plugins (personal-gmail, personal-calendar, cmail, github-prs) live in =.ai/workflows/= and are template-synced; project-specific plugins (a work project's Linear, work Gmail, work Slack, enterprise PRs) live in the project's =.ai/project-workflows/= and are never synced. Phase 0 globs *both* directories — the loud requirement, because missing the project dir silently halves the sweep. Naming convention: first dot is the engine/plugin boundary, deeper dots reserved for sub-adapters. This removed all DeepSat/Linear specifics from the engine; they become work-project plugins. diff --git a/.ai/workflows/triage-intake.signal.org b/.ai/workflows/triage-intake.signal.org deleted file mode 100644 index 978241d..0000000 --- a/.ai/workflows/triage-intake.signal.org +++ /dev/null @@ -1,116 +0,0 @@ -#+TITLE: Triage Intake — Signal Source -#+AUTHOR: Craig Jennings & Claude -#+DATE: 2026-06-10 - -# Source plugin for the triage-intake engine. See triage-intake.org for the -# contract and the Phase A-D orchestration. This file declares ONE source. -# -# General (personal) source: Signal Private Messenger. Emacs is the ONLY way -# to check Signal (Craig's ruling, 2026-06-10). His Emacs daemon runs -# signal-cli in jsonRpc mode via the signel package (~/code/signel, wired in -# ~/.emacs.d/modules/signal-config.el), and that daemon OWNS the account: it -# drains the server queue continuously, writes messages into *Signel: <id>* -# buffers, and fires desktop notifications. -# -# ⚠ NEVER run a standalone `signal-cli receive`. signel and standalone -# signal-cli share the same account config and device queue, so a standalone -# drain STEALS messages the Emacs client would otherwise show Craig — and if -# the daemon is live it hangs on the account lock besides (observed -# 2026-06-10: a backgrounded receive blocked 3+ minutes and the sweep shipped -# without Signal). signal-cli's only legitimate direct uses are -# queue-untouching reads like `signal-cli listAccounts`. - -* Source: signal -:PROPERTIES: -:ORDER: 22 -:ENABLED: command -v emacsclient -:ANCHOR: none -:SUBAGENT_OVER: 40 -:END: - -** Scan - -*** Step 0 — ensure signel is running (start it if down, leave it running) - -#+begin_src bash -SIGNEL_LIVE=$(emacsclient -e "(and (featurep 'signel) (process-live-p (get-process \"signal-rpc\")) t)" 2>/dev/null) -# Belt and suspenders — the raw process check catches a signel daemon even -# if the elisp probe fails: -pgrep -f "org.asamk.signal.Main.*jsonRpc" >/dev/null && SIGNEL_LIVE=t -echo "signel daemon live: ${SIGNEL_LIVE:-nil}" -#+end_src - -- =t= → proceed straight to the buffer query below. -- =nil= → *start signel through Emacs* and leave it running — a live signel is the desired steady state (unlike telega's leave-no-trace lifecycle, there is no teardown step): - - #+begin_src bash - emacsclient -e "(progn (require 'signel) (cj/signel--ensure-started))" - sleep 5 # let queued envelopes land in the chat buffers - #+end_src - -- emacsclient unreachable, or signel fails to start → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet." - -*** Buffer query - -signel keeps three sources of truth: =signel--active-chats= (hash of chat-ids seen this Emacs session), =signel--contact-map= (number → display name), and the =*Signel: <id>*= chat buffers (message lines stamped =[HH:MM]=, senders as =<Name>=, Craig's own as =<Me>=). - -One emacsclient call returns every active chat with its name and recent buffer tail: - -#+begin_src bash -emacsclient -e "(progn (require 'signel) - (let (chats) - (maphash (lambda (id _v) - (push (list id (or (gethash id signel--contact-map) \"?\") - (let ((b (get-buffer (format \"*Signel: %s*\" id)))) - (if b (with-current-buffer b - (buffer-substring-no-properties (max (point-min) (- (point-max) 1500)) (point-max))) - \"no buffer\"))) - chats)) - signel--active-chats) - chats))" -#+end_src - -Phase B reads the =[HH:MM]= stamps against the anchor to find what's new, and reads who spoke last: a thread whose last line is =<Me>= (or a closing acknowledgment from the contact) carries no reply owed; a thread ending on the contact's question does. - -Caveats, stated honestly in the render when they bite: -- =signel--active-chats= covers only chats with traffic since the Emacs daemon (or signel) last started. It is not a full history. -- A freshly-started signel shows only what arrives after the start plus the queued envelopes that land on connect. The phone and Signal Desktop still hold everything. -- Timestamps are =[HH:MM]= with no date. Treat stamps as today's unless buffer position says otherwise. - -** Classify - -Bias: Signal is personal, conversational, and time-sensitive — it leans *Action*, because a direct message usually carries an implicit "respond." Volume is low, signal-to-noise high (the opposite of personal Gmail). - -- *Action:* an explicit ask, a question, a scheduling request, a reply owed to a person. A thread ending on the contact's unanswered question is the prime case. -- *FYI:* a substantive message with no response owed — a link shared, a heads-up, a thread Craig already closed (last word =<Me>= or a contact's closing ack). -- *Noise-keep / trash:* automated notifications, reactions, sync echoes of Craig's own sends. Tally only. - -Flag a message from a contact Craig is mid-thread with prominently — a dropped personal reply is the expensive miss here. - -** Render - -#+begin_example -**Signal — N active chats, M with new traffic since anchor.** <one-line summary> -- Action: <items, sender + gist, reply owed called out> -- FYI: <threads with new traffic Craig already handled, terse> -- Noise: <tally> -- (Caveat line, only when relevant: "covers chats since signel started <when>") -#+end_example - -Omit the block ONLY when the scan genuinely ran and found nothing. A scan that could not run is never "quiet" — it renders as a loud SCAN FAILED line at the top of the whole summary (engine rule). - -** Actions - -All through the signel daemon: - -- reply (programmatic) :: =emacsclient -e "(progn (require 'signel) (signel--send-rpc \"send\" '((message . \"<body>\") (recipient . [\"+1555...\"]))))"= — for a group, replace =recipient= with =(groupId . \"<id>\")=. Public-facing (goes out under Craig's name): run =/voice personal= first. -- reply (interactive) :: =emacsclient -e "(cj/signel-message)"= pops Craig's contact picker + chat buffer for a considered reply by hand. -- attach :: open the chat buffer, =signel-attach-file=. - -No mark-read step: the daemon already consumed the queue. - -** History - -- 2026-06-08: initial standalone-receive plugin. -- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the dual-path split, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly. -- 2026-06-10 (correction, same day): Craig's ruling — Emacs is the ONLY way to check Signal. Path B (standalone draining receive) removed entirely: signel and standalone signal-cli share the same account config and device queue, so a standalone drain steals messages from the Emacs client. Step 0 now starts signel via =cj/signel--ensure-started= when it's down and leaves it running (running signel is the steady state; no teardown). signal-cli survives only for queue-untouching reads like =listAccounts=. diff --git a/claude-templates/.ai/workflows/triage-intake.org b/claude-templates/.ai/workflows/triage-intake.org index b804de1..b257f2d 100644 --- a/claude-templates/.ai/workflows/triage-intake.org +++ b/claude-templates/.ai/workflows/triage-intake.org @@ -48,7 +48,7 @@ The engine has no sources baked in. It discovers them by globbing *two* director ls .ai/workflows/triage-intake.*.org .ai/project-workflows/triage-intake.*.org 2>/dev/null #+end_src -- =.ai/workflows/triage-intake.*.org= — *general* source plugins, template-synced (personal Gmail, personal calendar, cmail/Proton, Signal, Telegram, personal GitHub PRs). +- =.ai/workflows/triage-intake.*.org= — *general* source plugins, template-synced (personal Gmail, personal calendar, cmail/Proton, Telegram, personal GitHub PRs). - =.ai/project-workflows/triage-intake.*.org= — *PROJECT-SPECIFIC* source plugins, never synced, owned by this project (e.g. a work project's Linear, work Gmail, work Slack, enterprise-GitHub PRs). ⚠ *THE #1 FAILURE MODE — read this twice.* Globbing only =.ai/workflows/= and silently missing every project plugin. If you skip =.ai/project-workflows/=, the sweep runs with *half its sources* and Craig never learns what it dropped — the omission is invisible, because a missing source looks identical to a quiet source in the output. There is no error, no empty block, no warning. The sweep just lies by omission. *Glob both directories. Always.* @@ -327,7 +327,7 @@ The sentinel is checked into git, but git tracks content, not mtime — so an mt Craig, via the work project's same-day handoff: "we only need to report if anything's changed when we do triage intake." Sweep summaries report deltas only — a new invite, a new/moved/cancelled event, a new message needing attention. Unchanged sources get no block (the "Calendar — quiet" roll-call is retired), and an all-quiet sweep renders as a single "HH:MM sweep: no changes" line. Failures keep their loud banner (never folded into the no-change line) and the suggested-actions line stays when actions are queued. Same ruling: the telegram plugin's dev-community group traffic is dropped from reports entirely unless Craig asks (see that plugin's 2026-06-11 note). **** 2026-06-10: Loud failure surfacing (Phase C item 0 + Common Mistake 9) -Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the signal and telegram plugins' failure paths point at this rule. +Craig: "highlight any failures in daily triage loudly. I get important communication from all these channels." Trigger: the 2026-06-10 sweep shipped with Signal silently missing — a standalone receive hung on the account lock while the signel daemon owned it, and the failure looked identical to a quiet source. Failures now lead the summary in a ⚠ SCAN FAILED banner; the telegram plugin's failure path points at this rule. **** 2026-05-26: Refactor into engine + source plugins Split the monolithic workflow into a source-agnostic engine (this file) and per-source plugins named =triage-intake.<source>.org=. The engine carries the anchor/sentinel logic, the four-bucket model, the Phase A-D orchestration, the todo.org persistence convention, and the exit criteria. Each source's scan/classify/render/action knowledge moved to its own plugin. General plugins (personal-gmail, personal-calendar, cmail, github-prs) live in =.ai/workflows/= and are template-synced; project-specific plugins (a work project's Linear, work Gmail, work Slack, enterprise PRs) live in the project's =.ai/project-workflows/= and are never synced. Phase 0 globs *both* directories — the loud requirement, because missing the project dir silently halves the sweep. Naming convention: first dot is the engine/plugin boundary, deeper dots reserved for sub-adapters. This removed all DeepSat/Linear specifics from the engine; they become work-project plugins. diff --git a/claude-templates/.ai/workflows/triage-intake.signal.org b/claude-templates/.ai/workflows/triage-intake.signal.org deleted file mode 100644 index 978241d..0000000 --- a/claude-templates/.ai/workflows/triage-intake.signal.org +++ /dev/null @@ -1,116 +0,0 @@ -#+TITLE: Triage Intake — Signal Source -#+AUTHOR: Craig Jennings & Claude -#+DATE: 2026-06-10 - -# Source plugin for the triage-intake engine. See triage-intake.org for the -# contract and the Phase A-D orchestration. This file declares ONE source. -# -# General (personal) source: Signal Private Messenger. Emacs is the ONLY way -# to check Signal (Craig's ruling, 2026-06-10). His Emacs daemon runs -# signal-cli in jsonRpc mode via the signel package (~/code/signel, wired in -# ~/.emacs.d/modules/signal-config.el), and that daemon OWNS the account: it -# drains the server queue continuously, writes messages into *Signel: <id>* -# buffers, and fires desktop notifications. -# -# ⚠ NEVER run a standalone `signal-cli receive`. signel and standalone -# signal-cli share the same account config and device queue, so a standalone -# drain STEALS messages the Emacs client would otherwise show Craig — and if -# the daemon is live it hangs on the account lock besides (observed -# 2026-06-10: a backgrounded receive blocked 3+ minutes and the sweep shipped -# without Signal). signal-cli's only legitimate direct uses are -# queue-untouching reads like `signal-cli listAccounts`. - -* Source: signal -:PROPERTIES: -:ORDER: 22 -:ENABLED: command -v emacsclient -:ANCHOR: none -:SUBAGENT_OVER: 40 -:END: - -** Scan - -*** Step 0 — ensure signel is running (start it if down, leave it running) - -#+begin_src bash -SIGNEL_LIVE=$(emacsclient -e "(and (featurep 'signel) (process-live-p (get-process \"signal-rpc\")) t)" 2>/dev/null) -# Belt and suspenders — the raw process check catches a signel daemon even -# if the elisp probe fails: -pgrep -f "org.asamk.signal.Main.*jsonRpc" >/dev/null && SIGNEL_LIVE=t -echo "signel daemon live: ${SIGNEL_LIVE:-nil}" -#+end_src - -- =t= → proceed straight to the buffer query below. -- =nil= → *start signel through Emacs* and leave it running — a live signel is the desired steady state (unlike telega's leave-no-trace lifecycle, there is no teardown step): - - #+begin_src bash - emacsclient -e "(progn (require 'signel) (cj/signel--ensure-started))" - sleep 5 # let queued envelopes land in the chat buffers - #+end_src - -- emacsclient unreachable, or signel fails to start → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet." - -*** Buffer query - -signel keeps three sources of truth: =signel--active-chats= (hash of chat-ids seen this Emacs session), =signel--contact-map= (number → display name), and the =*Signel: <id>*= chat buffers (message lines stamped =[HH:MM]=, senders as =<Name>=, Craig's own as =<Me>=). - -One emacsclient call returns every active chat with its name and recent buffer tail: - -#+begin_src bash -emacsclient -e "(progn (require 'signel) - (let (chats) - (maphash (lambda (id _v) - (push (list id (or (gethash id signel--contact-map) \"?\") - (let ((b (get-buffer (format \"*Signel: %s*\" id)))) - (if b (with-current-buffer b - (buffer-substring-no-properties (max (point-min) (- (point-max) 1500)) (point-max))) - \"no buffer\"))) - chats)) - signel--active-chats) - chats))" -#+end_src - -Phase B reads the =[HH:MM]= stamps against the anchor to find what's new, and reads who spoke last: a thread whose last line is =<Me>= (or a closing acknowledgment from the contact) carries no reply owed; a thread ending on the contact's question does. - -Caveats, stated honestly in the render when they bite: -- =signel--active-chats= covers only chats with traffic since the Emacs daemon (or signel) last started. It is not a full history. -- A freshly-started signel shows only what arrives after the start plus the queued envelopes that land on connect. The phone and Signal Desktop still hold everything. -- Timestamps are =[HH:MM]= with no date. Treat stamps as today's unless buffer position says otherwise. - -** Classify - -Bias: Signal is personal, conversational, and time-sensitive — it leans *Action*, because a direct message usually carries an implicit "respond." Volume is low, signal-to-noise high (the opposite of personal Gmail). - -- *Action:* an explicit ask, a question, a scheduling request, a reply owed to a person. A thread ending on the contact's unanswered question is the prime case. -- *FYI:* a substantive message with no response owed — a link shared, a heads-up, a thread Craig already closed (last word =<Me>= or a contact's closing ack). -- *Noise-keep / trash:* automated notifications, reactions, sync echoes of Craig's own sends. Tally only. - -Flag a message from a contact Craig is mid-thread with prominently — a dropped personal reply is the expensive miss here. - -** Render - -#+begin_example -**Signal — N active chats, M with new traffic since anchor.** <one-line summary> -- Action: <items, sender + gist, reply owed called out> -- FYI: <threads with new traffic Craig already handled, terse> -- Noise: <tally> -- (Caveat line, only when relevant: "covers chats since signel started <when>") -#+end_example - -Omit the block ONLY when the scan genuinely ran and found nothing. A scan that could not run is never "quiet" — it renders as a loud SCAN FAILED line at the top of the whole summary (engine rule). - -** Actions - -All through the signel daemon: - -- reply (programmatic) :: =emacsclient -e "(progn (require 'signel) (signel--send-rpc \"send\" '((message . \"<body>\") (recipient . [\"+1555...\"]))))"= — for a group, replace =recipient= with =(groupId . \"<id>\")=. Public-facing (goes out under Craig's name): run =/voice personal= first. -- reply (interactive) :: =emacsclient -e "(cj/signel-message)"= pops Craig's contact picker + chat buffer for a considered reply by hand. -- attach :: open the chat buffer, =signel-attach-file=. - -No mark-read step: the daemon already consumed the queue. - -** History - -- 2026-06-08: initial standalone-receive plugin. -- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the dual-path split, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly. -- 2026-06-10 (correction, same day): Craig's ruling — Emacs is the ONLY way to check Signal. Path B (standalone draining receive) removed entirely: signel and standalone signal-cli share the same account config and device queue, so a standalone drain steals messages from the Emacs client. Step 0 now starts signel via =cj/signel--ensure-started= when it's down and leaves it running (running signel is the steady state; no teardown). signal-cli survives only for queue-untouching reads like =listAccounts=. |
