aboutsummaryrefslogtreecommitdiff
path: root/.ai/workflows
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-08-20 06:13:35 -0700
committerCraig Jennings <c@cjennings.net>2026-08-20 06:13:35 -0700
commit23d1d998995a776b5c184c6719602b1aba2c1d41 (patch)
tree848e5806f6eca0b826a84e4d20ebc1ddacf5d2d4 /.ai/workflows
parentca1bb871417afafc319c115d6ae69fa8cd3fe53c (diff)
downloadrulesets-23d1d998995a776b5c184c6719602b1aba2c1d41.tar.gz
rulesets-23d1d998995a776b5c184c6719602b1aba2c1d41.zip
fix(cmail): install cmail-action on PATH instead of per-project copies
protocols.org said cmail-action was symlinked into ~/.local/bin and on PATH from any project. It wasn't on any machine. The only copies were the per-project .ai/scripts/cmail-action.py the template sync delivers, so the documented invocation died with command not found. The claim wasn't aspirational. A provisioning script used to create that symlink from ~/projects/claude-templates/, and that path went away when claude-templates became a subtree here. The doc outlived its mechanism. The script has no project coupling (every path in it is under $HOME against a hardcoded bridge address). I moved it to claude-templates/bin/, which make install already links. The documented command is now true, and 17 copies collapse to one. Its test moved to scripts/tests/ and needed an explicit SourceFileLoader, because dropping the .py suffix stops importlib inferring one from the extension. The test target gained a pytest pass over scripts/tests. Without it the move would have dropped 57 tests while still reporting green. triage-intake's cmail gate now tests command -v cmail-action rather than a file path, so it checks what it runs. protocols.org names the repair when the command is missing, and names Proton Bridge as a precondition.
Diffstat (limited to '.ai/workflows')
-rw-r--r--.ai/workflows/startup.org2
-rw-r--r--.ai/workflows/triage-intake.cmail.org12
2 files changed, 7 insertions, 7 deletions
diff --git a/.ai/workflows/startup.org b/.ai/workflows/startup.org
index bc89256..d52add6 100644
--- a/.ai/workflows/startup.org
+++ b/.ai/workflows/startup.org
@@ -205,7 +205,7 @@ Notes on what =sync-templates= does (the rsync behavior it carries):
- The =scripts/= sync excludes Python build artifacts (=__pycache__/=, =.pytest_cache/=, =*.pyc=). Running rulesets' own pytest leaves these in =claude-templates/.ai/scripts/tests/=, and =rsync -a= copies by disk presence regardless of =.gitignore=, so without the excludes every consuming project's tree gets polluted with machine-specific cache files. The excludes also protect existing dest copies from =--delete= cleanup, so a project that already received the cache must remove it once by hand.
- The sync is guarded to skip when rulesets has uncommitted changes under the synced source paths. =rsync -a --delete= copies the working tree by disk presence, so without the guard a downstream session started while rulesets had in-flight WIP would pull that WIP into its =.ai/workflows/= and =.ai/scripts/=, surfacing as drift the user never authored (and tempting a fake "chore: sync .ai tooling" commit). The guard is scoped to the synced paths, not the whole repo, so unrelated rulesets dirt doesn't block the sync. From the jr-estate handoff 2026-05-29.
- The sync is also guarded to skip when the *project* branch is behind its upstream (=proj_behind=). Phase A.0 correctly declines to fast-forward a diverged or behind-and-dirty branch, but the rsync would then land templates on the stale committed =.ai/= baseline — a huge diff measured against old content that conflicts once the branch reconciles to upstream's newer templates. Skipping is safe: the sync runs next session once the branch is current. Not an auto-discard — startup never =git checkout=s drift away, because a legitimate local stopgap in a synced file is indistinguishable from accidental drift by content alone (home reverted an intentional =flashcard-to-anki.py= fix this way on 2026-06-22). Prevention is safe; blind cleanup-after is not. Phase C's template-sync-churn safety net still surfaces any pre-existing dirt for a human decision. From the home handoff 2026-07-04.
-- The sync touches only =protocols.org=, =workflows/=, and =scripts/=. The project-owned dirs =project-workflows/= and =project-scripts/= are deliberately *outside* the synced set, so a project's own workflows and scripts survive startup. This is why a project script that a workflow imports must live in =.ai/project-scripts/=, never =.ai/scripts/= — the latter is wiped to match the template by =--delete= on every startup. Naming: a script imported as a Python module needs an importable name (underscores, e.g. =zlibrary_api.py=); a CLI-invoked script can stay kebab-case like the template tooling (=cmail-action.py=).
+- The sync touches only =protocols.org=, =workflows/=, and =scripts/=. The project-owned dirs =project-workflows/= and =project-scripts/= are deliberately *outside* the synced set, so a project's own workflows and scripts survive startup. This is why a project script that a workflow imports must live in =.ai/project-scripts/=, never =.ai/scripts/= — the latter is wiped to match the template by =--delete= on every startup. Naming: a script imported as a Python module needs an importable name (underscores, e.g. =zlibrary_api.py=); a CLI-invoked script can stay kebab-case like the template tooling (=inbox-status=).
Rationale: Every call in Phase A is read-only or writes to a distinct path. Running them sequentially wastes round-trips; running them in parallel gives Claude the complete starting picture in one round-trip.
diff --git a/.ai/workflows/triage-intake.cmail.org b/.ai/workflows/triage-intake.cmail.org
index 8d8abfb..66ecf80 100644
--- a/.ai/workflows/triage-intake.cmail.org
+++ b/.ai/workflows/triage-intake.cmail.org
@@ -8,7 +8,7 @@
* Source: cmail
:PROPERTIES:
:ORDER: 25
-:ENABLED: test -f .ai/scripts/cmail-action.py
+:ENABLED: command -v cmail-action
:ANCHOR: none
:SUBAGENT_OVER: 50
:END:
@@ -18,7 +18,7 @@
Proton (=c@cjennings.net=) via the bridge script. =ANCHOR: none= because this reports live IMAP unread *state*, not a since-window — the engine substitutes no cutoff. Phase B uses the anchor only to flag which of the current unread arrived since last check.
#+begin_src bash
-python3 .ai/scripts/cmail-action.py list-unread
+cmail-action list-unread
#+end_src
JSON output, keyed by UID. The script ignores messages already flagged =\Deleted= (those are pending-flush on the next Proton sync), so the list is the genuinely-live unread set.
@@ -47,7 +47,7 @@ Omit if zero unread.
All take one or more UIDs (from the =list-unread= JSON):
-- mark-read :: =python3 .ai/scripts/cmail-action.py mark-read <uid>=
-- star :: =python3 .ai/scripts/cmail-action.py star <uid>=
-- unstar :: =python3 .ai/scripts/cmail-action.py unstar <uid>=
-- trash :: =python3 .ai/scripts/cmail-action.py trash <uid>= (flags =\Deleted=; flushed on next Proton sync)
+- mark-read :: =cmail-action mark-read <uid>=
+- star :: =cmail-action star <uid>=
+- unstar :: =cmail-action unstar <uid>=
+- trash :: =cmail-action trash <uid>= (flags =\Deleted=; flushed on next Proton sync)