Skip to content

Commit effaf3f

Browse files
author
Douglas Jones
committed
dispatch-check: add completeness guard + agentStop hook
- codifide dispatch-check subcommand: flags orphaned readouts (Quill .readout.md without paired Glyph .yaml) and missing session-close pairs for today's date - agentStop hook: runs dispatch-check automatically when the agent stops; prompts to file missing artifacts if gaps found - Prevents Quill and Glyph from silently leaving incomplete dispatch pairs at session end
1 parent 75ef27c commit effaf3f

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"enabled": true,
3+
"name": "Dispatch Completeness Check",
4+
"description": "When the agent stops, check that every Quill readout has a paired Glyph YAML and that today's session has a session-close pair. If gaps are found, prompt the agent to file the missing artifacts before the session ends.",
5+
"version": "1",
6+
"when": {
7+
"type": "agentStop"
8+
},
9+
"then": {
10+
"type": "askAgent",
11+
"prompt": "Run `python3 -m codifide dispatch-check` and report the result. If it exits non-zero, file all missing dispatch artifacts before this session is considered complete. Every Quill readout (.readout.md) needs a paired Glyph YAML (.yaml). Every session that filed dispatches needs a session-close.readout.md and session-close.yaml pair. Do not end the session with gaps outstanding."
12+
}
13+
}

codifide/__main__.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,79 @@ def cmd_dispatch_index(args: argparse.Namespace) -> int:
562562
return 0
563563

564564

565+
def cmd_dispatch_check(args: argparse.Namespace) -> int:
566+
"""Check dispatch stream completeness — flag orphaned readouts and missing pairs.
567+
568+
Every Quill readout (.readout.md) should have a paired Glyph YAML (.yaml).
569+
Every session date should have a session-close pair.
570+
Proposals that have a readout but no YAML are flagged.
571+
572+
Exits 0 if complete, 1 if gaps found. Designed to run as an agentStop
573+
hook so Quill and Glyph don't fall asleep on the job.
574+
"""
575+
from pathlib import Path as _Path
576+
from .dispatch_index import _collect_entries
577+
578+
repo_root = _Path(__file__).resolve().parent.parent
579+
dispatch_dir = repo_root / "dispatches"
580+
if not dispatch_dir.exists():
581+
return 0
582+
583+
entries = _collect_entries(dispatch_dir)
584+
gaps = []
585+
586+
for e in entries:
587+
# A readout without a paired YAML is an orphan.
588+
# Exception: standalone .md dispatches (audit notes, journals)
589+
# that were never intended to have a YAML pair are fine — they
590+
# show up with readout_path but no yaml_path AND their slug
591+
# doesn't end in a pattern that implies a pair is expected.
592+
# We flag it if the readout path ends in .readout.md (explicit
593+
# Quill output) but no YAML exists.
594+
if (
595+
e.readout_path
596+
and e.readout_path.endswith(".readout.md")
597+
and not e.yaml_path
598+
):
599+
gaps.append(
600+
f" ORPHAN {e.date}-{e.slug}: "
601+
f"readout exists but no paired Glyph YAML"
602+
)
603+
604+
# A YAML without a readout is unusual but not always wrong
605+
# (some Glyph dispatches are standalone). Don't flag these.
606+
607+
# Check for session-close pair on any date that has other dispatches.
608+
dates_with_dispatches = {e.date for e in entries}
609+
dates_with_close = {
610+
e.date for e in entries
611+
if e.slug == "session-close" and e.readout_path and e.yaml_path
612+
}
613+
# Only flag today's date if it has dispatches but no close pair —
614+
# past sessions are already closed.
615+
import datetime
616+
today = datetime.date.today().isoformat()
617+
if today in dates_with_dispatches and today not in dates_with_close:
618+
gaps.append(
619+
f" MISSING {today}-session-close: "
620+
f"dispatches filed today but no session-close pair"
621+
)
622+
623+
if gaps:
624+
print("codifide dispatch-check: gaps found:")
625+
for g in gaps:
626+
print(g)
627+
print(
628+
"\nFile the missing artifacts before ending the session. "
629+
"Every Quill readout needs a paired Glyph YAML. "
630+
"Every session needs a session-close pair."
631+
)
632+
return 1
633+
634+
print("codifide dispatch-check: all dispatch pairs complete.")
635+
return 0
636+
637+
565638
def _default_entry(module) -> str:
566639
# If there's only one definition, use it; else prefer `main`.
567640
if len(module.symbols) == 1:
@@ -639,6 +712,12 @@ def main(argv=None) -> int:
639712
)
640713
p_dispatch_index.set_defaults(func=cmd_dispatch_index)
641714

715+
p_dispatch_check = sub.add_parser(
716+
"dispatch-check",
717+
help="check dispatch stream completeness — flag orphaned readouts and missing pairs",
718+
)
719+
p_dispatch_check.set_defaults(func=cmd_dispatch_check)
720+
642721
# Symbol store. A store root can be passed via --store or the
643722
# CODIFIDE_STORE environment variable; defaults to ~/.codifide/store.
644723
p_store = sub.add_parser(

0 commit comments

Comments
 (0)