aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/design/wrapup-routing-spec.org177
-rw-r--r--todo.org14
2 files changed, 180 insertions, 11 deletions
diff --git a/docs/design/wrapup-routing-spec.org b/docs/design/wrapup-routing-spec.org
new file mode 100644
index 0000000..396fb45
--- /dev/null
+++ b/docs/design/wrapup-routing-spec.org
@@ -0,0 +1,177 @@
+#+TITLE: Wrap-Up Inbox/Transcript Routing — Spec
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-06-13
+#+TODO: TODO | DONE SUPERSEDED CANCELLED
+
+* Metadata
+| Status | draft |
+|----------+-----------------------------------------------------|
+| Owner | Craig Jennings |
+|----------+-----------------------------------------------------|
+| Reviewer | Codex (spec-review) |
+|----------+-----------------------------------------------------|
+| Related | [[file:../../todo.org][todo.org: wrap-up routing task]] · [[file:2026-06-13-wrapup-inbox-transcript-routing-proposal.org][archsetup proposal]] |
+|----------+-----------------------------------------------------|
+
+* Summary
+
+At wrap-up, an inbox handoff that belongs to another project has nowhere to go but the current project's =todo.org= or a deferral. This adds an optional routing step to =wrap-it-up.org=: surface the items that belong elsewhere, recommend a destination project for each, and move the whole batch there on one confirmation. A parallel step files meeting-transcript recordings into the right project's =assets/=.
+
+* Problem / Context
+
+=process-inbox.org= dispositions each handoff as act / fold / file / reject, and "file as TODO" lands the task in the *current* project's =todo.org=. When the real home is a different project, the choices today are: file it locally and let it rot in the wrong tracker, hand-edit two projects' =todo.org= files, or defer it and carry the debt to next session.
+
+The wrap-up's existing Step 3 "Inbox sanity check" only counts unprocessed items and blocks the wrap until they clear. It answers "is the inbox clean?" — it doesn't route anything.
+
+Meeting transcripts have the same homelessness: a recording dropped during a session belongs in some project's =assets/=, but nothing moves it there at wrap.
+
+The friction is small per-item but recurring, and the manual cross-project edit is error-prone (two files, two repos, easy to leave one half-done).
+
+* Goals and Non-Goals
+
+** Goals
+- At wrap-up, surface inbox items (and transcripts) whose home is a different project, with a recommended destination each.
+- Move the whole batch on one confirmation ("go with recommendations") or leave it entirely ("skip"). No per-item triage.
+- Move a task/event item as a proper org task into the destination's "Open Work" section per =todo-format.md=; move a transcript as a flat-filed artifact per =working-files.md=.
+- Keep the move atomic and visible (it shows in the destination's next git diff, with a provenance note).
+- Discover any project with a =todo.org= as a candidate destination, not only =.ai/= projects.
+
+** Non-Goals
+- Not a wrap gate. A skip is a clean, complete wrap.
+- Not per-item triage. The interaction is batch-level: go or skip.
+- Not a replacement for =process-inbox.org='s value gate. Routing assumes the item is already an accepted keeper.
+- Not a confidence-free auto-mover. A low-confidence destination recommendation says so, and the batch "go" stays trustworthy because the surfaced list is reviewable before the keystroke.
+
+** Scope tiers
+- v1: task/event routing to a destination project's =todo.org=. The interaction, the recommendation engine, the atomic move helper, the widened project discovery.
+- Out of scope: per-item destination editing, an interactive correction loop, moving items that aren't accepted keepers.
+- vNext: meeting-transcript filing (gated on the unresolved source-location decision and the file-vs-file+extract question — see Decisions).
+
+* Design
+
+** User-facing (the wrap interaction)
+
+The router is a new sub-step of =wrap-it-up.org='s Step 3, running after the existing inbox sanity check. When it finds inbox items whose recommended home is a different project, it surfaces them as a list, one line each: the item, the recommended destination project, and a confidence marker when the inference is weak. Then two options, batch-level:
+
+1. Go with the recommendations — apply every recommended move.
+2. Skip — leave the whole batch in place. A skip is a clean wrap.
+
+That is the entire interaction. No per-item walk. The surfaced list is the review surface; the single keystroke is trustworthy because the list was reviewable and low-confidence recommendations flagged themselves.
+
+A move of a task/event relocates it into the destination project's "Open Work" section as a proper org task (terse heading, body for detail, tags on the heading line, per =todo-format.md=), and removes it from the source. A skipped or unroutable item stays where it is; the existing sanity check still governs whether the wrap is clean.
+
+** Implementer (the mechanics)
+
+*Destination discovery.* Widen the project-discovery filter from "directory with a =.ai/protocols.org= marker" (what =inbox-send.py= and the =ai= launcher use) to "directory with a =todo.org= containing a level-1 'Open Work' heading." A plain code repo Craig keeps a =todo.org= in is a valid destination; an =.ai/= directory is not required.
+
+*Destination anchor.* Reuse =todo-cleanup.el='s existing matcher: =tc--find-section= locates the unique level-1 heading containing "Open Work" (case-insensitive) and returns =nil= / ='multiple= when absent or ambiguous. A destination whose =todo.org= lacks a clean Open Work heading is surfaced and skipped, never guessed at.
+
+*The move helper.* A small tool inserts a task subtree under a named project's "Open Work" heading and removes the source atomically — extend =todo-cleanup.el= (it already owns the section matcher and the subtree-move logic for =--archive-done=) or add a sibling =.ai/scripts= tool. Hand-editing across two repos is the error-prone path this replaces.
+
+*Recommendation engine.* Infer the destination from the item's content — project names, file paths, topic words — matched against the discovered project list. Conservative by design: a weak match is labeled low-confidence so "go" stays a safe single keystroke. The engine is the interesting, uncertain part; it earns the spec.
+
+*Cross-project write discipline.* Moving an item into project X's =todo.org= writes into X's scope (=cross-project.md=). The batch "go" authorizes it, but the move stays visible (X's next git diff) and leaves a one-line provenance note on the moved task naming the source project.
+
+* Alternatives Considered
+
+** Per-item triage instead of batch go/skip
+- Good, because it gives precise control over each destination.
+- Bad, because it taxes the common case (a batch that's all-correct, or all-stay) with a walk. Craig explicitly asked for two options, not a triage loop.
+- Neutral, because per-item correction could return as a vNext refinement if batch-only proves too blunt.
+
+** Fold the router into the existing Inbox sanity check step
+- Good, because one inbox step is simpler than two.
+- Bad, because the sanity check *gates* the wrap (blocks until clean) and the router is *optional* (skip is clean). Merging a blocking check with an optional action muddies both.
+- Neutral, because the two could share discovery code while staying separate steps. (This is open decision D1.)
+
+** Reuse process-inbox's "file as TODO" with a destination argument
+- Good, because it avoids a second mechanism.
+- Bad, because =process-inbox= runs per-item mid-session against the local project; the router runs at wrap, batch-level, cross-project. Different cadence, different scope.
+- Neutral, because both ultimately call the same atomic move helper — the helper is the shared primitive, the two callers stay distinct.
+
+* Decisions [/]
+
+** DONE Reuse the Open Work matcher for destination anchoring
+- Context: the move needs a reliable insertion point in the destination =todo.org=; guessing risks corrupting another project's file.
+- Decision: We will reuse =todo-cleanup.el='s =tc--find-section "open work"= matcher, which already handles the unique / missing / ambiguous cases, and skip+surface any destination without a clean Open Work heading.
+- Consequences: easier — no new parser, consistent with =--archive-done=. Harder — destinations must carry the "Open Work" heading convention, so a project with a differently-named section is silently unroutable until it conforms.
+
+** DONE Move atomically through a helper, never hand-edit two repos
+- Context: a move touches two files in two repos; a half-done move loses or duplicates a task.
+- Decision: We will route every move through one helper (extend =todo-cleanup.el= or a sibling =.ai/scripts= tool) that inserts under the destination's Open Work heading and removes the source as one operation.
+- Consequences: easier — no partial-move corruption, one place to test. Harder — a new helper to build and cover with tests before the router can ship.
+
+** DONE Cross-project writes stay visible and carry provenance
+- Context: writing into another project's =todo.org= crosses the =cross-project.md= scope boundary.
+- Decision: We will treat the batch "go" as the authorization, leave the move visible in the destination's git diff, and stamp a one-line provenance note (source project + date) on each moved task.
+- Consequences: easier — the boundary rule is honored without a per-move prompt. Harder — the destination's next session sees an externally-authored task it didn't file, so the provenance note is load-bearing, not decorative.
+
+** TODO Separate router step or merge into the inbox sanity check
+- Owner / by-when: Craig / before implementation.
+- Context: the sanity check gates the wrap; the router is optional. archsetup leans separate; so do I (a blocking check and an optional action read more clearly apart). But it's Craig's wrap-cadence call.
+- Decision: We will (proposed) keep the router a *separate* optional sub-step running after the sanity check, sharing only the discovery code.
+- Consequences: easier — each step has one job, the optional one can't accidentally block. Harder — two inbox-touching steps in one wrap, slightly more workflow prose.
+
+** TODO Transcript routing — scope and trigger
+- Owner / by-when: Craig / before any transcript work starts.
+- Context: transcripts file as artifacts, not tasks, and a meeting usually produces both a recording to keep and action items to track. Two unknowns block it: where recordings accumulate (a recordings inbox, a downloads dir, wherever the meeting tooling drops them), and whether filing should also extract action items into the destination's =todo.org=.
+- Decision: We will (proposed) defer transcript routing to vNext, file the recording as an artifact only, and let action items route through the v1 task-router as a separate batch — keeping documents and tasks orthogonal.
+- Consequences: easier — v1 ships without the unresolved source-location dependency. Harder — a meeting's recording and its action items get handled by two mechanisms, which Craig may prefer combined into one "process this transcript" move.
+
+** TODO Reconcile with the process-inbox defer-and-stage router
+- Owner / by-when: Craig + author / before implementation.
+- Context: the 2026-06-12 Skeptical Review added a defer-and-stage path in =process-inbox.org= that files a =[#B]= VERIFY for shared-asset proposals parked for review. That also moves an inbox item toward a =todo.org= task — overlapping surface with this router.
+- Decision: We will (proposed) keep them distinct: defer-and-stage parks a *proposal under review* locally as a VERIFY; this router moves an *accepted keeper* to its home project. Shared primitive is the atomic move helper, not the policy.
+- Consequences: easier — two clear, non-competing policies. Harder — the workflows must state the boundary so a future reader doesn't collapse them into one and reintroduce the ambiguity.
+
+* Implementation phases
+
+** Phase 1 — Widened project discovery
+A discovery function returning every project with a =todo.org= that has a clean Open Work heading, reusing =tc--find-section=. Unit-tested against fixtures: =.ai/= project, plain-code-repo-with-todo, todo-without-Open-Work (excluded), ambiguous-Open-Work (excluded). Leaves the tree working — nothing calls it yet.
+
+** Phase 2 — Atomic cross-project move helper
+Extend =todo-cleanup.el= (or sibling tool) with a "move this subtree into project X's Open Work" operation that inserts at the destination and removes the source as one step, stamping the provenance line. ERT coverage: successful move, missing-destination-heading refusal, source-removal-on-success, no-partial-move-on-failure.
+
+** Phase 3 — Recommendation engine
+Infer destination from item content against the discovered list, with a confidence label. Unit-tested: strong match (project named in item), weak match (topic-only → low-confidence), no match (stays put). Pure function over (item, project-list) → (destination, confidence).
+
+** Phase 4 — Wrap-up step wiring
+Add the router sub-step to =wrap-it-up.org= Step 3: surface the batch, the two options, apply-on-go via the Phase 2 helper. Per the D1/D5 decisions once settled. Sync the =.ai/= mirror.
+
+** Phase 5 — Transcript routing (vNext, gated on the transcript decision)
+Only after the transcript-scope decision resolves. File a recording into the destination =assets/= per =working-files.md=, batch go/skip mirroring the task router.
+
+* Acceptance criteria
+- [ ] At wrap, an inbox item naming another project is surfaced with that project as the recommended destination.
+- [ ] "Go" moves every recommended item into its destination's Open Work section as a valid org task with a provenance line, and removes it from the source.
+- [ ] "Skip" leaves every item in place and the wrap completes cleanly.
+- [ ] A destination =todo.org= without a clean Open Work heading is surfaced and skipped, never corrupted.
+- [ ] A low-confidence recommendation is visibly labeled in the surfaced list.
+- [ ] A plain code repo with a =todo.org= (no =.ai/=) is a valid destination.
+- [ ] A failed move leaves both source and destination unchanged (no partial move).
+
+* Readiness dimensions
+- Data model & ownership: items are org subtrees; the destination owns the moved task after the move (provenance note records origin). N/A for remote/cached state — all local files.
+- Errors, empty states & failure: missing/ambiguous Open Work heading → skip+surface; failed move → atomic no-op; empty routable set → router stays silent (no prompt).
+- Security & privacy: N/A — local org files, no credentials or external services.
+- Observability: the move shows in the destination's git diff plus the provenance line; the surfaced batch list is the pre-move view.
+- Performance & scale: bounded by inbox size (single digits) and project count (tens); no hot path.
+- Reuse & lost opportunities: reuses =tc--find-section= and todo-cleanup's subtree-move; widens existing discovery rather than adding a parallel one.
+- Architecture fit & weak points: the recommendation engine is the weak point (a wrong-confident destination is the worst failure) — mitigated by the confidence label and reviewable batch list.
+- Config surface: possibly a discovery-root list (defaults to =~/projects/=, =~/code/=, matching =inbox-send.py=). Name it if it needs to be user-visible.
+- Documentation plan: =wrap-it-up.org= step prose; a note in =cross-project.md= that the router is a sanctioned cross-project write path.
+- Dev tooling: ERT for the elisp helper + discovery; the existing =make test= picks up new test files by glob.
+- Rollout, compatibility & rollback: additive workflow step; rollback is removing the sub-step. No persisted-data migration.
+- External APIs & deps: none.
+
+* Risks, Rabbit Holes, and Drawbacks
+- *Recommendation accuracy is the rabbit hole.* A confidently-wrong destination silently files a task in the wrong project. Dodge: keep the engine conservative, label low confidence, and keep the batch list reviewable before the keystroke. Don't chase a clever inference model in v1.
+- *Two inbox-touching steps* (sanity check + router) risk reading as redundant. Dodge: the D1 decision states the gate-vs-optional split in the workflow prose.
+- *Scope creep into transcripts* before the source-location question is answered would stall v1. Dodge: transcripts are explicitly vNext behind decision D4.
+
+* Review and iteration history
+
+** 2026-06-13 Sat @ 01:23:13 -0500 — Claude Code (rulesets) — author
+- What: initial draft. Problem, goals/scope tiers, two-altitude design, alternatives, six decisions (three DONE from grounding, three TODO for Craig), five implementation phases, acceptance criteria, readiness dimensions, risks.
+- Why: the archsetup 2026-06-13 handoff cleared the spec bar in inbox triage and was filed spec-bound rather than applied. This draft turns the proposal into a reviewable design with the open questions isolated as decision tasks.
+- Artifacts: proposal source at =docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org=; grounded against =wrap-it-up.org= Step 3, =todo-cleanup.el= =tc--find-section=, and =inbox-send.py= discovery.
diff --git a/todo.org b/todo.org
index 6d94160..7ff30de 100644
--- a/todo.org
+++ b/todo.org
@@ -34,22 +34,14 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest
* Rulesets Open Work
-** TODO [#B] Wrap-up inbox/transcript routing to destination projects :feature:spec:
+** DOING [#B] Wrap-up inbox/transcript routing to destination projects :feature:spec:
:PROPERTIES:
:CREATED: [2026-06-13 Sat]
:LAST_REVIEWED: 2026-06-13
:END:
-Add a routing step to =wrap-it-up.org=: when outstanding inbox items remain, surface them, recommend a destination project for each, and offer a batch-level "go with recommendations / skip" (not per-item, not a wrap gate). Task/event items move into the destination's =todo.org= "Open Work" section per =todo-format.md=; a parallel step files meeting transcripts into =assets/= per =working-files.md=.
+Optional wrap-up step that surfaces inbox items belonging to another project, recommends a destination, and batch-moves them into that project's =todo.org= Open Work section (transcript filing deferred to vNext). Spec drafted, decisions D1/D4/D5 awaiting Craig before implementation-ready.
-Spec-bound, not implement-now — clears the spec-create Phase 0 bar (>6h, real design uncertainty, cross-cutting, hard to reverse). Run =spec-create= first. Design questions the spec must settle:
-- Reconcile with this session's defer-and-stage router in =process-inbox.org= (the Skeptical Review's VERIFY path) — two "move inbox items to todo.org" mechanisms shouldn't compete.
-- Merge into the existing wrap-up "Inbox sanity check" step or stay a separate optional router (archsetup leans separate: one gates, one is optional).
-- Transcript half has an unresolved dependency (where recordings accumulate) and an open question (extract action items into todo.org, or file the artifact only and let the task-router handle items). Consider splitting transcript work to vNext.
-- Recommendation engine: infer destination from item content against discovered projects; low-confidence recs must flag themselves so the single keystroke stays trustworthy.
-- Widen project discovery from "=.ai/protocols.org= present" to "has =todo.org= with an Open Work heading" (reuse =todo-cleanup.el='s =--archive-done= matcher); a plain code repo with a =todo.org= is a valid destination.
-- Cross-project write discipline (=cross-project.md=): the batch "go" authorizes the write into another project's scope, but the move stays visible and leaves a provenance note. Likely needs an atomic cross-project move helper (extend =todo-cleanup.el= or a new =.ai/scripts= tool).
-
-Source: archsetup handoff 2026-06-13 (=inbox/2026-06-13-0021-from-archsetup-wrapup-inbox-routing-request.org=, moved to =docs/design/= on filing). No archsetup-side work; entirely a rulesets workflow change.
+Spec: [[file:docs/design/wrapup-routing-spec.org]]. Source proposal: [[file:docs/design/2026-06-13-wrapup-inbox-transcript-routing-proposal.org]] (archsetup handoff 2026-06-13). Next: =spec-review= once the open decisions resolve.
** TODO [#B] Helper-instance support — concurrent same-project Claude :feature:spec:
:PROPERTIES: