os-status: add tracker-configured SessionStart check (refs #11)

Reads the `tracker` key from .cc-os/config, validating the
planka:<board> | forgejo:<owner>/<repo> | github:<owner>/<repo> |
repo:<path> grammar. Present+valid is silent, malformed is a one-line
warn (never a crash), and absent-in-a-git-project is a daily-snoozed
nudge pointing at the os-backlog routing skill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XknQRvihHDpYE47RTmUR4N
This commit is contained in:
jared 2026-07-10 13:27:42 -04:00
parent 3e42aecc13
commit 3c5a17c288
2 changed files with 96 additions and 0 deletions

View File

@ -195,6 +195,40 @@ def orchestration_audit_due(ctx: Ctx) -> CheckResult:
) )
TRACKER_ABSENT_NOTE = (
"No 'tracker' key set in .cc-os/config — this project's issue tracker is"
" unregistered. Run the os-backlog routing skill to pick a tracker and"
" record it (planka:<board> | forgejo:<owner>/<repo> |"
" github:<owner>/<repo> | repo:<path>)."
)
_TRACKER_PATTERNS = {
"planka": re.compile(r"^[^/\s]+$"),
"forgejo": re.compile(r"^[^/\s]+/[^/\s]+$"),
"github": re.compile(r"^[^/\s]+/[^/\s]+$"),
"repo": re.compile(r"^\S+$"),
}
def tracker_configured(ctx: Ctx) -> CheckResult:
"""Read the 'tracker' key from .cc-os/config. Present + valid -> silent
(configured projects cost nothing at session start); present + malformed
-> one-line warn, never a crash; absent -> daily-snoozed nudge toward the
os-backlog routing skill."""
raw = str(ctx.config.get("tracker", "")).strip()
if not raw:
return warn(TRACKER_ABSENT_NOTE)
scheme, sep, rest = raw.partition(":")
pattern = _TRACKER_PATTERNS.get(scheme)
if not sep or not pattern or not pattern.match(rest):
return warn(
f".cc-os/config sets tracker = '{raw}', which does not match the"
" expected grammar (planka:<board> | forgejo:<owner>/<repo> |"
" github:<owner>/<repo> | repo:<path>) — fix the value."
)
return OK
@dataclass(frozen=True) @dataclass(frozen=True)
class Check: class Check:
name: str name: str
@ -207,4 +241,5 @@ REGISTRY = [
Check("adr-system-present", adr_system_present, project_scoped=True), Check("adr-system-present", adr_system_present, project_scoped=True),
Check("vault-hub-note-present", vault_hub_note_present, project_scoped=True), Check("vault-hub-note-present", vault_hub_note_present, project_scoped=True),
Check("orchestration-audit-due", orchestration_audit_due, project_scoped=False), Check("orchestration-audit-due", orchestration_audit_due, project_scoped=False),
Check("tracker-configured", tracker_configured, project_scoped=True),
] ]

View File

@ -22,11 +22,13 @@ from checks import ( # noqa: E402
ABSENT_NOTE, ABSENT_NOTE,
ENV_VAR, ENV_VAR,
PRESENT_NOTE, PRESENT_NOTE,
TRACKER_ABSENT_NOTE,
Check, Check,
CheckResult, CheckResult,
Ctx, Ctx,
adr_system_present, adr_system_present,
subagent_model_env_override, subagent_model_env_override,
tracker_configured,
vault_hub_note_present, vault_hub_note_present,
) )
from state import StateDir, find_project_root, read_config # noqa: E402 from state import StateDir, find_project_root, read_config # noqa: E402
@ -230,6 +232,65 @@ class AuditDueCheckTest(unittest.TestCase):
self.assertEqual("ok", checks.orchestration_audit_due(make_ctx(environ=env)).status) self.assertEqual("ok", checks.orchestration_audit_due(make_ctx(environ=env)).status)
class TrackerCheckTest(unittest.TestCase):
def test_absent_warns_pointing_to_routing_skill(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
result = tracker_configured(make_ctx(project_root=root))
self.assertEqual(CheckResult("warn", TRACKER_ABSENT_NOTE), result)
self.assertIn("os-backlog routing skill", result.message)
def test_planka_value_is_ok(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "planka:my-board"})
self.assertEqual("ok", tracker_configured(ctx).status)
def test_forgejo_value_is_ok(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "forgejo:jared/cc-os"})
self.assertEqual("ok", tracker_configured(ctx).status)
def test_github_value_is_ok(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "github:jared/cc-os"})
self.assertEqual("ok", tracker_configured(ctx).status)
def test_repo_value_is_ok(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "repo:issues/"})
self.assertEqual("ok", tracker_configured(ctx).status)
def test_unknown_scheme_warns_without_crashing(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "trello:board123"})
result = tracker_configured(ctx)
self.assertEqual("warn", result.status)
self.assertIn("trello:board123", result.message)
def test_missing_separator_warns_without_crashing(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "planka-my-board"})
result = tracker_configured(ctx)
self.assertEqual("warn", result.status)
def test_forgejo_missing_repo_part_warns(self):
with TemporaryDirectory() as tmp:
root = git_project(tmp)
ctx = make_ctx(project_root=root, config={"tracker": "forgejo:jared"})
result = tracker_configured(ctx)
self.assertEqual("warn", result.status)
def test_is_project_scoped_and_daily_snoozed_via_registry(self):
entry = next(c for c in checks.REGISTRY if c.name == "tracker-configured")
self.assertTrue(entry.project_scoped)
class RunnerTest(unittest.TestCase): class RunnerTest(unittest.TestCase):
"""run() contract: routing, aggregation, snooze/suppress, isolation.""" """run() contract: routing, aggregation, snooze/suppress, isolation."""