#!/usr/bin/env python3 """session_start.py — deterministic SessionStart injection note for os-backlog. Modeled on os-adr's session_start.py (the wording-source-of-record pattern). Injects the backlog process rules — WHEN->THEN mechanical triggers only. Deliberately contains ZERO board state and no briefs (notification policy v2: pull beats push — board state is fetched on demand via /os-backlog:list, never pushed into a session). Sibling hook triage_check.rb (card 1818330518584820864) is a narrow, sanctioned exception to that policy: it makes one bounded Planka read to decide whether to push a single mechanical dispatch directive — not a brief, not board contents — when the current project's own Backlog has unlabeled cards. The only board- derived fact it ever surfaces is the unlabeled count that justifies dispatching card-triage; it never lists card titles or otherwise renders board state. Kept as a separate script precisely so this file can stay a pure, network-free function of filesystem state. git project -> additionalContext note (rules only, near-zero tokens) not a git repo -> silent (process rules only apply inside real projects) Autonomy-label semantics in NOTE are the ADR-029 contract (which supersedes the wording in issue #16's body): hitl never picked up autonomously; semi stops at Review for human sign-off; afk-ready goes straight to Done after verification, skipping Review. Pure function of the project's filesystem state; no model, no network, no Ruby, no Planka call. Always exits 0 — the hook must never block a session. """ from __future__ import annotations import json import sys from pathlib import Path from typing import Optional NOTE = """\ [os-backlog] Backlog process rules (rules only — this note never carries board state; board state is pull-only via /os-backlog:list when the user asks). - CAPTURE: WHEN a concrete follow-up task, bug, or piece of deferred work surfaces mid-session that will NOT be done this session -> capture it as a Backlog card via /os-backlog:capture. Never a TODO comment in code, never only a mention in chat. - ROUTING: WHEN process/backlog/spec-tracking work surfaces -> read the tracker key from .cc-os/config (`tracker=planka:` | `forgejo:/` | `github:/` | `repo:`) and send work where it says. WHEN this is a git project and no tracker key is configured -> suggest /os-backlog:route once; do not nag. A subdirectory's own .cc-os/config overrides the repo root's for any session started there — umbrella repos route each subproject to its own board/tracker this way; do not assume one repo means one board. - PROMOTION: WHEN a Planka-tracked effort accretes significant code or design decisions in card comments -> flag promotion to the user (never silently): carve out a repo, move the durable spec into git issues via /to-issues, and keep the Planka card as a pointer (title + link). Never duplicate spec text into a card description. - COLUMN OWNERSHIP (CLI-enforced per ADR-029; a violating card-move fails with the rule named): create cards at Backlog only. Never move a card into or out of Next — Next is human-curated in both directions. Never move a card labeled hitl; never move a card out of Done. Working a card MEANS moving it via card-move: Doing when work starts, Waiting + a blocker comment when blocked, and on shipping: Review (semi) or Done (afk-ready, only after verification). - LABELING/TRIAGE: priority (P0-P3) and autonomy (hitl/semi/afk-ready) labeling is the RESPONSIBLE project's job (it knows its own priorities) — a parent umbrella may label its own and its child boards, but never label cards on a board that belongs to a different project you're just filing onto. Triage is initiated by this project's own SessionStart check (a separate hook), not by capture — if it reports unlabeled Backlog cards, dispatch the card-triage agent (haiku, background) to clear them. - CROSS-PROJECT: WHEN a needed change belongs to a DIFFERENT project THEN do not edit it — query `os-backlog projects`, file an issue/card with that project's tracker using the Discoverer template (see os-backlog route references/cross-project-filing.md). - AUTONOMY LABELS (ADR-029): hitl -> human-owned; never picked up autonomously. semi -> run the mechanical parts, stop at named decision gates; shipped work goes to Review for human sign-off. afk-ready -> shipped + verified goes straight to Done, skipping Review. """ def find_project_root(cwd: Path) -> Optional[Path]: """Nearest ancestor (including cwd) containing .git, else None.""" for candidate in [cwd, *cwd.parents]: if (candidate / ".git").exists(): return candidate return None def run(root: Optional[Path], out) -> str: """Pure decision: ('context'|'silent'). Emits hook JSON when in a project.""" if root is None: return "silent" out.write(json.dumps({ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": NOTE, } })) return "context" def main() -> int: try: run(find_project_root(Path.cwd()), sys.stdout) except Exception: pass # never block a session return 0 if __name__ == "__main__": sys.exit(main())